UNPKG

11.5 kBJavaScriptView Raw
1/* jslint esnext: true */
2
3// Match these datetime components in a CLDR pattern, except those in single quotes
4let expDTComponents = /(?:[Eec]{1,6}|G{1,5}|[Qq]{1,5}|(?:[yYur]+|U{1,5})|[ML]{1,5}|d{1,2}|D{1,3}|F{1}|[abB]{1,5}|[hkHK]{1,2}|w{1,2}|W{1}|m{1,2}|s{1,2}|[zZOvVxX]{1,4})(?=([^']*'[^']*')*[^']*$)/g;
5// trim patterns after transformations
6let expPatternTrimmer = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
7// Skip over patterns with these datetime components because we don't have data
8// to back them up:
9// timezone, weekday, amoung others
10let unwantedDTCs = /[rqQASjJgwWIQq]/; // xXVO were removed from this list in favor of computing matches with timeZoneName values but printing as empty string
11
12let dtKeys = ["weekday", "era", "year", "month", "day", "weekday", "quarter"];
13let tmKeys = ["hour", "minute", "second", "hour12", "timeZoneName"];
14
15function isDateFormatOnly(obj) {
16 for (let i = 0; i < tmKeys.length; i += 1) {
17 if (obj.hasOwnProperty(tmKeys[i])) {
18 return false;
19 }
20 }
21 return true;
22}
23
24function isTimeFormatOnly(obj) {
25 for (let i = 0; i < dtKeys.length; i += 1) {
26 if (obj.hasOwnProperty(dtKeys[i])) {
27 return false;
28 }
29 }
30 return true;
31}
32
33function joinDateAndTimeFormats(dateFormatObj, timeFormatObj) {
34 let o = { _: {} };
35 for (let i = 0; i < dtKeys.length; i += 1) {
36 if (dateFormatObj[dtKeys[i]]) {
37 o[dtKeys[i]] = dateFormatObj[dtKeys[i]];
38 }
39 if (dateFormatObj._[dtKeys[i]]) {
40 o._[dtKeys[i]] = dateFormatObj._[dtKeys[i]];
41 }
42 }
43 for (let j = 0; j < tmKeys.length; j += 1) {
44 if (timeFormatObj[tmKeys[j]]) {
45 o[tmKeys[j]] = timeFormatObj[tmKeys[j]];
46 }
47 if (timeFormatObj._[tmKeys[j]]) {
48 o._[tmKeys[j]] = timeFormatObj._[tmKeys[j]];
49 }
50 }
51 return o;
52}
53
54function computeFinalPatterns(formatObj) {
55 // From http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns:
56 // 'In patterns, two single quotes represents a literal single quote, either
57 // inside or outside single quotes. Text within single quotes is not
58 // interpreted in any way (except for two adjacent single quotes).'
59 formatObj.pattern12 = formatObj.extendedPattern.replace(/'([^']*)'/g, ($0, literal) => {
60 return literal ? literal : "'";
61 });
62
63 // pattern 12 is always the default. we can produce the 24 by removing {ampm}
64 formatObj.pattern = formatObj.pattern12.replace('{ampm}', '').replace(expPatternTrimmer, '');
65 return formatObj;
66}
67
68function expDTComponentsMeta($0, formatObj) {
69 switch ($0.charAt(0)) {
70 // --- Era
71 case 'G':
72 formatObj.era = [ 'short', 'short', 'short', 'long', 'narrow' ][$0.length-1];
73 return '{era}';
74
75 // --- Year
76 case 'y':
77 case 'Y':
78 case 'u':
79 case 'U':
80 case 'r':
81 formatObj.year = $0.length === 2 ? '2-digit' : 'numeric';
82 return '{year}';
83
84 // --- Quarter (not supported in this polyfill)
85 case 'Q':
86 case 'q':
87 formatObj.quarter = [ 'numeric', '2-digit', 'short', 'long', 'narrow' ][$0.length-1];
88 return '{quarter}';
89
90 // --- Month
91 case 'M':
92 case 'L':
93 formatObj.month = [ 'numeric', '2-digit', 'short', 'long', 'narrow' ][$0.length-1];
94 return '{month}';
95
96 // --- Week (not supported in this polyfill)
97 case 'w':
98 // week of the year
99 formatObj.week = $0.length === 2 ? '2-digit' : 'numeric';
100 return '{weekday}';
101 case 'W':
102 // week of the month
103 formatObj.week = 'numeric';
104 return '{weekday}';
105
106 // --- Day
107 case 'd':
108 // day of the month
109 formatObj.day = $0.length === 2 ? '2-digit' : 'numeric';
110 return '{day}';
111 case 'D': // day of the year
112 case 'F': // day of the week
113 case 'g':
114 // 1..n: Modified Julian day
115 formatObj.day = 'numeric';
116 return '{day}';
117
118 // --- Week Day
119 case 'E':
120 // day of the week
121 formatObj.weekday = [ 'short', 'short', 'short', 'long', 'narrow', 'short' ][$0.length-1];
122 return '{weekday}';
123 case 'e':
124 // local day of the week
125 formatObj.weekday = [ 'numeric', '2-digit', 'short', 'long', 'narrow', 'short' ][$0.length-1];
126 return '{weekday}';
127 case 'c':
128 // stand alone local day of the week
129 formatObj.weekday = [ 'numeric', undefined, 'short', 'long', 'narrow', 'short' ][$0.length-1];
130 return '{weekday}';
131
132 // --- Period
133 case 'a': // AM, PM
134 case 'b': // am, pm, noon, midnight
135 case 'B': // flexible day periods
136 formatObj.hour12 = true;
137 return '{ampm}';
138
139 // --- Hour
140 case 'h':
141 case 'H':
142 formatObj.hour = $0.length === 2 ? '2-digit' : 'numeric';
143 return '{hour}';
144 case 'k':
145 case 'K':
146 formatObj.hour12 = true; // 12-hour-cycle time formats (using h or K)
147 formatObj.hour = $0.length === 2 ? '2-digit' : 'numeric';
148 return '{hour}';
149
150 // --- Minute
151 case 'm':
152 formatObj.minute = $0.length === 2 ? '2-digit' : 'numeric';
153 return '{minute}';
154
155 // --- Second
156 case 's':
157 formatObj.second = $0.length === 2 ? '2-digit' : 'numeric';
158 return '{second}';
159 case 'S':
160 case 'A':
161 formatObj.second = 'numeric';
162 return '{second}';
163
164 // --- Timezone
165 case 'z': // 1..3, 4: specific non-location format
166 case 'Z': // 1..3, 4, 5: The ISO8601 varios formats
167 case 'O': // 1, 4: miliseconds in day short, long
168 case 'v': // 1, 4: generic non-location format
169 case 'V': // 1, 2, 3, 4: time zone ID or city
170 case 'X': // 1, 2, 3, 4: The ISO8601 varios formats
171 case 'x': // 1, 2, 3, 4: The ISO8601 varios formats
172 // this polyfill only supports much, for now, we are just doing something dummy
173 formatObj.timeZoneName = $0.length < 4 ? 'short' : 'long';
174 return '{timeZoneName}';
175 }
176}
177
178
179/**
180 * Converts the CLDR availableFormats into the objects and patterns required by
181 * the ECMAScript Internationalization API specification.
182 */
183export function createDateTimeFormat(skeleton, pattern) {
184 // we ignore certain patterns that are unsupported to avoid this expensive op.
185 if (unwantedDTCs.test(pattern))
186 return undefined;
187
188 let formatObj = {
189 originalPattern: pattern,
190 _: {},
191 };
192
193 // Replace the pattern string with the one required by the specification, whilst
194 // at the same time evaluating it for the subsets and formats
195 formatObj.extendedPattern = pattern.replace(expDTComponents, ($0) => {
196 // See which symbol we're dealing with
197 return expDTComponentsMeta($0, formatObj._);
198 });
199
200 // Match the skeleton string with the one required by the specification
201 // this implementation is based on the Date Field Symbol Table:
202 // http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
203 // Note: we are adding extra data to the formatObject even though this polyfill
204 // might not support it.
205 skeleton.replace(expDTComponents, ($0) => {
206 // See which symbol we're dealing with
207 return expDTComponentsMeta($0, formatObj);
208 });
209
210 return computeFinalPatterns(formatObj);
211}
212
213/**
214 * Processes DateTime formats from CLDR to an easier-to-parse format.
215 * the result of this operation should be cached the first time a particular
216 * calendar is analyzed.
217 *
218 * The specification requires we support at least the following subsets of
219 * date/time components:
220 *
221 * - 'weekday', 'year', 'month', 'day', 'hour', 'minute', 'second'
222 * - 'weekday', 'year', 'month', 'day'
223 * - 'year', 'month', 'day'
224 * - 'year', 'month'
225 * - 'month', 'day'
226 * - 'hour', 'minute', 'second'
227 * - 'hour', 'minute'
228 *
229 * We need to cherry pick at least these subsets from the CLDR data and convert
230 * them into the pattern objects used in the ECMA-402 API.
231 */
232export function createDateTimeFormats(formats) {
233 let availableFormats = formats.availableFormats;
234 let timeFormats = formats.timeFormats;
235 let dateFormats = formats.dateFormats;
236 let result = [];
237 let skeleton, pattern, computed, i, j;
238 let timeRelatedFormats = [];
239 let dateRelatedFormats = [];
240
241 // Map available (custom) formats into a pattern for createDateTimeFormats
242 for (skeleton in availableFormats) {
243 if (availableFormats.hasOwnProperty(skeleton)) {
244 pattern = availableFormats[skeleton];
245 computed = createDateTimeFormat(skeleton, pattern);
246 if (computed) {
247 result.push(computed);
248 // in some cases, the format is only displaying date specific props
249 // or time specific props, in which case we need to also produce the
250 // combined formats.
251 if (isDateFormatOnly(computed)) {
252 dateRelatedFormats.push(computed);
253 } else if (isTimeFormatOnly(computed)) {
254 timeRelatedFormats.push(computed);
255 }
256 }
257 }
258 }
259
260 // Map time formats into a pattern for createDateTimeFormats
261 for (skeleton in timeFormats) {
262 if (timeFormats.hasOwnProperty(skeleton)) {
263 pattern = timeFormats[skeleton];
264 computed = createDateTimeFormat(skeleton, pattern);
265 if (computed) {
266 result.push(computed);
267 timeRelatedFormats.push(computed);
268 }
269 }
270 }
271
272 // Map date formats into a pattern for createDateTimeFormats
273 for (skeleton in dateFormats) {
274 if (dateFormats.hasOwnProperty(skeleton)) {
275 pattern = dateFormats[skeleton];
276 computed = createDateTimeFormat(skeleton, pattern);
277 if (computed) {
278 result.push(computed);
279 dateRelatedFormats.push(computed);
280 }
281 }
282 }
283
284 // combine custom time and custom date formats when they are orthogonals to complete the
285 // formats supported by CLDR.
286 // This Algo is based on section "Missing Skeleton Fields" from:
287 // http://unicode.org/reports/tr35/tr35-dates.html#availableFormats_appendItems
288 for (i = 0; i < timeRelatedFormats.length; i += 1) {
289 for (j = 0; j < dateRelatedFormats.length; j += 1) {
290 if (dateRelatedFormats[j].month === 'long') {
291 pattern = dateRelatedFormats[j].weekday ? formats.full : formats.long;
292 } else if (dateRelatedFormats[j].month === 'short') {
293 pattern = formats.medium;
294 } else {
295 pattern = formats.short;
296 }
297 computed = joinDateAndTimeFormats(dateRelatedFormats[j], timeRelatedFormats[i]);
298 computed.originalPattern = pattern;
299 computed.extendedPattern = pattern
300 .replace('{0}', timeRelatedFormats[i].extendedPattern)
301 .replace('{1}', dateRelatedFormats[j].extendedPattern)
302 .replace(/^[,\s]+|[,\s]+$/gi, '');
303 result.push(computeFinalPatterns(computed));
304 }
305 }
306
307 return result;
308}