Skip to content

Commit 7e5d24d

Browse files
authored
Add Interval.toLocaleString() (#1320)
* Add Interval.toLocaleString()
1 parent 545ace5 commit 7e5d24d

File tree

3 files changed

+200
-5
lines changed

3 files changed

+200
-5
lines changed

src/impl/formatter.js

+5
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ export default class Formatter {
108108
return df.formatToParts();
109109
}
110110

111+
formatInterval(interval, opts = {}) {
112+
const df = this.loc.dtFormatter(interval.start, { ...this.opts, ...opts });
113+
return df.dtf.formatRange(interval.start.toJSDate(), interval.end.toJSDate());
114+
}
115+
111116
resolvedOptions(dt, opts = {}) {
112117
const df = this.loc.dtFormatter(dt, { ...this.opts, ...opts });
113118
return df.resolvedOptions();

src/interval.js

+35-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import Duration from "./duration.js";
33
import Settings from "./settings.js";
44
import { InvalidArgumentError, InvalidIntervalError } from "./errors.js";
55
import Invalid from "./impl/invalid.js";
6+
import Formatter from "./impl/formatter.js";
7+
import * as Formats from "./impl/formats.js";
68

79
const INVALID = "Invalid Interval";
810

@@ -32,7 +34,7 @@ function validateStartEnd(start, end) {
3234
* * **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}.
3335
* * **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}.
3436
* * **Comparison** To compare this Interval to another one, use {@link Interval#equals}, {@link Interval#overlaps}, {@link Interval#abutsStart}, {@link Interval#abutsEnd}, {@link Interval#engulfs}
35-
* * **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}.
37+
* * **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}.
3638
*/
3739
export default class Interval {
3840
/**
@@ -529,6 +531,30 @@ export default class Interval {
529531
return `[${this.s.toISO()}${this.e.toISO()})`;
530532
}
531533

534+
/**
535+
* Returns a localized string representing this Interval. Accepts the same options as the
536+
* Intl.DateTimeFormat constructor and any presets defined by Luxon, such as
537+
* {@link DateTime.DATE_FULL} or {@link DateTime.TIME_SIMPLE}. The exact behavior of this method
538+
* is browser-specific, but in general it will return an appropriate representation of the
539+
* Interval in the assigned locale. Defaults to the system's locale if no locale has been
540+
* specified.
541+
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
542+
* @param {Object} [formatOpts=DateTime.DATE_SHORT] - Either a DateTime preset or
543+
* Intl.DateTimeFormat constructor options.
544+
* @param {Object} opts - Options to override the configuration of the start DateTime.
545+
* @example Interval.fromISO('2022-11-07T09:00Z/2022-11-08T09:00Z').toLocaleString(); //=> 11/7/2022 – 11/8/2022
546+
* @example Interval.fromISO('2022-11-07T09:00Z/2022-11-08T09:00Z').toLocaleString(DateTime.DATE_FULL); //=> November 7 – 8, 2022
547+
* @example Interval.fromISO('2022-11-07T09:00Z/2022-11-08T09:00Z').toLocaleString(DateTime.DATE_FULL, { locale: 'fr-FR' }); //=> 7–8 novembre 2022
548+
* @example Interval.fromISO('2022-11-07T17:00Z/2022-11-07T19:00Z').toLocaleString(DateTime.TIME_SIMPLE); //=> 6:00 – 8:00 PM
549+
* @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
550+
* @return {string}
551+
*/
552+
toLocaleString(formatOpts = Formats.DATE_SHORT, opts = {}) {
553+
return this.isValid
554+
? Formatter.create(this.s.loc.clone(opts), formatOpts).formatInterval(this)
555+
: INVALID;
556+
}
557+
532558
/**
533559
* Returns an ISO 8601-compliant string representation of this Interval.
534560
* @see https://en.wikipedia.org/wiki/ISO_8601#Time_intervals
@@ -564,10 +590,14 @@ export default class Interval {
564590
}
565591

566592
/**
567-
* Returns a string representation of this Interval formatted according to the specified format string.
568-
* @param {string} dateFormat - the format string. This string formats the start and end time. See {@link DateTime#toFormat} for details.
569-
* @param {Object} opts - options
570-
* @param {string} [opts.separator = ' – '] - a separator to place between the start and end representations
593+
* Returns a string representation of this Interval formatted according to the specified format
594+
* string. **You may not want this.** See {@link Interval#toLocaleString} for a more flexible
595+
* formatting tool.
596+
* @param {string} dateFormat - The format string. This string formats the start and end time.
597+
* See {@link DateTime#toFormat} for details.
598+
* @param {Object} opts - Options.
599+
* @param {string} [opts.separator = ' – '] - A separator to place between the start and end
600+
* representations.
571601
* @return {string}
572602
*/
573603
toFormat(dateFormat, { separator = " – " } = {}) {

test/interval/format.test.js

+160
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,166 @@ test("Interval#toString returns a simple range format", () =>
1616
test("Interval#toString returns an unfriendly string for invalid intervals", () =>
1717
expect(invalid.toString()).toBe("Invalid Interval"));
1818

19+
//------
20+
// .toLocaleString()
21+
//------
22+
23+
test("Interval#toLocaleString defaults to the DATE_SHORT format", () =>
24+
expect(interval.toLocaleString()).toBe("5/25/1982 – 10/14/1983"));
25+
26+
test("Interval#toLocaleString returns an unfriendly string for invalid intervals", () =>
27+
expect(invalid.toLocaleString()).toBe("Invalid Interval"));
28+
29+
test("Interval#toLocaleString lets the locale set the numbering system", () => {
30+
expect(
31+
Interval.after(interval.start.reconfigure({ locale: "ja-JP" }), { hour: 2 }).toLocaleString({
32+
hour: "numeric",
33+
})
34+
).toBe("9時~11時");
35+
});
36+
37+
test("Interval#toLocaleString accepts locale settings from the start DateTime", () => {
38+
expect(
39+
Interval.fromDateTimes(
40+
interval.start.reconfigure({ locale: "be" }),
41+
interval.end
42+
).toLocaleString()
43+
).toBe("25.5.1982 – 14.10.1983");
44+
});
45+
46+
test("Interval#toLocaleString accepts numbering system settings from the start DateTime", () => {
47+
expect(
48+
Interval.fromDateTimes(
49+
interval.start.reconfigure({ numberingSystem: "beng" }),
50+
interval.end
51+
).toLocaleString()
52+
).toBe("৫/২৫/১৯৮২ – ১০/১৪/১৯৮৩");
53+
});
54+
55+
test("Interval#toLocaleString accepts ouptput calendar settings from the start DateTime", () => {
56+
expect(
57+
Interval.fromDateTimes(
58+
interval.start.reconfigure({ outputCalendar: "islamic" }),
59+
interval.end
60+
).toLocaleString()
61+
).toBe("8/2/1402 – 1/8/1404 AH");
62+
});
63+
64+
test("Interval#toLocaleString accepts options to the formatter", () => {
65+
expect(interval.toLocaleString({ weekday: "short" })).toBe("Tue – Fri");
66+
});
67+
68+
test("Interval#toLocaleString can override the start DateTime's locale", () => {
69+
expect(
70+
Interval.fromDateTimes(
71+
interval.start.reconfigure({ locale: "be" }),
72+
interval.end
73+
).toLocaleString({}, { locale: "fr" })
74+
).toBe("25/05/1982 – 14/10/1983");
75+
});
76+
77+
test("Interval#toLocaleString can override the start DateTime's numbering system", () => {
78+
expect(
79+
Interval.fromDateTimes(
80+
interval.start.reconfigure({ numberingSystem: "beng" }),
81+
interval.end
82+
).toLocaleString({ numberingSystem: "mong" })
83+
).toBe("᠕/᠒᠕/᠑᠙᠘᠒ – ᠑᠐/᠑᠔/᠑᠙᠘᠓");
84+
});
85+
86+
test("Interval#toLocaleString can override the start DateTime's output calendar", () => {
87+
expect(
88+
Interval.fromDateTimes(
89+
interval.start.reconfigure({ outputCalendar: "islamic" }),
90+
interval.end
91+
).toLocaleString({}, { outputCalendar: "coptic" })
92+
).toBe("9/17/1698 – 2/3/1700 ERA1");
93+
});
94+
95+
test("Interval#toLocaleString shows things in the right IANA zone", () => {
96+
expect(
97+
Interval.fromDateTimes(
98+
interval.start.setZone("Australia/Melbourne"),
99+
interval.end
100+
).toLocaleString(DateTime.DATETIME_SHORT)
101+
).toBe("5/25/1982, 7:00 PM – 10/14/1983, 11:30 PM");
102+
});
103+
104+
test("Interval#toLocaleString shows things in the right fixed-offset zone", () => {
105+
expect(
106+
Interval.fromDateTimes(interval.start.setZone("UTC-8"), interval.end).toLocaleString(
107+
DateTime.DATETIME_SHORT
108+
)
109+
).toBe("5/25/1982, 1:00 AM – 10/14/1983, 5:30 AM");
110+
});
111+
112+
test("Interval#toLocaleString shows things in the right fixed-offset zone when showing the zone", () => {
113+
expect(
114+
Interval.fromDateTimes(interval.start.setZone("UTC-8"), interval.end).toLocaleString(
115+
DateTime.DATETIME_FULL
116+
)
117+
).toBe("May 25, 1982 at 1:00 AM GMT-8 – October 14, 1983 at 5:30 AM GMT-8");
118+
});
119+
120+
test("Interval#toLocaleString shows things with UTC if fixed-offset with 0 offset is used", () => {
121+
expect(
122+
Interval.fromDateTimes(interval.start.setZone("UTC"), interval.end).toLocaleString(
123+
DateTime.DATETIME_FULL
124+
)
125+
).toBe("May 25, 1982 at 9:00 AM UTC – October 14, 1983 at 1:30 PM UTC");
126+
});
127+
128+
test("Interval#toLocaleString does the best it can with unsupported fixed-offset zone when showing the zone", () => {
129+
expect(
130+
Interval.fromDateTimes(interval.start.setZone("UTC+4:30"), interval.end).toLocaleString(
131+
DateTime.DATETIME_FULL
132+
)
133+
).toBe("May 25, 1982 at 9:00 AM UTC – October 14, 1983 at 1:30 PM UTC");
134+
});
135+
136+
test("Interval#toLocaleString uses locale-appropriate time formats", () => {
137+
expect(
138+
Interval.after(interval.start.reconfigure({ locale: "en-US" }), { hour: 2 }).toLocaleString(
139+
DateTime.TIME_SIMPLE
140+
)
141+
).toBe("9:00 – 11:00 AM");
142+
expect(
143+
Interval.after(interval.start.reconfigure({ locale: "en-US" }), { hour: 2 }).toLocaleString(
144+
DateTime.TIME_24_SIMPLE
145+
)
146+
).toBe("09:00 – 11:00");
147+
148+
// France has 24-hour by default
149+
expect(
150+
Interval.after(interval.start.reconfigure({ locale: "fr" }), { hour: 2 }).toLocaleString(
151+
DateTime.TIME_SIMPLE
152+
)
153+
).toBe("09:00 – 11:00");
154+
expect(
155+
Interval.after(interval.start.reconfigure({ locale: "fr" }), { hour: 2 }).toLocaleString(
156+
DateTime.TIME_24_SIMPLE
157+
)
158+
).toBe("09:00 – 11:00");
159+
160+
// Spain does't prefix with "0" and doesn't use spaces
161+
expect(
162+
Interval.after(interval.start.reconfigure({ locale: "es" }), { hour: 2 }).toLocaleString(
163+
DateTime.TIME_SIMPLE
164+
)
165+
).toBe("9:00–11:00");
166+
expect(
167+
Interval.after(interval.start.reconfigure({ locale: "es" }), { hour: 2 }).toLocaleString(
168+
DateTime.TIME_24_SIMPLE
169+
)
170+
).toBe("9:00–11:00");
171+
});
172+
173+
test("Interval#toLocaleString sets the separator between days for same-month dates", () => {
174+
expect(Interval.after(interval.start, { day: 2 }).toLocaleString(DateTime.DATE_MED)).toBe(
175+
"May 25 – 27, 1982"
176+
);
177+
});
178+
19179
//------
20180
// .toISO()
21181
//------

0 commit comments

Comments
 (0)