Skip to content

Commit

Permalink
Add Interval.toLocaleString() (#1320)
Browse files Browse the repository at this point in the history
* Add Interval.toLocaleString()
  • Loading branch information
fodier committed Nov 29, 2022
1 parent 545ace5 commit 7e5d24d
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 5 deletions.
5 changes: 5 additions & 0 deletions src/impl/formatter.js
Expand Up @@ -108,6 +108,11 @@ export default class Formatter {
return df.formatToParts();
}

formatInterval(interval, opts = {}) {
const df = this.loc.dtFormatter(interval.start, { ...this.opts, ...opts });
return df.dtf.formatRange(interval.start.toJSDate(), interval.end.toJSDate());
}

resolvedOptions(dt, opts = {}) {
const df = this.loc.dtFormatter(dt, { ...this.opts, ...opts });
return df.resolvedOptions();
Expand Down
40 changes: 35 additions & 5 deletions src/interval.js
Expand Up @@ -3,6 +3,8 @@ import Duration from "./duration.js";
import Settings from "./settings.js";
import { InvalidArgumentError, InvalidIntervalError } from "./errors.js";
import Invalid from "./impl/invalid.js";
import Formatter from "./impl/formatter.js";
import * as Formats from "./impl/formats.js";

const INVALID = "Invalid Interval";

Expand Down Expand Up @@ -32,7 +34,7 @@ function validateStartEnd(start, end) {
* * **Interrogation** To analyze the Interval, use {@link Interval#count}, {@link Interval#length}, {@link Interval#hasSame}, {@link Interval#contains}, {@link Interval#isAfter}, or {@link Interval#isBefore}.
* * **Transformation** To create other Intervals out of this one, use {@link Interval#set}, {@link Interval#splitAt}, {@link Interval#splitBy}, {@link Interval#divideEqually}, {@link Interval.merge}, {@link Interval.xor}, {@link Interval#union}, {@link Interval#intersection}, or {@link Interval#difference}.
* * **Comparison** To compare this Interval to another one, use {@link Interval#equals}, {@link Interval#overlaps}, {@link Interval#abutsStart}, {@link Interval#abutsEnd}, {@link Interval#engulfs}
* * **Output** To convert the Interval into other representations, see {@link Interval#toString}, {@link Interval#toISO}, {@link Interval#toISODate}, {@link Interval#toISOTime}, {@link Interval#toFormat}, and {@link Interval#toDuration}.
* * **Output** To convert the Interval into other representations, see {@link Interval#toString}, {@link Interval#toLocaleString}, {@link Interval#toISO}, {@link Interval#toISODate}, {@link Interval#toISOTime}, {@link Interval#toFormat}, and {@link Interval#toDuration}.
*/
export default class Interval {
/**
Expand Down Expand Up @@ -529,6 +531,30 @@ export default class Interval {
return `[${this.s.toISO()}${this.e.toISO()})`;
}

/**
* Returns a localized string representing this Interval. Accepts the same options as the
* Intl.DateTimeFormat constructor and any presets defined by Luxon, such as
* {@link DateTime.DATE_FULL} or {@link DateTime.TIME_SIMPLE}. The exact behavior of this method
* is browser-specific, but in general it will return an appropriate representation of the
* Interval in the assigned locale. Defaults to the system's locale if no locale has been
* specified.
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
* @param {Object} [formatOpts=DateTime.DATE_SHORT] - Either a DateTime preset or
* Intl.DateTimeFormat constructor options.
* @param {Object} opts - Options to override the configuration of the start DateTime.
* @example Interval.fromISO('2022-11-07T09:00Z/2022-11-08T09:00Z').toLocaleString(); //=> 11/7/2022 – 11/8/2022
* @example Interval.fromISO('2022-11-07T09:00Z/2022-11-08T09:00Z').toLocaleString(DateTime.DATE_FULL); //=> November 7 – 8, 2022
* @example Interval.fromISO('2022-11-07T09:00Z/2022-11-08T09:00Z').toLocaleString(DateTime.DATE_FULL, { locale: 'fr-FR' }); //=> 7–8 novembre 2022
* @example Interval.fromISO('2022-11-07T17:00Z/2022-11-07T19:00Z').toLocaleString(DateTime.TIME_SIMPLE); //=> 6:00 – 8:00 PM
* @example Interval.fromISO('2022-11-07T17:00Z/2022-11-07T19:00Z').toLocaleString({ weekday: 'short', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }); //=> Mon, Nov 07, 6:00 – 8:00 p
* @return {string}
*/
toLocaleString(formatOpts = Formats.DATE_SHORT, opts = {}) {
return this.isValid
? Formatter.create(this.s.loc.clone(opts), formatOpts).formatInterval(this)
: INVALID;
}

/**
* Returns an ISO 8601-compliant string representation of this Interval.
* @see https://en.wikipedia.org/wiki/ISO_8601#Time_intervals
Expand Down Expand Up @@ -564,10 +590,14 @@ export default class Interval {
}

/**
* Returns a string representation of this Interval formatted according to the specified format string.
* @param {string} dateFormat - the format string. This string formats the start and end time. See {@link DateTime#toFormat} for details.
* @param {Object} opts - options
* @param {string} [opts.separator = ' – '] - a separator to place between the start and end representations
* Returns a string representation of this Interval formatted according to the specified format
* string. **You may not want this.** See {@link Interval#toLocaleString} for a more flexible
* formatting tool.
* @param {string} dateFormat - The format string. This string formats the start and end time.
* See {@link DateTime#toFormat} for details.
* @param {Object} opts - Options.
* @param {string} [opts.separator = ' – '] - A separator to place between the start and end
* representations.
* @return {string}
*/
toFormat(dateFormat, { separator = " – " } = {}) {
Expand Down
160 changes: 160 additions & 0 deletions test/interval/format.test.js
Expand Up @@ -16,6 +16,166 @@ test("Interval#toString returns a simple range format", () =>
test("Interval#toString returns an unfriendly string for invalid intervals", () =>
expect(invalid.toString()).toBe("Invalid Interval"));

//------
// .toLocaleString()
//------

test("Interval#toLocaleString defaults to the DATE_SHORT format", () =>
expect(interval.toLocaleString()).toBe("5/25/1982 – 10/14/1983"));

test("Interval#toLocaleString returns an unfriendly string for invalid intervals", () =>
expect(invalid.toLocaleString()).toBe("Invalid Interval"));

test("Interval#toLocaleString lets the locale set the numbering system", () => {
expect(
Interval.after(interval.start.reconfigure({ locale: "ja-JP" }), { hour: 2 }).toLocaleString({
hour: "numeric",
})
).toBe("9時~11時");
});

test("Interval#toLocaleString accepts locale settings from the start DateTime", () => {
expect(
Interval.fromDateTimes(
interval.start.reconfigure({ locale: "be" }),
interval.end
).toLocaleString()
).toBe("25.5.1982 – 14.10.1983");
});

test("Interval#toLocaleString accepts numbering system settings from the start DateTime", () => {
expect(
Interval.fromDateTimes(
interval.start.reconfigure({ numberingSystem: "beng" }),
interval.end
).toLocaleString()
).toBe("৫/২৫/১৯৮২ – ১০/১৪/১৯৮৩");
});

test("Interval#toLocaleString accepts ouptput calendar settings from the start DateTime", () => {
expect(
Interval.fromDateTimes(
interval.start.reconfigure({ outputCalendar: "islamic" }),
interval.end
).toLocaleString()
).toBe("8/2/1402 – 1/8/1404 AH");
});

test("Interval#toLocaleString accepts options to the formatter", () => {
expect(interval.toLocaleString({ weekday: "short" })).toBe("Tue – Fri");
});

test("Interval#toLocaleString can override the start DateTime's locale", () => {
expect(
Interval.fromDateTimes(
interval.start.reconfigure({ locale: "be" }),
interval.end
).toLocaleString({}, { locale: "fr" })
).toBe("25/05/1982 – 14/10/1983");
});

test("Interval#toLocaleString can override the start DateTime's numbering system", () => {
expect(
Interval.fromDateTimes(
interval.start.reconfigure({ numberingSystem: "beng" }),
interval.end
).toLocaleString({ numberingSystem: "mong" })
).toBe("᠕/᠒᠕/᠑᠙᠘᠒ – ᠑᠐/᠑᠔/᠑᠙᠘᠓");
});

test("Interval#toLocaleString can override the start DateTime's output calendar", () => {
expect(
Interval.fromDateTimes(
interval.start.reconfigure({ outputCalendar: "islamic" }),
interval.end
).toLocaleString({}, { outputCalendar: "coptic" })
).toBe("9/17/1698 – 2/3/1700 ERA1");
});

test("Interval#toLocaleString shows things in the right IANA zone", () => {
expect(
Interval.fromDateTimes(
interval.start.setZone("Australia/Melbourne"),
interval.end
).toLocaleString(DateTime.DATETIME_SHORT)
).toBe("5/25/1982, 7:00 PM – 10/14/1983, 11:30 PM");
});

test("Interval#toLocaleString shows things in the right fixed-offset zone", () => {
expect(
Interval.fromDateTimes(interval.start.setZone("UTC-8"), interval.end).toLocaleString(
DateTime.DATETIME_SHORT
)
).toBe("5/25/1982, 1:00 AM – 10/14/1983, 5:30 AM");
});

test("Interval#toLocaleString shows things in the right fixed-offset zone when showing the zone", () => {
expect(
Interval.fromDateTimes(interval.start.setZone("UTC-8"), interval.end).toLocaleString(
DateTime.DATETIME_FULL
)
).toBe("May 25, 1982 at 1:00 AM GMT-8 – October 14, 1983 at 5:30 AM GMT-8");
});

test("Interval#toLocaleString shows things with UTC if fixed-offset with 0 offset is used", () => {
expect(
Interval.fromDateTimes(interval.start.setZone("UTC"), interval.end).toLocaleString(
DateTime.DATETIME_FULL
)
).toBe("May 25, 1982 at 9:00 AM UTC – October 14, 1983 at 1:30 PM UTC");
});

test("Interval#toLocaleString does the best it can with unsupported fixed-offset zone when showing the zone", () => {
expect(
Interval.fromDateTimes(interval.start.setZone("UTC+4:30"), interval.end).toLocaleString(
DateTime.DATETIME_FULL
)
).toBe("May 25, 1982 at 9:00 AM UTC – October 14, 1983 at 1:30 PM UTC");
});

test("Interval#toLocaleString uses locale-appropriate time formats", () => {
expect(
Interval.after(interval.start.reconfigure({ locale: "en-US" }), { hour: 2 }).toLocaleString(
DateTime.TIME_SIMPLE
)
).toBe("9:00 – 11:00 AM");
expect(
Interval.after(interval.start.reconfigure({ locale: "en-US" }), { hour: 2 }).toLocaleString(
DateTime.TIME_24_SIMPLE
)
).toBe("09:00 – 11:00");

// France has 24-hour by default
expect(
Interval.after(interval.start.reconfigure({ locale: "fr" }), { hour: 2 }).toLocaleString(
DateTime.TIME_SIMPLE
)
).toBe("09:00 – 11:00");
expect(
Interval.after(interval.start.reconfigure({ locale: "fr" }), { hour: 2 }).toLocaleString(
DateTime.TIME_24_SIMPLE
)
).toBe("09:00 – 11:00");

// Spain does't prefix with "0" and doesn't use spaces
expect(
Interval.after(interval.start.reconfigure({ locale: "es" }), { hour: 2 }).toLocaleString(
DateTime.TIME_SIMPLE
)
).toBe("9:00–11:00");
expect(
Interval.after(interval.start.reconfigure({ locale: "es" }), { hour: 2 }).toLocaleString(
DateTime.TIME_24_SIMPLE
)
).toBe("9:00–11:00");
});

test("Interval#toLocaleString sets the separator between days for same-month dates", () => {
expect(Interval.after(interval.start, { day: 2 }).toLocaleString(DateTime.DATE_MED)).toBe(
"May 25 – 27, 1982"
);
});

//------
// .toISO()
//------
Expand Down

0 comments on commit 7e5d24d

Please sign in to comment.