Skip to content
This repository was archived by the owner on Aug 1, 2024. It is now read-only.

Commit 9251751

Browse files
Closure Teamcopybara-github
authored andcommitted
RELNOTES[NEW]: New API for Closure/I18N, similar to ICU4J RelativeTimeFormatter.
PiperOrigin-RevId: 508137941 Change-Id: Ic5f5e4fed039d9915ff289293de9b7b2a2bc95ae
1 parent bd24699 commit 9251751

File tree

6 files changed

+20156
-0
lines changed

6 files changed

+20156
-0
lines changed

closure/goog/i18n/BUILD

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ closure_js_library(
2626
":datetimesymbols",
2727
":datetimesymbolsext",
2828
":dayperiodsymbols",
29+
":durationformat",
30+
":durationsymbols",
31+
":durationsymbolsext",
32+
":durationsymboltypes",
2933
":graphemebreak",
3034
":listformat",
3135
":listsymbols",
@@ -298,6 +302,43 @@ closure_js_library(
298302
],
299303
)
300304

305+
closure_js_library(
306+
name = "durationformat",
307+
srcs = ["durationformat.js"],
308+
lenient = True,
309+
deps = [
310+
":durationsymbols",
311+
":durationsymboltypes",
312+
":listformat",
313+
":localefeature",
314+
":messageformat",
315+
"//closure/goog/asserts",
316+
],
317+
)
318+
319+
closure_js_library(
320+
name = "durationsymbols",
321+
srcs = ["durationsymbols.js"],
322+
lenient = True,
323+
deps = [":durationsymboltypes"],
324+
)
325+
326+
closure_js_library(
327+
name = "durationsymbolsext",
328+
srcs = ["durationsymbolsext.js"],
329+
lenient = True,
330+
deps = [
331+
":durationsymbols",
332+
":durationsymboltypes",
333+
],
334+
)
335+
336+
closure_js_library(
337+
name = "durationsymboltypes",
338+
srcs = ["durationsymboltypes.js"],
339+
lenient = True,
340+
)
341+
301342
closure_js_library(
302343
name = "graphemebreak",
303344
srcs = ["graphemebreak.js"],
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
/**
2+
* @license
3+
* Copyright The Closure Library Authors.
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/**
8+
* @fileoverview DurationFormat provides methods to format duration time
9+
* into a human-readable, locale-sensitive string in a user friendly way and a
10+
* locale sensitive manner.
11+
*/
12+
goog.module('goog.i18n.DurationFormat');
13+
14+
const DurationSymbols = goog.require('goog.i18n.DurationSymbols');
15+
const MessageFormat = goog.require('goog.i18n.MessageFormat');
16+
const {DurationSymbols: DurationSymbolsTypes, DurationSymbolsFormatStyles} = goog.require('goog.i18n.DurationSymbolTypes');
17+
const {ListFormat, ListFormatStyle, ListFormatType} = goog.require('goog.i18n.listFormat');
18+
const {assert, assertNumber, assertObject} = goog.require('goog.asserts');
19+
20+
/**
21+
* Choices for options bag 'type' in DurationFormat's constructor.
22+
* @enum {number} DurationFormatStyle
23+
*/
24+
const DurationFormatStyle = {
25+
SHORT: 0,
26+
LONG: 1,
27+
NARROW: 2,
28+
};
29+
exports.DurationFormatStyle = DurationFormatStyle;
30+
31+
/**
32+
* Available keys for the input object of public method format.
33+
* @enum {string} DurationFormatUnit
34+
*/
35+
const DurationFormatUnit = {
36+
YEAR: 'years',
37+
MONTH: 'months',
38+
WEEK: 'weeks',
39+
DAY: 'days',
40+
HOUR: 'hours',
41+
MINUTE: 'minutes',
42+
SECOND: 'seconds'
43+
};
44+
exports.DurationFormatUnit = DurationFormatUnit;
45+
46+
/**
47+
* Collection of duration unit and time for a locale.
48+
* @typedef {{
49+
* days: (number|undefined),
50+
* hours: (number|undefined),
51+
* minutes: (number|undefined),
52+
* months: (number|undefined),
53+
* seconds: (number|undefined),
54+
* weeks: (number|undefined),
55+
* years: (number|undefined)
56+
* }}
57+
*/
58+
let DurationLike; /* The data for the locale */
59+
60+
/** @typedef {!DurationLike} */
61+
exports.DurationLike;
62+
63+
/**
64+
* Collection of duration display style.
65+
* @typedef {{
66+
* style: DurationFormatStyle!
67+
* }}
68+
*/
69+
let DurationFormatOptions; /* The data for the display style */
70+
71+
/** @typedef {!DurationFormatOptions} */
72+
exports.DurationFormatOptions;
73+
74+
class DurationFormat {
75+
/**
76+
* Returns the durationformatter for the locale given by goog.LOCALE.
77+
* specified, a durationformatter for the user's locale will be returned.
78+
* @param {!DurationFormatOptions=} opt_options
79+
* This optional value determines the style of the duration time output.
80+
* A s part of the resulting formatted string, values include LONG, SHORT,
81+
* NARROW. Default is SHORT to keep consistency with ECMAScript.
82+
* @param {!DurationSymbolsTypes=} opt_durationSymbols
83+
* This optional value can be used to override the duration format symbols
84+
* selected using goog.LOCALE. This does not override the symbols used by
85+
* the underlying list formatter, so users overriding this should prefer
86+
* to use format each unit and do list formatting on the results.
87+
* @final
88+
*/
89+
constructor(opt_options, opt_durationSymbols) {
90+
/** @private {!DurationFormatStyle} */
91+
const style = opt_options?.style || DurationFormatStyle.SHORT;
92+
assert(
93+
style >= DurationFormatStyle.SHORT &&
94+
style <= DurationFormatStyle.NARROW,
95+
'Style must be LONG, SHORT, NARROW');
96+
/** @private @const {!DurationFormatStyle} */
97+
this.style_ = style;
98+
99+
/**
100+
* DurationSymbols object for locale data required by the formatter.
101+
* @private @const {!DurationSymbolsTypes}
102+
*/
103+
const durationSymbols =
104+
opt_durationSymbols || DurationSymbols.getDurationSymbols();
105+
assert(durationSymbols !== null, 'Duration symbols cannot be null');
106+
this.durationSymbols_ = durationSymbols;
107+
}
108+
109+
/**
110+
* From the data, return the information for the given unit and style.
111+
* @param {string} durationUnit
112+
* @return {string}
113+
* @private
114+
*/
115+
getIcuFormattingPattern_(durationUnit) {
116+
const unitInfo = this.getIcuFormattingPatternsForUnit_(durationUnit);
117+
assertObject(unitInfo);
118+
return this.getIcuFormattingPatternForStyle_(unitInfo);
119+
};
120+
121+
/**
122+
* From the data, check if the duration unit is legal.
123+
* @param {string} durationUnit
124+
* @return {boolean}
125+
* @private
126+
*/
127+
checkIfLegalUnit_(durationUnit) {
128+
return durationUnit === DurationFormatUnit.YEAR ||
129+
durationUnit === DurationFormatUnit.MONTH ||
130+
durationUnit === DurationFormatUnit.WEEK ||
131+
durationUnit === DurationFormatUnit.DAY ||
132+
durationUnit === DurationFormatUnit.HOUR ||
133+
durationUnit === DurationFormatUnit.MINUTE ||
134+
durationUnit === DurationFormatUnit.SECOND;
135+
}
136+
137+
/**
138+
* Use public unit symbol to retrieve data for that unit.
139+
* @param {string} unit
140+
* @return {!DurationSymbolsFormatStyles}
141+
* @private
142+
*/
143+
getIcuFormattingPatternsForUnit_(unit) {
144+
assert(
145+
this.checkIfLegalUnit_(unit),
146+
'Unit must be years, months, weeks, days, hours, minutes or seconds');
147+
switch (unit) {
148+
default:
149+
case DurationFormatUnit.YEAR:
150+
return this.durationSymbols_.YEAR;
151+
case DurationFormatUnit.MONTH:
152+
return this.durationSymbols_.MONTH;
153+
case DurationFormatUnit.WEEK:
154+
return this.durationSymbols_.WEEK;
155+
case DurationFormatUnit.DAY:
156+
return this.durationSymbols_.DAY;
157+
case DurationFormatUnit.HOUR:
158+
return this.durationSymbols_.HOUR;
159+
case DurationFormatUnit.MINUTE:
160+
return this.durationSymbols_.MINUTE;
161+
case DurationFormatUnit.SECOND:
162+
return this.durationSymbols_.SECOND;
163+
}
164+
};
165+
166+
/**
167+
* Use unit symbol to retrieve data for that unit, given the style.
168+
* @param{!DurationSymbolsFormatStyles} unitInfo
169+
* @return {string}
170+
* @private
171+
*/
172+
getIcuFormattingPatternForStyle_(unitInfo) {
173+
// Fall back from LONG to NARROW to SHORT as needed.
174+
switch (this.style_) {
175+
case DurationFormatStyle.LONG:
176+
if (unitInfo.LONG != undefined) {
177+
return unitInfo.LONG;
178+
}
179+
case DurationFormatStyle.NARROW:
180+
if (unitInfo.NARROW != undefined) {
181+
return unitInfo.NARROW;
182+
}
183+
case DurationFormatStyle.SHORT:
184+
default:
185+
return unitInfo.SHORT;
186+
}
187+
};
188+
189+
/**
190+
* Format using pure JavaScript
191+
* @param {number} quantity Duration time value.
192+
* @param {string} durationUnit string such as hours, years,
193+
* months.
194+
* @return {string} The formatted result. May be empty string for an
195+
* unsupported locale.
196+
* @private
197+
*/
198+
formatPolyfill_(quantity, durationUnit) {
199+
/**
200+
* Find the right data based on unit, quantity, and plural.
201+
*/
202+
const unitStyleString = this.getIcuFormattingPattern_(durationUnit);
203+
if (!unitStyleString) return '';
204+
205+
/**
206+
* Formatter for the messages requiring units. Plural formatting needed.
207+
* @type {?MessageFormat}
208+
*/
209+
// Take basic message and wrap with plural message type.
210+
const msgFormatter =
211+
new MessageFormat('{DURATION_VALUE,plural,' + unitStyleString + '}');
212+
return msgFormatter.format({'DURATION_VALUE': quantity});
213+
};
214+
215+
/**
216+
* Formats a string with the amount and one unit.
217+
* @param {number} quantity A value for the duration time.
218+
* @param {string} durationUnit string such as hours, years,
219+
* months.
220+
* @return {string} The formatted result.
221+
* @private
222+
*/
223+
formatWithUnit_(quantity, durationUnit) {
224+
assertNumber(quantity, 'Quantity must be a number');
225+
assert(quantity >= 0, 'Duration value should not be less than zero.');
226+
227+
// TODO(user): Add formatNative_ method when available.
228+
return this.formatPolyfill_(quantity, durationUnit);
229+
};
230+
231+
/**
232+
* Formats a string with the amount and correspondent unit.
233+
* @param {!DurationLike} durationLike An object for the duration time whose
234+
* keys can be one of DurationFormatUnit value.
235+
* @return {string} The formatted result.
236+
*/
237+
format(durationLike) {
238+
const formattedDurationList = [];
239+
const years = durationLike[DurationFormatUnit.YEAR];
240+
const months = durationLike[DurationFormatUnit.MONTH];
241+
const weeks = durationLike[DurationFormatUnit.WEEK];
242+
const days = durationLike[DurationFormatUnit.DAY];
243+
const hours = durationLike[DurationFormatUnit.HOUR];
244+
const minutes = durationLike[DurationFormatUnit.MINUTE];
245+
const seconds = durationLike[DurationFormatUnit.SECOND];
246+
if (years != null) {
247+
formattedDurationList.push(
248+
this.formatWithUnit_(years, DurationFormatUnit.YEAR));
249+
}
250+
251+
if (months != null) {
252+
formattedDurationList.push(
253+
this.formatWithUnit_(months, DurationFormatUnit.MONTH));
254+
}
255+
256+
if (weeks != null) {
257+
formattedDurationList.push(
258+
this.formatWithUnit_(weeks, DurationFormatUnit.WEEK));
259+
}
260+
261+
if (days != null) {
262+
formattedDurationList.push(
263+
this.formatWithUnit_(days, DurationFormatUnit.DAY));
264+
}
265+
266+
if (hours != null) {
267+
formattedDurationList.push(
268+
this.formatWithUnit_(hours, DurationFormatUnit.HOUR));
269+
}
270+
271+
if (minutes != null) {
272+
formattedDurationList.push(
273+
this.formatWithUnit_(minutes, DurationFormatUnit.MINUTE));
274+
}
275+
276+
if (seconds != null) {
277+
formattedDurationList.push(
278+
this.formatWithUnit_(seconds, DurationFormatUnit.SECOND));
279+
}
280+
281+
// TODO(user): Add method for STYLE.DIGIT when available.
282+
const newListFormater = new ListFormat(
283+
{type: ListFormatType.UNIT, style: ListFormatStyle.NARROW});
284+
return newListFormater.format(formattedDurationList);
285+
};
286+
}
287+
288+
exports.DurationFormat = DurationFormat;

0 commit comments

Comments
 (0)