toISO() {
    // we could use the formatter, but this is an easier way to get the minimum string
    if (!this.isValid) return null;

    let s = "P";
    if (this.years !== 0) s += this.years + "Y";
    if (this.months !== 0 || this.quarters !== 0) s += this.months + this.quarters * 3 + "M";
    if (this.weeks !== 0) s += this.weeks + "W";
    if (this.days !== 0) s += this.days + "D";
    if (this.hours !== 0 || this.minutes !== 0 || this.seconds !== 0 || this.milliseconds !== 0)
      s += "T";
    if (this.hours !== 0) s += this.hours + "H";
    if (this.minutes !== 0) s += this.minutes + "M";
    if (this.seconds !== 0 || this.milliseconds !== 0)
      // this will handle "floating point madness" by removing extra decimal places
      // https://stackoverflow.com/questions/588004/is-floating-point-math-broken
      s += roundTo(this.seconds + this.milliseconds / 1000, 3) + "S";
    if (s === "P") s += "T0S";
    return s;
  }
Returns an ISO 8601-compliant string representation of this Duration.
@see https://en.wikipedia.org/wiki/ISO_8601#Durations
@example Duration.fromObject({ years: 3, seconds: 45 }).toISO() //=> 'P3YT45S'
@example Duration.fromObject({ months: 4, seconds: 45 }).toISO() //=> 'P4MT45S'
@example Duration.fromObject({ months: 5 }).toISO() //=> 'P5M'
@example Duration.fromObject({ minutes: 5 }).toISO() //=> 'PT5M'
@example Duration.fromObject({ milliseconds: 6 }).toISO() //=> 'PT0.006S'
@return {string}