globalize-rpk 1.7.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/globalize/currency.js +590 -0
  2. package/globalize/date.js +3139 -0
  3. package/globalize/globalize-runtime.js +326 -0
  4. package/globalize/globalize.js +507 -0
  5. package/globalize/message.js +2076 -0
  6. package/globalize/number.js +1727 -0
  7. package/globalize/plural.js +376 -0
  8. package/globalize/relative-time.js +203 -0
  9. package/globalize/unit.js +301 -0
  10. package/node-main.js +27 -0
  11. package/package.json +3 -26
  12. package/CONTRIBUTING.md +0 -5
  13. package/README.md +0 -818
  14. package/doc/api/core/constructor.md +0 -28
  15. package/doc/api/core/load.md +0 -96
  16. package/doc/api/core/locale.md +0 -43
  17. package/doc/api/currency/currency-formatter.md +0 -196
  18. package/doc/api/currency/currency-to-parts-formatter.md +0 -117
  19. package/doc/api/date/date-formatter.md +0 -203
  20. package/doc/api/date/date-parser.md +0 -60
  21. package/doc/api/date/date-to-parts-formatter.md +0 -176
  22. package/doc/api/date/load-iana-time-zone.md +0 -29
  23. package/doc/api/message/load-messages.md +0 -105
  24. package/doc/api/message/message-formatter.md +0 -208
  25. package/doc/api/number/number-formatter.md +0 -202
  26. package/doc/api/number/number-parser.md +0 -130
  27. package/doc/api/number/number-to-parts-formatter.md +0 -140
  28. package/doc/api/plural/plural-generator.md +0 -84
  29. package/doc/api/relative-time/relative-time-formatter.md +0 -60
  30. package/doc/api/unit/unit-formatter.md +0 -72
  31. package/doc/blog-post/2017-07-xx-1.3.0-announcement.md +0 -177
  32. package/doc/cldr.md +0 -114
  33. package/doc/error/e-default-locale-not-defined.md +0 -9
  34. package/doc/error/e-invalid-cldr.md +0 -14
  35. package/doc/error/e-invalid-par-type.md +0 -12
  36. package/doc/error/e-invalid-par-value.md +0 -11
  37. package/doc/error/e-missing-cldr.md +0 -11
  38. package/doc/error/e-missing-parameter.md +0 -10
  39. package/doc/error/e-missing-plural-module.md +0 -9
  40. package/doc/error/e-par-missing-key.md +0 -11
  41. package/doc/error/e-par-out-of-range.md +0 -13
  42. package/doc/error/e-unsupported.md +0 -10
  43. package/doc/migrating-from-0.x.md +0 -64
  44. package/examples/amd-bower/.bowerrc +0 -7
  45. package/examples/amd-bower/README.md +0 -65
  46. package/examples/amd-bower/bower.json +0 -13
  47. package/examples/amd-bower/index.html +0 -46
  48. package/examples/amd-bower/main.js +0 -141
  49. package/examples/amd-bower/messages/en.json +0 -12
  50. package/examples/amd-bower/package.json +0 -14
  51. package/examples/app-npm-webpack/README.md +0 -74
  52. package/examples/app-npm-webpack/app/index.js +0 -89
  53. package/examples/app-npm-webpack/index-template.html +0 -71
  54. package/examples/app-npm-webpack/messages/ar.json +0 -25
  55. package/examples/app-npm-webpack/messages/de.json +0 -21
  56. package/examples/app-npm-webpack/messages/en.json +0 -21
  57. package/examples/app-npm-webpack/messages/es.json +0 -21
  58. package/examples/app-npm-webpack/messages/pt.json +0 -21
  59. package/examples/app-npm-webpack/messages/ru.json +0 -23
  60. package/examples/app-npm-webpack/messages/zh.json +0 -20
  61. package/examples/app-npm-webpack/package.json +0 -17
  62. package/examples/app-npm-webpack/webpack-config.js +0 -63
  63. package/examples/globalize-compiler/README.md +0 -45
  64. package/examples/globalize-compiler/app.js +0 -58
  65. package/examples/globalize-compiler/development.html +0 -121
  66. package/examples/globalize-compiler/messages.json +0 -12
  67. package/examples/globalize-compiler/package.json +0 -15
  68. package/examples/globalize-compiler/production.html +0 -75
  69. package/examples/node-npm/README.md +0 -57
  70. package/examples/node-npm/main.js +0 -65
  71. package/examples/node-npm/messages/en.json +0 -12
  72. package/examples/node-npm/package.json +0 -10
  73. package/examples/plain-javascript/README.md +0 -81
  74. package/examples/plain-javascript/index.html +0 -445
@@ -0,0 +1,3139 @@
1
+ /**
2
+ * Globalize v1.7.0
3
+ *
4
+ * https://github.com/globalizejs/globalize
5
+ *
6
+ * Copyright OpenJS Foundation and other contributors
7
+ * Released under the MIT license
8
+ * https://jquery.org/license
9
+ *
10
+ * Date: 2021-08-02T11:53Z
11
+ */
12
+ /*!
13
+ * Globalize v1.7.0 2021-08-02T11:53Z Released under the MIT license
14
+ * http://git.io/TrdQbw
15
+ */
16
+ (function( root, factory ) {
17
+
18
+ // UMD returnExports
19
+ if ( typeof define === "function" && define.amd ) {
20
+
21
+ // AMD
22
+ define([
23
+ "cldr",
24
+ "../globalize",
25
+ "./number",
26
+ "cldr/event",
27
+ "cldr/supplemental"
28
+ ], factory );
29
+ } else if ( typeof exports === "object" ) {
30
+
31
+ // Node, CommonJS
32
+ module.exports = factory( require( "cldrjs" ), require( "../globalize" ) );
33
+ } else {
34
+
35
+ // Extend global
36
+ factory( root.Cldr, root.Globalize );
37
+ }
38
+ }(this, function( Cldr, Globalize ) {
39
+
40
+ var createError = Globalize._createError,
41
+ createErrorUnsupportedFeature = Globalize._createErrorUnsupportedFeature,
42
+ formatMessage = Globalize._formatMessage,
43
+ isPlainObject = Globalize._isPlainObject,
44
+ looseMatching = Globalize._looseMatching,
45
+ numberNumberingSystemDigitsMap = Globalize._numberNumberingSystemDigitsMap,
46
+ numberSymbol = Globalize._numberSymbol,
47
+ partsJoin = Globalize._partsJoin,
48
+ partsPush = Globalize._partsPush,
49
+ regexpEscape = Globalize._regexpEscape,
50
+ removeLiteralQuotes = Globalize._removeLiteralQuotes,
51
+ runtimeBind = Globalize._runtimeBind,
52
+ stringPad = Globalize._stringPad,
53
+ validate = Globalize._validate,
54
+ validateCldr = Globalize._validateCldr,
55
+ validateDefaultLocale = Globalize._validateDefaultLocale,
56
+ validateParameterPresence = Globalize._validateParameterPresence,
57
+ validateParameterType = Globalize._validateParameterType,
58
+ validateParameterTypePlainObject = Globalize._validateParameterTypePlainObject,
59
+ validateParameterTypeString = Globalize._validateParameterTypeString;
60
+
61
+
62
+ var validateParameterTypeDate = function( value, name ) {
63
+ validateParameterType( value, name, value === undefined || value instanceof Date, "Date" );
64
+ };
65
+
66
+
67
+
68
+
69
+ var createErrorInvalidParameterValue = function( name, value ) {
70
+ return createError( "E_INVALID_PAR_VALUE", "Invalid `{name}` value ({value}).", {
71
+ name: name,
72
+ value: value
73
+ });
74
+ };
75
+
76
+
77
+
78
+
79
+ /**
80
+ * Create a map between the skeleton fields and their positions, e.g.,
81
+ * {
82
+ * G: 0
83
+ * y: 1
84
+ * ...
85
+ * }
86
+ */
87
+ var validateSkeletonFieldsPosMap = "GyYuUrQqMLlwWEecdDFghHKkmsSAzZOvVXx".split( "" ).reduce(function( memo, item, i ) {
88
+ memo[ item ] = i;
89
+ return memo;
90
+ }, {});
91
+
92
+
93
+
94
+
95
+ /**
96
+ * validateSkeleton( skeleton )
97
+ *
98
+ * skeleton: Assume `j` has already been converted into a localized hour field.
99
+ */
100
+ var validateSkeleton = function validateSkeleton( skeleton ) {
101
+ var last,
102
+
103
+ // Using easier to read variable.
104
+ fieldsPosMap = validateSkeletonFieldsPosMap;
105
+
106
+ // "The fields are from the Date Field Symbol Table in Date Format Patterns"
107
+ // Ref: http://www.unicode.org/reports/tr35/tr35-dates.html#availableFormats_appendItems
108
+ // I.e., check for invalid characters.
109
+ skeleton.replace( /[^GyYuUrQqMLlwWEecdDFghHKkmsSAzZOvVXx]/, function( field ) {
110
+ throw createError(
111
+ "E_INVALID_OPTIONS", "Invalid field `{invalidField}` of skeleton `{value}`",
112
+ {
113
+ invalidField: field,
114
+ type: "skeleton",
115
+ value: skeleton
116
+ }
117
+ );
118
+ });
119
+
120
+ // "The canonical order is from top to bottom in that table; that is, yM not My".
121
+ // http://www.unicode.org/reports/tr35/tr35-dates.html#availableFormats_appendItems
122
+ // I.e., check for invalid order.
123
+ skeleton.split( "" ).every(function( field ) {
124
+ if ( fieldsPosMap[ field ] < last ) {
125
+ throw createError(
126
+ "E_INVALID_OPTIONS", "Invalid order `{invalidField}` of skeleton `{value}`",
127
+ {
128
+ invalidField: field,
129
+ type: "skeleton",
130
+ value: skeleton
131
+ }
132
+ );
133
+ }
134
+ last = fieldsPosMap[ field ];
135
+ return true;
136
+ });
137
+ };
138
+
139
+
140
+
141
+
142
+ /**
143
+ * Returns a new object created by using `object`'s values as keys, and the keys as values.
144
+ */
145
+ var objectInvert = function( object, fn ) {
146
+ fn = fn || function( object, key, value ) {
147
+ object[ value ] = key;
148
+ return object;
149
+ };
150
+ return Object.keys( object ).reduce(function( newObject, key ) {
151
+ return fn( newObject, key, object[ key ] );
152
+ }, {});
153
+ };
154
+
155
+
156
+
157
+
158
+ // Invert key and values, e.g., {"e": "eEc"} ==> {"e": "e", "E": "e", "c": "e"}.
159
+ var dateExpandPatternSimilarFieldsMap = objectInvert({
160
+ "e": "eEc",
161
+ "L": "ML"
162
+ }, function( object, key, value ) {
163
+ value.split( "" ).forEach(function( field ) {
164
+ object[ field ] = key;
165
+ });
166
+ return object;
167
+ });
168
+
169
+
170
+
171
+
172
+ var dateExpandPatternNormalizePatternType = function( character ) {
173
+ return dateExpandPatternSimilarFieldsMap[ character ] || character;
174
+ };
175
+
176
+
177
+
178
+
179
+ var datePatternRe = ( /([a-z])\1*|'([^']|'')+'|''|./ig );
180
+
181
+
182
+
183
+
184
+ var stringRepeat = function( str, count ) {
185
+ var i, result = "";
186
+ for ( i = 0; i < count; i++ ) {
187
+ result = result + str;
188
+ }
189
+ return result;
190
+ };
191
+
192
+
193
+
194
+
195
+ function expandBestMatchFormat( skeletonWithoutFractionalSeconds, bestMatchFormat ) {
196
+ var i, j, bestMatchFormatParts, matchedType, matchedLength, requestedType,
197
+ requestedLength, requestedSkeletonParts,
198
+
199
+ // Using an easier to read variable.
200
+ normalizePatternType = dateExpandPatternNormalizePatternType;
201
+
202
+ requestedSkeletonParts = skeletonWithoutFractionalSeconds.match( datePatternRe );
203
+ bestMatchFormatParts = bestMatchFormat.match( datePatternRe );
204
+
205
+ for ( i = 0; i < bestMatchFormatParts.length; i++ ) {
206
+ matchedType = bestMatchFormatParts[ i ].charAt( 0 );
207
+ matchedLength = bestMatchFormatParts[ i ].length;
208
+ for ( j = 0; j < requestedSkeletonParts.length; j++ ) {
209
+ requestedType = requestedSkeletonParts[ j ].charAt( 0 );
210
+ requestedLength = requestedSkeletonParts[ j ].length;
211
+ if ( normalizePatternType( matchedType ) === normalizePatternType( requestedType ) &&
212
+ matchedLength < requestedLength
213
+ ) {
214
+ bestMatchFormatParts[ i ] = stringRepeat( matchedType, requestedLength );
215
+ }
216
+ }
217
+ }
218
+
219
+ return bestMatchFormatParts.join( "" );
220
+ }
221
+
222
+ // See: http://www.unicode.org/reports/tr35/tr35-dates.html#Matching_Skeletons
223
+ var dateExpandPatternAugmentFormat = function( requestedSkeleton, bestMatchFormat, decimalSeparator ) {
224
+ var countOfFractionalSeconds, fractionalSecondMatch, lastSecondIdx,
225
+ skeletonWithoutFractionalSeconds;
226
+
227
+ fractionalSecondMatch = requestedSkeleton.match( /S/g );
228
+ countOfFractionalSeconds = fractionalSecondMatch ? fractionalSecondMatch.length : 0;
229
+ skeletonWithoutFractionalSeconds = requestedSkeleton.replace( /S/g, "" );
230
+
231
+ bestMatchFormat = expandBestMatchFormat( skeletonWithoutFractionalSeconds, bestMatchFormat );
232
+
233
+ lastSecondIdx = bestMatchFormat.lastIndexOf( "s" );
234
+ if ( lastSecondIdx !== -1 && countOfFractionalSeconds !== 0 ) {
235
+ bestMatchFormat =
236
+ bestMatchFormat.slice( 0, lastSecondIdx + 1 ) +
237
+ decimalSeparator +
238
+ stringRepeat( "S", countOfFractionalSeconds ) +
239
+ bestMatchFormat.slice( lastSecondIdx + 1 );
240
+ }
241
+ return bestMatchFormat;
242
+ };
243
+
244
+
245
+
246
+
247
+ var dateExpandPatternCompareFormats = function( formatA, formatB ) {
248
+ var a, b, distance, lenA, lenB, typeA, typeB, i, j,
249
+
250
+ // Using easier to read variables.
251
+ normalizePatternType = dateExpandPatternNormalizePatternType;
252
+
253
+ if ( formatA === formatB ) {
254
+ return 0;
255
+ }
256
+
257
+ formatA = formatA.match( datePatternRe );
258
+ formatB = formatB.match( datePatternRe );
259
+
260
+ if ( formatA.length !== formatB.length ) {
261
+ return -1;
262
+ }
263
+
264
+ distance = 1;
265
+ for ( i = 0; i < formatA.length; i++ ) {
266
+ a = formatA[ i ].charAt( 0 );
267
+ typeA = normalizePatternType( a );
268
+ typeB = null;
269
+ for ( j = 0; j < formatB.length; j++ ) {
270
+ b = formatB[ j ].charAt( 0 );
271
+ typeB = normalizePatternType( b );
272
+ if ( typeA === typeB ) {
273
+ break;
274
+ } else {
275
+ typeB = null;
276
+ }
277
+ }
278
+ if ( typeB === null ) {
279
+ return -1;
280
+ }
281
+ lenA = formatA[ i ].length;
282
+ lenB = formatB[ j ].length;
283
+ distance = distance + Math.abs( lenA - lenB );
284
+
285
+ // Most symbols have a small distance from each other, e.g., M ≅ L; E ≅ c; a ≅ b ≅ B;
286
+ // H ≅ k ≅ h ≅ K; ...
287
+ if ( a !== b ) {
288
+ distance += 1;
289
+ }
290
+
291
+ // Numeric (l<3) and text fields (l>=3) are given a larger distance from each other.
292
+ if ( ( lenA < 3 && lenB >= 3 ) || ( lenA >= 3 && lenB < 3 ) ) {
293
+ distance += 20;
294
+ }
295
+ }
296
+ return distance;
297
+ };
298
+
299
+
300
+
301
+
302
+ var dateExpandPatternGetBestMatchPattern = function( cldr, askedSkeleton ) {
303
+ var availableFormats, decimalSeparator, pattern, ratedFormats, skeleton,
304
+ path = "dates/calendars/gregorian/dateTimeFormats/availableFormats",
305
+
306
+ // Using easier to read variables.
307
+ augmentFormat = dateExpandPatternAugmentFormat,
308
+ compareFormats = dateExpandPatternCompareFormats;
309
+
310
+ pattern = cldr.main([ path, askedSkeleton ]);
311
+
312
+ if ( askedSkeleton && !pattern ) {
313
+ availableFormats = cldr.main([ path ]);
314
+ ratedFormats = [];
315
+
316
+ for ( skeleton in availableFormats ) {
317
+ ratedFormats.push({
318
+ skeleton: skeleton,
319
+ pattern: availableFormats[ skeleton ],
320
+ rate: compareFormats( askedSkeleton, skeleton )
321
+ });
322
+ }
323
+
324
+ ratedFormats = ratedFormats
325
+ .filter( function( format ) {
326
+ return format.rate > -1;
327
+ } )
328
+ .sort( function( formatA, formatB ) {
329
+ return formatA.rate - formatB.rate;
330
+ });
331
+
332
+ if ( ratedFormats.length ) {
333
+ decimalSeparator = numberSymbol( "decimal", cldr );
334
+ pattern = augmentFormat( askedSkeleton, ratedFormats[ 0 ].pattern, decimalSeparator );
335
+ }
336
+ }
337
+
338
+ return pattern;
339
+ };
340
+
341
+
342
+
343
+
344
+ /**
345
+ * expandPattern( options, cldr )
346
+ *
347
+ * @options [Object] if String, it's considered a skeleton. Object accepts:
348
+ * - skeleton: [String] lookup availableFormat;
349
+ * - date: [String] ( "full" | "long" | "medium" | "short" );
350
+ * - time: [String] ( "full" | "long" | "medium" | "short" );
351
+ * - datetime: [String] ( "full" | "long" | "medium" | "short" );
352
+ * - raw: [String] For more info see datetime/format.js.
353
+ *
354
+ * @cldr [Cldr instance].
355
+ *
356
+ * Return the corresponding pattern.
357
+ * Eg for "en":
358
+ * - "GyMMMd" returns "MMM d, y G";
359
+ * - { skeleton: "GyMMMd" } returns "MMM d, y G";
360
+ * - { date: "full" } returns "EEEE, MMMM d, y";
361
+ * - { time: "full" } returns "h:mm:ss a zzzz";
362
+ * - { datetime: "full" } returns "EEEE, MMMM d, y 'at' h:mm:ss a zzzz";
363
+ * - { raw: "dd/mm" } returns "dd/mm";
364
+ */
365
+ var dateExpandPattern = function( options, cldr ) {
366
+ var dateSkeleton, result, skeleton, timeSkeleton, type,
367
+
368
+ // Using easier to read variables.
369
+ getBestMatchPattern = dateExpandPatternGetBestMatchPattern;
370
+
371
+ function combineDateTime( type, datePattern, timePattern ) {
372
+ return formatMessage(
373
+ cldr.main([
374
+ "dates/calendars/gregorian/dateTimeFormats",
375
+ type
376
+ ]),
377
+ [ timePattern, datePattern ]
378
+ );
379
+ }
380
+
381
+ switch ( true ) {
382
+ case "skeleton" in options:
383
+ skeleton = options.skeleton;
384
+
385
+ // Preferred hour (j).
386
+ skeleton = skeleton.replace( /j/g, function() {
387
+ return cldr.supplemental.timeData.preferred();
388
+ });
389
+
390
+ validateSkeleton( skeleton );
391
+
392
+ // Try direct map (note that getBestMatchPattern handles it).
393
+ // ... or, try to "best match" the whole skeleton.
394
+ result = getBestMatchPattern(
395
+ cldr,
396
+ skeleton
397
+ );
398
+ if ( result ) {
399
+ break;
400
+ }
401
+
402
+ // ... or, try to "best match" the date and time parts individually.
403
+ timeSkeleton = skeleton.split( /[^hHKkmsSAzZOvVXx]/ ).slice( -1 )[ 0 ];
404
+ dateSkeleton = skeleton.split( /[^GyYuUrQqMLlwWdDFgEec]/ )[ 0 ];
405
+ dateSkeleton = getBestMatchPattern(
406
+ cldr,
407
+ dateSkeleton
408
+ );
409
+ timeSkeleton = getBestMatchPattern(
410
+ cldr,
411
+ timeSkeleton
412
+ );
413
+
414
+ if ( /(MMMM|LLLL).*[Ec]/.test( dateSkeleton ) ) {
415
+ type = "full";
416
+ } else if ( /MMMM|LLLL/.test( dateSkeleton ) ) {
417
+ type = "long";
418
+ } else if ( /MMM|LLL/.test( dateSkeleton ) ) {
419
+ type = "medium";
420
+ } else {
421
+ type = "short";
422
+ }
423
+
424
+ if ( dateSkeleton && timeSkeleton ) {
425
+ result = combineDateTime( type, dateSkeleton, timeSkeleton );
426
+ } else {
427
+ result = dateSkeleton || timeSkeleton;
428
+ }
429
+
430
+ break;
431
+
432
+ case "date" in options:
433
+ case "time" in options:
434
+ result = cldr.main([
435
+ "dates/calendars/gregorian",
436
+ "date" in options ? "dateFormats" : "timeFormats",
437
+ ( options.date || options.time )
438
+ ]);
439
+ break;
440
+
441
+ case "datetime" in options:
442
+ result = combineDateTime( options.datetime,
443
+ cldr.main([ "dates/calendars/gregorian/dateFormats", options.datetime ]),
444
+ cldr.main([ "dates/calendars/gregorian/timeFormats", options.datetime ])
445
+ );
446
+ break;
447
+
448
+ case "raw" in options:
449
+ result = options.raw;
450
+ break;
451
+
452
+ default:
453
+ throw createErrorInvalidParameterValue({
454
+ name: "options",
455
+ value: options
456
+ });
457
+ }
458
+
459
+ return result;
460
+ };
461
+
462
+
463
+
464
+
465
+ var dateWeekDays = [ "sun", "mon", "tue", "wed", "thu", "fri", "sat" ];
466
+
467
+
468
+
469
+
470
+ /**
471
+ * firstDayOfWeek
472
+ */
473
+ var dateFirstDayOfWeek = function( cldr ) {
474
+ return dateWeekDays.indexOf( cldr.supplemental.weekData.firstDay() );
475
+ };
476
+
477
+
478
+
479
+
480
+ /**
481
+ * getTimeZoneName( length, type )
482
+ */
483
+ var dateGetTimeZoneName = function( length, type, timeZone, cldr ) {
484
+ var metaZone, result;
485
+
486
+ if ( !timeZone ) {
487
+ return;
488
+ }
489
+
490
+ result = cldr.main([
491
+ "dates/timeZoneNames/zone",
492
+ timeZone,
493
+ length < 4 ? "short" : "long",
494
+ type
495
+ ]);
496
+
497
+ if ( result ) {
498
+ return result;
499
+ }
500
+
501
+ // The latest metazone data of the metazone array.
502
+ // TODO expand to support the historic metazones based on the given date.
503
+ metaZone = cldr.supplemental([
504
+ "metaZones/metazoneInfo/timezone", timeZone, 0,
505
+ "usesMetazone/_mzone"
506
+ ]);
507
+
508
+ return cldr.main([
509
+ "dates/timeZoneNames/metazone",
510
+ metaZone,
511
+ length < 4 ? "short" : "long",
512
+ type
513
+ ]);
514
+ };
515
+
516
+
517
+
518
+
519
+ /**
520
+ * timezoneHourFormatShortH( hourFormat )
521
+ *
522
+ * @hourFormat [String]
523
+ *
524
+ * Unofficial deduction of the short hourFormat given time zone `hourFormat` element.
525
+ * Official spec is pending resolution: http://unicode.org/cldr/trac/ticket/8293
526
+ *
527
+ * Example:
528
+ * - "+HH.mm;-HH.mm" => "+H;-H"
529
+ * - "+HH:mm;-HH:mm" => "+H;-H"
530
+ * - "+HH:mm;−HH:mm" => "+H;−H" (Note MINUS SIGN \u2212)
531
+ * - "+HHmm;-HHmm" => "+H:-H"
532
+ */
533
+ var dateTimezoneHourFormatH = function( hourFormat ) {
534
+ return hourFormat
535
+ .split( ";" )
536
+ .map(function( format ) {
537
+ return format.slice( 0, format.indexOf( "H" ) + 1 );
538
+ })
539
+ .join( ";" );
540
+ };
541
+
542
+
543
+
544
+
545
+ /**
546
+ * timezoneHourFormatLongHm( hourFormat )
547
+ *
548
+ * @hourFormat [String]
549
+ *
550
+ * Unofficial deduction of the short hourFormat given time zone `hourFormat` element.
551
+ * Official spec is pending resolution: http://unicode.org/cldr/trac/ticket/8293
552
+ *
553
+ * Example (hFormat === "H"): (used for short Hm)
554
+ * - "+HH.mm;-HH.mm" => "+H.mm;-H.mm"
555
+ * - "+HH:mm;-HH:mm" => "+H:mm;-H:mm"
556
+ * - "+HH:mm;−HH:mm" => "+H:mm;−H:mm" (Note MINUS SIGN \u2212)
557
+ * - "+HHmm;-HHmm" => "+Hmm:-Hmm"
558
+ *
559
+ * Example (hFormat === "HH": (used for long Hm)
560
+ * - "+HH.mm;-HH.mm" => "+HH.mm;-HH.mm"
561
+ * - "+HH:mm;-HH:mm" => "+HH:mm;-HH:mm"
562
+ * - "+H:mm;-H:mm" => "+HH:mm;-HH:mm"
563
+ * - "+HH:mm;−HH:mm" => "+HH:mm;−HH:mm" (Note MINUS SIGN \u2212)
564
+ * - "+HHmm;-HHmm" => "+HHmm:-HHmm"
565
+ */
566
+ var dateTimezoneHourFormatHm = function( hourFormat, hFormat ) {
567
+ return hourFormat
568
+ .split( ";" )
569
+ .map(function( format ) {
570
+ var parts = format.split( /H+/ );
571
+ parts.splice( 1, 0, hFormat );
572
+ return parts.join( "" );
573
+ })
574
+ .join( ";" );
575
+ };
576
+
577
+
578
+
579
+
580
+ var runtimeCacheDataBind = function( key, data ) {
581
+ var fn = function() {
582
+ return data;
583
+ };
584
+ fn.dataCacheKey = key;
585
+ return fn;
586
+ };
587
+
588
+
589
+
590
+
591
+ /**
592
+ * properties( pattern, cldr )
593
+ *
594
+ * @pattern [String] raw pattern.
595
+ * ref: http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
596
+ *
597
+ * @cldr [Cldr instance].
598
+ *
599
+ * Return the properties given the pattern and cldr.
600
+ *
601
+ * TODO Support other calendar types.
602
+ */
603
+ var dateFormatProperties = function( pattern, cldr, timeZone ) {
604
+ var properties = {
605
+ numberFormatters: {},
606
+ pattern: pattern,
607
+ timeSeparator: numberSymbol( "timeSeparator", cldr )
608
+ },
609
+ widths = [ "abbreviated", "wide", "narrow" ];
610
+
611
+ function setNumberFormatterPattern( pad ) {
612
+ properties.numberFormatters[ pad ] = stringPad( "", pad );
613
+ }
614
+
615
+ if ( timeZone ) {
616
+ properties.timeZoneData = runtimeCacheDataBind( "iana/" + timeZone, {
617
+ offsets: cldr.get([ "globalize-iana/zoneData", timeZone, "offsets" ]),
618
+ untils: cldr.get([ "globalize-iana/zoneData", timeZone, "untils" ]),
619
+ isdsts: cldr.get([ "globalize-iana/zoneData", timeZone, "isdsts" ])
620
+ });
621
+ }
622
+
623
+ pattern.replace( datePatternRe, function( current ) {
624
+ var aux, chr, daylightTzName, formatNumber, genericTzName, length, standardTzName;
625
+
626
+ chr = current.charAt( 0 );
627
+ length = current.length;
628
+
629
+ if ( chr === "j" ) {
630
+
631
+ // Locale preferred hHKk.
632
+ // http://www.unicode.org/reports/tr35/tr35-dates.html#Time_Data
633
+ properties.preferredTime = chr = cldr.supplemental.timeData.preferred();
634
+ }
635
+
636
+ // ZZZZ: same as "OOOO".
637
+ if ( chr === "Z" && length === 4 ) {
638
+ chr = "O";
639
+ length = 4;
640
+ }
641
+
642
+ // z...zzz: "{shortRegion}", eg. "PST" or "PDT".
643
+ // zzzz: "{regionName} {Standard Time}" or "{regionName} {Daylight Time}",
644
+ // e.g., "Pacific Standard Time" or "Pacific Daylight Time".
645
+ // http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
646
+ if ( chr === "z" ) {
647
+ standardTzName = dateGetTimeZoneName( length, "standard", timeZone, cldr );
648
+ daylightTzName = dateGetTimeZoneName( length, "daylight", timeZone, cldr );
649
+ if ( standardTzName ) {
650
+ properties.standardTzName = standardTzName;
651
+ }
652
+ if ( daylightTzName ) {
653
+ properties.daylightTzName = daylightTzName;
654
+ }
655
+
656
+ // Fall through the "O" format in case one name is missing.
657
+ if ( !standardTzName || !daylightTzName ) {
658
+ chr = "O";
659
+ if ( length < 4 ) {
660
+ length = 1;
661
+ }
662
+ }
663
+ }
664
+
665
+ // v...vvv: "{shortRegion}", eg. "PT".
666
+ // vvvv: "{regionName} {Time}" or "{regionName} {Time}",
667
+ // e.g., "Pacific Time"
668
+ // http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
669
+ if ( chr === "v" ) {
670
+ genericTzName = dateGetTimeZoneName( length, "generic", timeZone, cldr );
671
+
672
+ // Fall back to "V" format.
673
+ if ( !genericTzName ) {
674
+ chr = "V";
675
+ length = 4;
676
+ }
677
+ }
678
+
679
+ switch ( chr ) {
680
+
681
+ // Era
682
+ case "G":
683
+ properties.eras = cldr.main([
684
+ "dates/calendars/gregorian/eras",
685
+ length <= 3 ? "eraAbbr" : ( length === 4 ? "eraNames" : "eraNarrow" )
686
+ ]);
687
+ break;
688
+
689
+ // Year
690
+ case "y":
691
+
692
+ // Plain year.
693
+ formatNumber = true;
694
+ break;
695
+
696
+ case "Y":
697
+
698
+ // Year in "Week of Year"
699
+ properties.firstDay = dateFirstDayOfWeek( cldr );
700
+ properties.minDays = cldr.supplemental.weekData.minDays();
701
+ formatNumber = true;
702
+ break;
703
+
704
+ case "u": // Extended year. Need to be implemented.
705
+ case "U": // Cyclic year name. Need to be implemented.
706
+ throw createErrorUnsupportedFeature({
707
+ feature: "year pattern `" + chr + "`"
708
+ });
709
+
710
+ // Quarter
711
+ case "Q":
712
+ case "q":
713
+ if ( length > 2 ) {
714
+ if ( !properties.quarters ) {
715
+ properties.quarters = {};
716
+ }
717
+ if ( !properties.quarters[ chr ] ) {
718
+ properties.quarters[ chr ] = {};
719
+ }
720
+ properties.quarters[ chr ][ length ] = cldr.main([
721
+ "dates/calendars/gregorian/quarters",
722
+ chr === "Q" ? "format" : "stand-alone",
723
+ widths[ length - 3 ]
724
+ ]);
725
+ } else {
726
+ formatNumber = true;
727
+ }
728
+ break;
729
+
730
+ // Month
731
+ case "M":
732
+ case "L":
733
+ if ( length > 2 ) {
734
+ if ( !properties.months ) {
735
+ properties.months = {};
736
+ }
737
+ if ( !properties.months[ chr ] ) {
738
+ properties.months[ chr ] = {};
739
+ }
740
+ properties.months[ chr ][ length ] = cldr.main([
741
+ "dates/calendars/gregorian/months",
742
+ chr === "M" ? "format" : "stand-alone",
743
+ widths[ length - 3 ]
744
+ ]);
745
+ } else {
746
+ formatNumber = true;
747
+ }
748
+ break;
749
+
750
+ // Week - Week of Year (w) or Week of Month (W).
751
+ case "w":
752
+ case "W":
753
+ properties.firstDay = dateFirstDayOfWeek( cldr );
754
+ properties.minDays = cldr.supplemental.weekData.minDays();
755
+ formatNumber = true;
756
+ break;
757
+
758
+ // Day
759
+ case "d":
760
+ case "D":
761
+ case "F":
762
+ formatNumber = true;
763
+ break;
764
+
765
+ case "g":
766
+
767
+ // Modified Julian day. Need to be implemented.
768
+ throw createErrorUnsupportedFeature({
769
+ feature: "Julian day pattern `g`"
770
+ });
771
+
772
+ // Week day
773
+ case "e":
774
+ case "c":
775
+ if ( length <= 2 ) {
776
+ properties.firstDay = dateFirstDayOfWeek( cldr );
777
+ formatNumber = true;
778
+ break;
779
+ }
780
+
781
+ /* falls through */
782
+ case "E":
783
+ if ( !properties.days ) {
784
+ properties.days = {};
785
+ }
786
+ if ( !properties.days[ chr ] ) {
787
+ properties.days[ chr ] = {};
788
+ }
789
+ if ( length === 6 ) {
790
+
791
+ // If short day names are not explicitly specified, abbreviated day names are
792
+ // used instead.
793
+ // http://www.unicode.org/reports/tr35/tr35-dates.html#months_days_quarters_eras
794
+ // http://unicode.org/cldr/trac/ticket/6790
795
+ properties.days[ chr ][ length ] = cldr.main([
796
+ "dates/calendars/gregorian/days",
797
+ chr === "c" ? "stand-alone" : "format",
798
+ "short"
799
+ ]) || cldr.main([
800
+ "dates/calendars/gregorian/days",
801
+ chr === "c" ? "stand-alone" : "format",
802
+ "abbreviated"
803
+ ]);
804
+ } else {
805
+ properties.days[ chr ][ length ] = cldr.main([
806
+ "dates/calendars/gregorian/days",
807
+ chr === "c" ? "stand-alone" : "format",
808
+ widths[ length < 3 ? 0 : length - 3 ]
809
+ ]);
810
+ }
811
+ break;
812
+
813
+ // Period (AM or PM)
814
+ case "a":
815
+ properties.dayPeriods = {
816
+ am: cldr.main(
817
+ "dates/calendars/gregorian/dayPeriods/format/wide/am"
818
+ ),
819
+ pm: cldr.main(
820
+ "dates/calendars/gregorian/dayPeriods/format/wide/pm"
821
+ )
822
+ };
823
+ break;
824
+
825
+ // Hour
826
+ case "h": // 1-12
827
+ case "H": // 0-23
828
+ case "K": // 0-11
829
+ case "k": // 1-24
830
+
831
+ // Minute
832
+ case "m":
833
+
834
+ // Second
835
+ case "s":
836
+ case "S":
837
+ case "A":
838
+ formatNumber = true;
839
+ break;
840
+
841
+ // Zone
842
+ case "v":
843
+ if ( length !== 1 && length !== 4 ) {
844
+ throw createErrorUnsupportedFeature({
845
+ feature: "timezone pattern `" + pattern + "`"
846
+ });
847
+ }
848
+ properties.genericTzName = genericTzName;
849
+ break;
850
+
851
+ case "V":
852
+
853
+ if ( length === 1 ) {
854
+ throw createErrorUnsupportedFeature({
855
+ feature: "timezone pattern `" + pattern + "`"
856
+ });
857
+ }
858
+
859
+ if ( timeZone ) {
860
+ if ( length === 2 ) {
861
+ properties.timeZoneName = timeZone;
862
+ break;
863
+ }
864
+
865
+ var timeZoneName,
866
+ exemplarCity = cldr.main([
867
+ "dates/timeZoneNames/zone", timeZone, "exemplarCity"
868
+ ]);
869
+
870
+ if ( length === 3 ) {
871
+ if ( !exemplarCity ) {
872
+ exemplarCity = cldr.main([
873
+ "dates/timeZoneNames/zone/Etc/Unknown/exemplarCity"
874
+ ]);
875
+ }
876
+ timeZoneName = exemplarCity;
877
+ }
878
+
879
+ if ( exemplarCity && length === 4 ) {
880
+ timeZoneName = formatMessage(
881
+ cldr.main(
882
+ "dates/timeZoneNames/regionFormat"
883
+ ),
884
+ [ exemplarCity ]
885
+ );
886
+ }
887
+
888
+ if ( timeZoneName ) {
889
+ properties.timeZoneName = timeZoneName;
890
+ break;
891
+ }
892
+ }
893
+
894
+ if ( current === "v" ) {
895
+ length = 1;
896
+ }
897
+
898
+ /* falls through */
899
+ case "O":
900
+
901
+ // O: "{gmtFormat}+H;{gmtFormat}-H" or "{gmtZeroFormat}", eg. "GMT-8" or "GMT".
902
+ // OOOO: "{gmtFormat}{hourFormat}" or "{gmtZeroFormat}", eg. "GMT-08:00" or "GMT".
903
+ properties.gmtFormat = cldr.main( "dates/timeZoneNames/gmtFormat" );
904
+ properties.gmtZeroFormat = cldr.main( "dates/timeZoneNames/gmtZeroFormat" );
905
+
906
+ // Unofficial deduction of the hourFormat variations.
907
+ // Official spec is pending resolution: http://unicode.org/cldr/trac/ticket/8293
908
+ aux = cldr.main( "dates/timeZoneNames/hourFormat" );
909
+ properties.hourFormat = length < 4 ?
910
+ [ dateTimezoneHourFormatH( aux ), dateTimezoneHourFormatHm( aux, "H" ) ] :
911
+ dateTimezoneHourFormatHm( aux, "HH" );
912
+
913
+ /* falls through */
914
+ case "Z":
915
+ case "X":
916
+ case "x":
917
+ setNumberFormatterPattern( 1 );
918
+ setNumberFormatterPattern( 2 );
919
+ break;
920
+ }
921
+
922
+ if ( formatNumber ) {
923
+ setNumberFormatterPattern( length );
924
+ }
925
+ });
926
+
927
+ return properties;
928
+ };
929
+
930
+
931
+
932
+
933
+ var dateFormatterFn = function( dateToPartsFormatter ) {
934
+ return function dateFormatter( value ) {
935
+ return partsJoin( dateToPartsFormatter( value ));
936
+ };
937
+ };
938
+
939
+
940
+
941
+
942
+ /**
943
+ * parseProperties( cldr )
944
+ *
945
+ * @cldr [Cldr instance].
946
+ *
947
+ * @timeZone [String] FIXME.
948
+ *
949
+ * Return parser properties.
950
+ */
951
+ var dateParseProperties = function( cldr, timeZone ) {
952
+ var properties = {
953
+ preferredTimeData: cldr.supplemental.timeData.preferred()
954
+ };
955
+
956
+ if ( timeZone ) {
957
+ properties.timeZoneData = runtimeCacheDataBind( "iana/" + timeZone, {
958
+ offsets: cldr.get([ "globalize-iana/zoneData", timeZone, "offsets" ]),
959
+ untils: cldr.get([ "globalize-iana/zoneData", timeZone, "untils" ]),
960
+ isdsts: cldr.get([ "globalize-iana/zoneData", timeZone, "isdsts" ])
961
+ });
962
+ }
963
+
964
+ return properties;
965
+ };
966
+
967
+
968
+ var ZonedDateTime = (function() {
969
+ function definePrivateProperty(object, property, value) {
970
+ Object.defineProperty(object, property, {
971
+ value: value
972
+ });
973
+ }
974
+
975
+ function getUntilsIndex(original, untils) {
976
+ var index = 0;
977
+ var originalTime = original.getTime();
978
+
979
+ // TODO Should we do binary search for improved performance?
980
+ while (index < untils.length - 1 && originalTime >= untils[index]) {
981
+ index++;
982
+ }
983
+ return index;
984
+ }
985
+
986
+ function setWrap(fn) {
987
+ var offset1 = this.getTimezoneOffset();
988
+ var ret = fn();
989
+ this.original.setTime(new Date(this.getTime()));
990
+ var offset2 = this.getTimezoneOffset();
991
+ if (offset2 - offset1) {
992
+ this.original.setMinutes(this.original.getMinutes() + offset2 - offset1);
993
+ }
994
+ return ret;
995
+ }
996
+
997
+ var ZonedDateTime = function(date, timeZoneData) {
998
+ definePrivateProperty(this, "original", new Date(date.getTime()));
999
+ definePrivateProperty(this, "local", new Date(date.getTime()));
1000
+ definePrivateProperty(this, "timeZoneData", timeZoneData);
1001
+ definePrivateProperty(this, "setWrap", setWrap);
1002
+ if (!(timeZoneData.untils && timeZoneData.offsets && timeZoneData.isdsts)) {
1003
+ throw new Error("Invalid IANA data");
1004
+ }
1005
+ this.setTime(this.local.getTime() - this.getTimezoneOffset() * 60 * 1000);
1006
+ };
1007
+
1008
+ ZonedDateTime.prototype.clone = function() {
1009
+ return new ZonedDateTime(this.original, this.timeZoneData);
1010
+ };
1011
+
1012
+ // Date field getters.
1013
+ ["getFullYear", "getMonth", "getDate", "getDay", "getHours", "getMinutes",
1014
+ "getSeconds", "getMilliseconds"].forEach(function(method) {
1015
+ // Corresponding UTC method, e.g., "getUTCFullYear" if method === "getFullYear".
1016
+ var utcMethod = "getUTC" + method.substr(3);
1017
+ ZonedDateTime.prototype[method] = function() {
1018
+ return this.local[utcMethod]();
1019
+ };
1020
+ });
1021
+
1022
+ // Note: Define .valueOf = .getTime for arithmetic operations like date1 - date2.
1023
+ ZonedDateTime.prototype.valueOf =
1024
+ ZonedDateTime.prototype.getTime = function() {
1025
+ return this.local.getTime() + this.getTimezoneOffset() * 60 * 1000;
1026
+ };
1027
+
1028
+ ZonedDateTime.prototype.getTimezoneOffset = function() {
1029
+ var index = getUntilsIndex(this.original, this.timeZoneData.untils);
1030
+ return this.timeZoneData.offsets[index];
1031
+ };
1032
+
1033
+ // Date field setters.
1034
+ ["setFullYear", "setMonth", "setDate", "setHours", "setMinutes", "setSeconds", "setMilliseconds"].forEach(function(method) {
1035
+ // Corresponding UTC method, e.g., "setUTCFullYear" if method === "setFullYear".
1036
+ var utcMethod = "setUTC" + method.substr(3);
1037
+ ZonedDateTime.prototype[method] = function(value) {
1038
+ var local = this.local;
1039
+ // Note setWrap is needed for seconds and milliseconds just because
1040
+ // abs(value) could be >= a minute.
1041
+ return this.setWrap(function() {
1042
+ return local[utcMethod](value);
1043
+ });
1044
+ };
1045
+ });
1046
+
1047
+ ZonedDateTime.prototype.setTime = function(time) {
1048
+ return this.local.setTime(time);
1049
+ };
1050
+
1051
+ ZonedDateTime.prototype.isDST = function() {
1052
+ var index = getUntilsIndex(this.original, this.timeZoneData.untils);
1053
+ return Boolean(this.timeZoneData.isdsts[index]);
1054
+ };
1055
+
1056
+ ZonedDateTime.prototype.inspect = function() {
1057
+ var index = getUntilsIndex(this.original, this.timeZoneData.untils);
1058
+ var abbrs = this.timeZoneData.abbrs;
1059
+ return this.local.toISOString().replace(/Z$/, "") + " " +
1060
+ (abbrs && abbrs[index] + " " || (this.getTimezoneOffset() * -1) + " ") +
1061
+ (this.isDST() ? "(daylight savings)" : "");
1062
+ };
1063
+
1064
+ ZonedDateTime.prototype.toDate = function() {
1065
+ return new Date(this.getTime());
1066
+ };
1067
+
1068
+ // Type cast getters.
1069
+ ["toISOString", "toJSON", "toUTCString"].forEach(function(method) {
1070
+ ZonedDateTime.prototype[method] = function() {
1071
+ return this.toDate()[method]();
1072
+ };
1073
+ });
1074
+
1075
+ return ZonedDateTime;
1076
+ }());
1077
+
1078
+
1079
+ /**
1080
+ * isLeapYear( year )
1081
+ *
1082
+ * @year [Number]
1083
+ *
1084
+ * Returns an indication whether the specified year is a leap year.
1085
+ */
1086
+ var dateIsLeapYear = function( year ) {
1087
+ return new Date( year, 1, 29 ).getMonth() === 1;
1088
+ };
1089
+
1090
+
1091
+
1092
+
1093
+ /**
1094
+ * lastDayOfMonth( date )
1095
+ *
1096
+ * @date [Date]
1097
+ *
1098
+ * Return the last day of the given date's month
1099
+ */
1100
+ var dateLastDayOfMonth = function( date ) {
1101
+ return new Date( date.getFullYear(), date.getMonth() + 1, 0 ).getDate();
1102
+ };
1103
+
1104
+
1105
+
1106
+
1107
+ /**
1108
+ * startOf changes the input to the beginning of the given unit.
1109
+ *
1110
+ * For example, starting at the start of a day, resets hours, minutes
1111
+ * seconds and milliseconds to 0. Starting at the month does the same, but
1112
+ * also sets the date to 1.
1113
+ *
1114
+ * Returns the modified date
1115
+ */
1116
+ var dateStartOf = function( date, unit ) {
1117
+ date = date instanceof ZonedDateTime ? date.clone() : new Date( date.getTime() );
1118
+ switch ( unit ) {
1119
+ case "year":
1120
+ date.setMonth( 0 );
1121
+ /* falls through */
1122
+ case "month":
1123
+ date.setDate( 1 );
1124
+ /* falls through */
1125
+ case "day":
1126
+ date.setHours( 0 );
1127
+ /* falls through */
1128
+ case "hour":
1129
+ date.setMinutes( 0 );
1130
+ /* falls through */
1131
+ case "minute":
1132
+ date.setSeconds( 0 );
1133
+ /* falls through */
1134
+ case "second":
1135
+ date.setMilliseconds( 0 );
1136
+ }
1137
+ return date;
1138
+ };
1139
+
1140
+
1141
+
1142
+
1143
+ /**
1144
+ * Differently from native date.setDate(), this function returns a date whose
1145
+ * day remains inside the month boundaries. For example:
1146
+ *
1147
+ * setDate( FebDate, 31 ): a "Feb 28" date.
1148
+ * setDate( SepDate, 31 ): a "Sep 30" date.
1149
+ */
1150
+ var dateSetDate = function( date, day ) {
1151
+ var lastDay = new Date( date.getFullYear(), date.getMonth() + 1, 0 ).getDate();
1152
+
1153
+ date.setDate( day < 1 ? 1 : day < lastDay ? day : lastDay );
1154
+ };
1155
+
1156
+
1157
+
1158
+
1159
+ /**
1160
+ * Differently from native date.setMonth(), this function adjusts date if
1161
+ * needed, so final month is always the one set.
1162
+ *
1163
+ * setMonth( Jan31Date, 1 ): a "Feb 28" date.
1164
+ * setDate( Jan31Date, 8 ): a "Sep 30" date.
1165
+ */
1166
+ var dateSetMonth = function( date, month ) {
1167
+ var originalDate = date.getDate();
1168
+
1169
+ date.setDate( 1 );
1170
+ date.setMonth( month );
1171
+ dateSetDate( date, originalDate );
1172
+ };
1173
+
1174
+
1175
+
1176
+
1177
+ var outOfRange = function( value, low, high ) {
1178
+ return value < low || value > high;
1179
+ };
1180
+
1181
+
1182
+
1183
+
1184
+ /**
1185
+ * parse( value, tokens, properties )
1186
+ *
1187
+ * @value [String] string date.
1188
+ *
1189
+ * @tokens [Object] tokens returned by date/tokenizer.
1190
+ *
1191
+ * @properties [Object] output returned by date/tokenizer-properties.
1192
+ *
1193
+ * ref: http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
1194
+ */
1195
+ var dateParse = function( _value, tokens, properties ) {
1196
+ var amPm, day, daysOfYear, month, era, hour, hour12, timezoneOffset, valid,
1197
+ YEAR = 0,
1198
+ MONTH = 1,
1199
+ DAY = 2,
1200
+ HOUR = 3,
1201
+ MINUTE = 4,
1202
+ SECOND = 5,
1203
+ MILLISECONDS = 6,
1204
+ date = new Date(),
1205
+ truncateAt = [],
1206
+ units = [ "year", "month", "day", "hour", "minute", "second", "milliseconds" ];
1207
+
1208
+ // Create globalize date with given timezone data.
1209
+ if ( properties.timeZoneData ) {
1210
+ date = new ZonedDateTime( date, properties.timeZoneData() );
1211
+ }
1212
+
1213
+ if ( !tokens.length ) {
1214
+ return null;
1215
+ }
1216
+
1217
+ valid = tokens.every(function( token ) {
1218
+ var century, chr, value, length;
1219
+
1220
+ if ( token.type === "literal" ) {
1221
+
1222
+ // continue
1223
+ return true;
1224
+ }
1225
+
1226
+ chr = token.type.charAt( 0 );
1227
+ length = token.type.length;
1228
+
1229
+ if ( chr === "j" ) {
1230
+
1231
+ // Locale preferred hHKk.
1232
+ // http://www.unicode.org/reports/tr35/tr35-dates.html#Time_Data
1233
+ chr = properties.preferredTimeData;
1234
+ }
1235
+
1236
+ switch ( chr ) {
1237
+
1238
+ // Era
1239
+ case "G":
1240
+ truncateAt.push( YEAR );
1241
+ era = +token.value;
1242
+ break;
1243
+
1244
+ // Year
1245
+ case "y":
1246
+ value = token.value;
1247
+ if ( length === 2 ) {
1248
+ if ( outOfRange( value, 0, 99 ) ) {
1249
+ return false;
1250
+ }
1251
+
1252
+ // mimic dojo/date/locale: choose century to apply, according to a sliding
1253
+ // window of 80 years before and 20 years after present year.
1254
+ century = Math.floor( date.getFullYear() / 100 ) * 100;
1255
+ value += century;
1256
+ if ( value > date.getFullYear() + 20 ) {
1257
+ value -= 100;
1258
+ }
1259
+ }
1260
+ date.setFullYear( value );
1261
+ truncateAt.push( YEAR );
1262
+ break;
1263
+
1264
+ case "Y": // Year in "Week of Year"
1265
+ throw createErrorUnsupportedFeature({
1266
+ feature: "year pattern `" + chr + "`"
1267
+ });
1268
+
1269
+ // Quarter (skip)
1270
+ case "Q":
1271
+ case "q":
1272
+ break;
1273
+
1274
+ // Month
1275
+ case "M":
1276
+ case "L":
1277
+ if ( length <= 2 ) {
1278
+ value = token.value;
1279
+ } else {
1280
+ value = +token.value;
1281
+ }
1282
+ if ( outOfRange( value, 1, 12 ) ) {
1283
+ return false;
1284
+ }
1285
+
1286
+ // Setting the month later so that we have the correct year and can determine
1287
+ // the correct last day of February in case of leap year.
1288
+ month = value;
1289
+ truncateAt.push( MONTH );
1290
+ break;
1291
+
1292
+ // Week (skip)
1293
+ case "w": // Week of Year.
1294
+ case "W": // Week of Month.
1295
+ break;
1296
+
1297
+ // Day
1298
+ case "d":
1299
+ day = token.value;
1300
+ truncateAt.push( DAY );
1301
+ break;
1302
+
1303
+ case "D":
1304
+ daysOfYear = token.value;
1305
+ truncateAt.push( DAY );
1306
+ break;
1307
+
1308
+ case "F":
1309
+
1310
+ // Day of Week in month. eg. 2nd Wed in July.
1311
+ // Skip
1312
+ break;
1313
+
1314
+ // Week day
1315
+ case "e":
1316
+ case "c":
1317
+ case "E":
1318
+
1319
+ // Skip.
1320
+ // value = arrayIndexOf( dateWeekDays, token.value );
1321
+ break;
1322
+
1323
+ // Period (AM or PM)
1324
+ case "a":
1325
+ amPm = token.value;
1326
+ break;
1327
+
1328
+ // Hour
1329
+ case "h": // 1-12
1330
+ value = token.value;
1331
+ if ( outOfRange( value, 1, 12 ) ) {
1332
+ return false;
1333
+ }
1334
+ hour = hour12 = true;
1335
+ date.setHours( value === 12 ? 0 : value );
1336
+ truncateAt.push( HOUR );
1337
+ break;
1338
+
1339
+ case "K": // 0-11
1340
+ value = token.value;
1341
+ if ( outOfRange( value, 0, 11 ) ) {
1342
+ return false;
1343
+ }
1344
+ hour = hour12 = true;
1345
+ date.setHours( value );
1346
+ truncateAt.push( HOUR );
1347
+ break;
1348
+
1349
+ case "k": // 1-24
1350
+ value = token.value;
1351
+ if ( outOfRange( value, 1, 24 ) ) {
1352
+ return false;
1353
+ }
1354
+ hour = true;
1355
+ date.setHours( value === 24 ? 0 : value );
1356
+ truncateAt.push( HOUR );
1357
+ break;
1358
+
1359
+ case "H": // 0-23
1360
+ value = token.value;
1361
+ if ( outOfRange( value, 0, 23 ) ) {
1362
+ return false;
1363
+ }
1364
+ hour = true;
1365
+ date.setHours( value );
1366
+ truncateAt.push( HOUR );
1367
+ break;
1368
+
1369
+ // Minute
1370
+ case "m":
1371
+ value = token.value;
1372
+ if ( outOfRange( value, 0, 59 ) ) {
1373
+ return false;
1374
+ }
1375
+ date.setMinutes( value );
1376
+ truncateAt.push( MINUTE );
1377
+ break;
1378
+
1379
+ // Second
1380
+ case "s":
1381
+ value = token.value;
1382
+ if ( outOfRange( value, 0, 59 ) ) {
1383
+ return false;
1384
+ }
1385
+ date.setSeconds( value );
1386
+ truncateAt.push( SECOND );
1387
+ break;
1388
+
1389
+ case "A":
1390
+ date.setHours( 0 );
1391
+ date.setMinutes( 0 );
1392
+ date.setSeconds( 0 );
1393
+
1394
+ /* falls through */
1395
+ case "S":
1396
+ value = Math.round( token.value * Math.pow( 10, 3 - length ) );
1397
+ date.setMilliseconds( value );
1398
+ truncateAt.push( MILLISECONDS );
1399
+ break;
1400
+
1401
+ // Zone
1402
+ case "z":
1403
+ case "Z":
1404
+ case "O":
1405
+ case "v":
1406
+ case "V":
1407
+ case "X":
1408
+ case "x":
1409
+ if ( typeof token.value === "number" ) {
1410
+ timezoneOffset = token.value;
1411
+ }
1412
+ break;
1413
+ }
1414
+
1415
+ return true;
1416
+ });
1417
+
1418
+ if ( !valid ) {
1419
+ return null;
1420
+ }
1421
+
1422
+ // 12-hour format needs AM or PM, 24-hour format doesn't, ie. return null
1423
+ // if amPm && !hour12 || !amPm && hour12.
1424
+ if ( hour && !( !amPm ^ hour12 ) ) {
1425
+ return null;
1426
+ }
1427
+
1428
+ if ( era === 0 ) {
1429
+
1430
+ // 1 BC = year 0
1431
+ date.setFullYear( date.getFullYear() * -1 + 1 );
1432
+ }
1433
+
1434
+ if ( month !== undefined ) {
1435
+ dateSetMonth( date, month - 1 );
1436
+ }
1437
+
1438
+ if ( day !== undefined ) {
1439
+ if ( outOfRange( day, 1, dateLastDayOfMonth( date ) ) ) {
1440
+ return null;
1441
+ }
1442
+ date.setDate( day );
1443
+ } else if ( daysOfYear !== undefined ) {
1444
+ if ( outOfRange( daysOfYear, 1, dateIsLeapYear( date.getFullYear() ) ? 366 : 365 ) ) {
1445
+ return null;
1446
+ }
1447
+ date.setMonth( 0 );
1448
+ date.setDate( daysOfYear );
1449
+ }
1450
+
1451
+ if ( hour12 && amPm === "pm" ) {
1452
+ date.setHours( date.getHours() + 12 );
1453
+ }
1454
+
1455
+ if ( timezoneOffset !== undefined ) {
1456
+ date.setMinutes( date.getMinutes() + timezoneOffset - date.getTimezoneOffset() );
1457
+ }
1458
+
1459
+ // Truncate date at the most precise unit defined. Eg.
1460
+ // If value is "12/31", and pattern is "MM/dd":
1461
+ // => new Date( <current Year>, 12, 31, 0, 0, 0, 0 );
1462
+ truncateAt = Math.max.apply( null, truncateAt );
1463
+ date = dateStartOf( date, units[ truncateAt ] );
1464
+
1465
+ // Get date back from globalize date.
1466
+ if ( date instanceof ZonedDateTime ) {
1467
+ date = date.toDate();
1468
+ }
1469
+
1470
+ return date;
1471
+ };
1472
+
1473
+
1474
+ /* eslint-disable no-unused-expressions */
1475
+
1476
+
1477
+
1478
+ /**
1479
+ * tokenizer( value, numberParser, properties )
1480
+ *
1481
+ * @value [String] string date.
1482
+ *
1483
+ * @numberParser [Function]
1484
+ *
1485
+ * @properties [Object] output returned by date/tokenizer-properties.
1486
+ *
1487
+ * Returns an Array of tokens, eg. value "5 o'clock PM", pattern "h 'o''clock' a":
1488
+ * [{
1489
+ * type: "h",
1490
+ * lexeme: "5"
1491
+ * }, {
1492
+ * type: "literal",
1493
+ * lexeme: " "
1494
+ * }, {
1495
+ * type: "literal",
1496
+ * lexeme: "o'clock"
1497
+ * }, {
1498
+ * type: "literal",
1499
+ * lexeme: " "
1500
+ * }, {
1501
+ * type: "a",
1502
+ * lexeme: "PM",
1503
+ * value: "pm"
1504
+ * }]
1505
+ *
1506
+ * OBS: lexeme's are always String and may return invalid ranges depending of the token type.
1507
+ * Eg. "99" for month number.
1508
+ *
1509
+ * Return an empty Array when not successfully parsed.
1510
+ */
1511
+ var dateTokenizer = function( value, numberParser, properties ) {
1512
+ var digitsRe, valid,
1513
+ tokens = [],
1514
+ widths = [ "abbreviated", "wide", "narrow" ];
1515
+
1516
+ digitsRe = properties.digitsRe;
1517
+ value = looseMatching( value );
1518
+
1519
+ valid = properties.pattern.match( datePatternRe ).every(function( current ) {
1520
+ var aux, chr, length, numeric, tokenRe,
1521
+ token = {};
1522
+
1523
+ function hourFormatParse( tokenRe, numberParser ) {
1524
+ var aux, isPositive,
1525
+ match = value.match( tokenRe );
1526
+ numberParser = numberParser || function( value ) {
1527
+ return +value;
1528
+ };
1529
+
1530
+ if ( !match ) {
1531
+ return false;
1532
+ }
1533
+
1534
+ isPositive = match[ 1 ];
1535
+
1536
+ // hourFormat containing H only, e.g., `+H;-H`
1537
+ if ( match.length < 6 ) {
1538
+ aux = isPositive ? 1 : 3;
1539
+ token.value = numberParser( match[ aux ] ) * 60;
1540
+
1541
+ // hourFormat containing H and m, e.g., `+HHmm;-HHmm`
1542
+ } else if ( match.length < 10 ) {
1543
+ aux = isPositive ? [ 1, 3 ] : [ 5, 7 ];
1544
+ token.value = numberParser( match[ aux[ 0 ] ] ) * 60 +
1545
+ numberParser( match[ aux[ 1 ] ] );
1546
+
1547
+ // hourFormat containing H, m, and s e.g., `+HHmmss;-HHmmss`
1548
+ } else {
1549
+ aux = isPositive ? [ 1, 3, 5 ] : [ 7, 9, 11 ];
1550
+ token.value = numberParser( match[ aux[ 0 ] ] ) * 60 +
1551
+ numberParser( match[ aux[ 1 ] ] ) +
1552
+ numberParser( match[ aux[ 2 ] ] ) / 60;
1553
+ }
1554
+
1555
+ if ( isPositive ) {
1556
+ token.value *= -1;
1557
+ }
1558
+
1559
+ return true;
1560
+ }
1561
+
1562
+ function oneDigitIfLengthOne() {
1563
+ if ( length === 1 ) {
1564
+
1565
+ // Unicode equivalent to /\d/
1566
+ numeric = true;
1567
+ return tokenRe = digitsRe;
1568
+ }
1569
+ }
1570
+
1571
+ function oneOrTwoDigitsIfLengthOne() {
1572
+ if ( length === 1 ) {
1573
+
1574
+ // Unicode equivalent to /\d\d?/
1575
+ numeric = true;
1576
+ return tokenRe = new RegExp( "^(" + digitsRe.source + "){1,2}" );
1577
+ }
1578
+ }
1579
+
1580
+ function oneOrTwoDigitsIfLengthOneOrTwo() {
1581
+ if ( length === 1 || length === 2 ) {
1582
+
1583
+ // Unicode equivalent to /\d\d?/
1584
+ numeric = true;
1585
+ return tokenRe = new RegExp( "^(" + digitsRe.source + "){1,2}" );
1586
+ }
1587
+ }
1588
+
1589
+ function twoDigitsIfLengthTwo() {
1590
+ if ( length === 2 ) {
1591
+
1592
+ // Unicode equivalent to /\d\d/
1593
+ numeric = true;
1594
+ return tokenRe = new RegExp( "^(" + digitsRe.source + "){2}" );
1595
+ }
1596
+ }
1597
+
1598
+ // Brute-force test every locale entry in an attempt to match the given value.
1599
+ // Return the first found one (and set token accordingly), or null.
1600
+ function lookup( path ) {
1601
+ var array = properties[ path.join( "/" ) ];
1602
+
1603
+ if ( !array ) {
1604
+ return null;
1605
+ }
1606
+
1607
+ // array of pairs [key, value] sorted by desc value length.
1608
+ array.some(function( item ) {
1609
+ var valueRe = item[ 1 ];
1610
+ if ( valueRe.test( value ) ) {
1611
+ token.value = item[ 0 ];
1612
+ tokenRe = item[ 1 ];
1613
+ return true;
1614
+ }
1615
+ });
1616
+ return null;
1617
+ }
1618
+
1619
+ token.type = current;
1620
+ chr = current.charAt( 0 );
1621
+ length = current.length;
1622
+
1623
+ if ( chr === "Z" ) {
1624
+
1625
+ // Z..ZZZ: same as "xxxx".
1626
+ if ( length < 4 ) {
1627
+ chr = "x";
1628
+ length = 4;
1629
+
1630
+ // ZZZZ: same as "OOOO".
1631
+ } else if ( length < 5 ) {
1632
+ chr = "O";
1633
+ length = 4;
1634
+
1635
+ // ZZZZZ: same as "XXXXX"
1636
+ } else {
1637
+ chr = "X";
1638
+ length = 5;
1639
+ }
1640
+ }
1641
+
1642
+ if ( chr === "z" ) {
1643
+ if ( properties.standardOrDaylightTzName ) {
1644
+ token.value = null;
1645
+ tokenRe = properties.standardOrDaylightTzName;
1646
+ }
1647
+ }
1648
+
1649
+ // v...vvv: "{shortRegion}", eg. "PT".
1650
+ // vvvv: "{regionName} {Time}" or "{regionName} {Time}",
1651
+ // e.g., "Pacific Time"
1652
+ // http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
1653
+ if ( chr === "v" ) {
1654
+ if ( properties.genericTzName ) {
1655
+ token.value = null;
1656
+ tokenRe = properties.genericTzName;
1657
+
1658
+ // Fall back to "V" format.
1659
+ } else {
1660
+ chr = "V";
1661
+ length = 4;
1662
+ }
1663
+ }
1664
+
1665
+ if ( chr === "V" && properties.timeZoneName ) {
1666
+ token.value = length === 2 ? properties.timeZoneName : null;
1667
+ tokenRe = properties.timeZoneNameRe;
1668
+ }
1669
+
1670
+ switch ( chr ) {
1671
+
1672
+ // Era
1673
+ case "G":
1674
+ lookup([
1675
+ "gregorian/eras",
1676
+ length <= 3 ? "eraAbbr" : ( length === 4 ? "eraNames" : "eraNarrow" )
1677
+ ]);
1678
+ break;
1679
+
1680
+ // Year
1681
+ case "y":
1682
+ case "Y":
1683
+ numeric = true;
1684
+
1685
+ // number l=1:+, l=2:{2}, l=3:{3,}, l=4:{4,}, ...
1686
+ if ( length === 1 ) {
1687
+
1688
+ // Unicode equivalent to /\d+/.
1689
+ tokenRe = new RegExp( "^(" + digitsRe.source + ")+" );
1690
+ } else if ( length === 2 ) {
1691
+
1692
+ // Lenient parsing: there's no year pattern to indicate non-zero-padded 2-digits
1693
+ // year, so parser accepts both zero-padded and non-zero-padded for `yy`.
1694
+ //
1695
+ // Unicode equivalent to /\d\d?/
1696
+ tokenRe = new RegExp( "^(" + digitsRe.source + "){1,2}" );
1697
+ } else {
1698
+
1699
+ // Unicode equivalent to /\d{length,}/
1700
+ tokenRe = new RegExp( "^(" + digitsRe.source + "){" + length + ",}" );
1701
+ }
1702
+ break;
1703
+
1704
+ // Quarter
1705
+ case "Q":
1706
+ case "q":
1707
+
1708
+ // number l=1:{1}, l=2:{2}.
1709
+ // lookup l=3...
1710
+ oneDigitIfLengthOne() || twoDigitsIfLengthTwo() ||
1711
+ lookup([
1712
+ "gregorian/quarters",
1713
+ chr === "Q" ? "format" : "stand-alone",
1714
+ widths[ length - 3 ]
1715
+ ]);
1716
+ break;
1717
+
1718
+ // Month
1719
+ case "M":
1720
+ case "L":
1721
+
1722
+ // number l=1:{1,2}, l=2:{2}.
1723
+ // lookup l=3...
1724
+ //
1725
+ // Lenient parsing: skeleton "yMd" (i.e., one M) may include MM for the pattern,
1726
+ // therefore parser accepts both zero-padded and non-zero-padded for M and MM.
1727
+ // Similar for L.
1728
+ oneOrTwoDigitsIfLengthOneOrTwo() || lookup([
1729
+ "gregorian/months",
1730
+ chr === "M" ? "format" : "stand-alone",
1731
+ widths[ length - 3 ]
1732
+ ]);
1733
+ break;
1734
+
1735
+ // Day
1736
+ case "D":
1737
+
1738
+ // number {l,3}.
1739
+ if ( length <= 3 ) {
1740
+
1741
+ // Equivalent to /\d{length,3}/
1742
+ numeric = true;
1743
+ tokenRe = new RegExp( "^(" + digitsRe.source + "){" + length + ",3}" );
1744
+ }
1745
+ break;
1746
+
1747
+ case "W":
1748
+ case "F":
1749
+
1750
+ // number l=1:{1}.
1751
+ oneDigitIfLengthOne();
1752
+ break;
1753
+
1754
+ // Week day
1755
+ case "e":
1756
+ case "c":
1757
+
1758
+ // number l=1:{1}, l=2:{2}.
1759
+ // lookup for length >=3.
1760
+ if ( length <= 2 ) {
1761
+ oneDigitIfLengthOne() || twoDigitsIfLengthTwo();
1762
+ break;
1763
+ }
1764
+
1765
+ /* falls through */
1766
+ case "E":
1767
+ if ( length === 6 ) {
1768
+
1769
+ // Note: if short day names are not explicitly specified, abbreviated day
1770
+ // names are used instead http://www.unicode.org/reports/tr35/tr35-dates.html#months_days_quarters_eras
1771
+ lookup([
1772
+ "gregorian/days",
1773
+ [ chr === "c" ? "stand-alone" : "format" ],
1774
+ "short"
1775
+ ]) || lookup([
1776
+ "gregorian/days",
1777
+ [ chr === "c" ? "stand-alone" : "format" ],
1778
+ "abbreviated"
1779
+ ]);
1780
+ } else {
1781
+ lookup([
1782
+ "gregorian/days",
1783
+ [ chr === "c" ? "stand-alone" : "format" ],
1784
+ widths[ length < 3 ? 0 : length - 3 ]
1785
+ ]);
1786
+ }
1787
+ break;
1788
+
1789
+ // Period (AM or PM)
1790
+ case "a":
1791
+ lookup([
1792
+ "gregorian/dayPeriods/format/wide"
1793
+ ]);
1794
+ break;
1795
+
1796
+ // Week
1797
+ case "w":
1798
+
1799
+ // number l1:{1,2}, l2:{2}.
1800
+ oneOrTwoDigitsIfLengthOne() || twoDigitsIfLengthTwo();
1801
+ break;
1802
+
1803
+ // Day, Hour, Minute, or Second
1804
+ case "d":
1805
+ case "h":
1806
+ case "H":
1807
+ case "K":
1808
+ case "k":
1809
+ case "j":
1810
+ case "m":
1811
+ case "s":
1812
+
1813
+ // number l1:{1,2}, l2:{2}.
1814
+ //
1815
+ // Lenient parsing:
1816
+ // - skeleton "hms" (i.e., one m) always includes mm for the pattern, i.e., it's
1817
+ // impossible to use a different skeleton to parse non-zero-padded minutes,
1818
+ // therefore parser accepts both zero-padded and non-zero-padded for m. Similar
1819
+ // for seconds s.
1820
+ // - skeleton "hms" (i.e., one h) may include h or hh for the pattern, i.e., it's
1821
+ // impossible to use a different skeleton to parser non-zero-padded hours for some
1822
+ // locales, therefore parser accepts both zero-padded and non-zero-padded for h.
1823
+ // Similar for d (in skeleton yMd).
1824
+ oneOrTwoDigitsIfLengthOneOrTwo();
1825
+ break;
1826
+
1827
+ case "S":
1828
+
1829
+ // number {l}.
1830
+
1831
+ // Unicode equivalent to /\d{length}/
1832
+ numeric = true;
1833
+ tokenRe = new RegExp( "^(" + digitsRe.source + "){" + length + "}" );
1834
+ break;
1835
+
1836
+ case "A":
1837
+
1838
+ // number {l+5}.
1839
+
1840
+ // Unicode equivalent to /\d{length+5}/
1841
+ numeric = true;
1842
+ tokenRe = new RegExp( "^(" + digitsRe.source + "){" + ( length + 5 ) + "}" );
1843
+ break;
1844
+
1845
+ // Zone
1846
+ case "v":
1847
+ case "V":
1848
+ case "z":
1849
+ if ( tokenRe && tokenRe.test( value ) ) {
1850
+ break;
1851
+ }
1852
+ if ( chr === "V" && length === 2 ) {
1853
+ break;
1854
+ }
1855
+
1856
+ /* falls through */
1857
+ case "O":
1858
+
1859
+ // O: "{gmtFormat}+H;{gmtFormat}-H" or "{gmtZeroFormat}", eg. "GMT-8" or "GMT".
1860
+ // OOOO: "{gmtFormat}{hourFormat}" or "{gmtZeroFormat}", eg. "GMT-08:00" or "GMT".
1861
+ if ( value === properties[ "timeZoneNames/gmtZeroFormat" ] ) {
1862
+ token.value = 0;
1863
+ tokenRe = properties[ "timeZoneNames/gmtZeroFormatRe" ];
1864
+ } else {
1865
+ aux = properties[ "timeZoneNames/hourFormat" ].some(function( hourFormatRe ) {
1866
+ if ( hourFormatParse( hourFormatRe, numberParser ) ) {
1867
+ tokenRe = hourFormatRe;
1868
+ return true;
1869
+ }
1870
+ });
1871
+ if ( !aux ) {
1872
+ return null;
1873
+ }
1874
+ }
1875
+ break;
1876
+
1877
+ case "X":
1878
+
1879
+ // Same as x*, except it uses "Z" for zero offset.
1880
+ if ( value === "Z" ) {
1881
+ token.value = 0;
1882
+ tokenRe = /^Z/;
1883
+ break;
1884
+ }
1885
+
1886
+ /* falls through */
1887
+ case "x":
1888
+
1889
+ // x: hourFormat("+HH[mm];-HH[mm]")
1890
+ // xx: hourFormat("+HHmm;-HHmm")
1891
+ // xxx: hourFormat("+HH:mm;-HH:mm")
1892
+ // xxxx: hourFormat("+HHmm[ss];-HHmm[ss]")
1893
+ // xxxxx: hourFormat("+HH:mm[:ss];-HH:mm[:ss]")
1894
+ aux = properties.x.some(function( hourFormatRe ) {
1895
+ if ( hourFormatParse( hourFormatRe ) ) {
1896
+ tokenRe = hourFormatRe;
1897
+ return true;
1898
+ }
1899
+ });
1900
+ if ( !aux ) {
1901
+ return null;
1902
+ }
1903
+ break;
1904
+
1905
+ case "'":
1906
+ token.type = "literal";
1907
+ tokenRe = new RegExp( "^" + regexpEscape( removeLiteralQuotes( current ) ) );
1908
+ break;
1909
+
1910
+ default:
1911
+ token.type = "literal";
1912
+ tokenRe = new RegExp( "^" + regexpEscape( current ) );
1913
+ }
1914
+
1915
+ if ( !tokenRe ) {
1916
+ return false;
1917
+ }
1918
+
1919
+ // Get lexeme and consume it.
1920
+ value = value.replace( tokenRe, function( lexeme ) {
1921
+ token.lexeme = lexeme;
1922
+ if ( numeric ) {
1923
+ token.value = numberParser( lexeme );
1924
+ }
1925
+ return "";
1926
+ });
1927
+
1928
+ if ( !token.lexeme ) {
1929
+ return false;
1930
+ }
1931
+
1932
+ if ( numeric && isNaN( token.value ) ) {
1933
+ return false;
1934
+ }
1935
+
1936
+ tokens.push( token );
1937
+ return true;
1938
+ });
1939
+
1940
+ if ( value !== "" ) {
1941
+ valid = false;
1942
+ }
1943
+
1944
+ return valid ? tokens : [];
1945
+ };
1946
+
1947
+
1948
+
1949
+
1950
+ var dateParserFn = function( numberParser, parseProperties, tokenizerProperties ) {
1951
+ return function dateParser( value ) {
1952
+ var tokens;
1953
+
1954
+ validateParameterPresence( value, "value" );
1955
+ validateParameterTypeString( value, "value" );
1956
+
1957
+ tokens = dateTokenizer( value, numberParser, tokenizerProperties );
1958
+ return dateParse( value, tokens, parseProperties ) || null;
1959
+ };
1960
+ };
1961
+
1962
+
1963
+
1964
+
1965
+ var objectFilter = function( object, testRe ) {
1966
+ var key,
1967
+ copy = {};
1968
+
1969
+ for ( key in object ) {
1970
+ if ( testRe.test( key ) ) {
1971
+ copy[ key ] = object[ key ];
1972
+ }
1973
+ }
1974
+
1975
+ return copy;
1976
+ };
1977
+
1978
+
1979
+
1980
+
1981
+ /**
1982
+ * tokenizerProperties( pattern, cldr )
1983
+ *
1984
+ * @pattern [String] raw pattern.
1985
+ *
1986
+ * @cldr [Cldr instance].
1987
+ *
1988
+ * Return Object with data that will be used by tokenizer.
1989
+ */
1990
+ var dateTokenizerProperties = function( pattern, cldr, timeZone ) {
1991
+ var digitsReSource,
1992
+ properties = {
1993
+ pattern: looseMatching( pattern )
1994
+ },
1995
+ timeSeparator = numberSymbol( "timeSeparator", cldr ),
1996
+ widths = [ "abbreviated", "wide", "narrow" ];
1997
+
1998
+ digitsReSource = numberNumberingSystemDigitsMap( cldr );
1999
+ digitsReSource = digitsReSource ? "[" + digitsReSource + "]" : "\\d";
2000
+ properties.digitsRe = new RegExp( digitsReSource );
2001
+
2002
+ // Transform:
2003
+ // - "+H;-H" -> /\+(\d\d?)|-(\d\d?)/
2004
+ // - "+HH;-HH" -> /\+(\d\d)|-(\d\d)/
2005
+ // - "+HHmm;-HHmm" -> /\+(\d\d)(\d\d)|-(\d\d)(\d\d)/
2006
+ // - "+HH:mm;-HH:mm" -> /\+(\d\d):(\d\d)|-(\d\d):(\d\d)/
2007
+ //
2008
+ // If gmtFormat is GMT{0}, the regexp must fill {0} in each side, e.g.:
2009
+ // - "+H;-H" -> /GMT\+(\d\d?)|GMT-(\d\d?)/
2010
+ function hourFormatRe( hourFormat, gmtFormat, digitsReSource, timeSeparator ) {
2011
+ var re;
2012
+
2013
+ if ( !digitsReSource ) {
2014
+ digitsReSource = "\\d";
2015
+ }
2016
+ if ( !gmtFormat ) {
2017
+ gmtFormat = "{0}";
2018
+ }
2019
+
2020
+ re = hourFormat
2021
+ .replace( "+", "\\+" )
2022
+
2023
+ // Unicode equivalent to (\\d\\d)
2024
+ .replace( /HH|mm|ss/g, "((" + digitsReSource + "){2})" )
2025
+
2026
+ // Unicode equivalent to (\\d\\d?)
2027
+ .replace( /H|m/g, "((" + digitsReSource + "){1,2})" );
2028
+
2029
+ if ( timeSeparator ) {
2030
+ re = re.replace( /:/g, timeSeparator );
2031
+ }
2032
+
2033
+ re = re.split( ";" ).map(function( part ) {
2034
+ return gmtFormat.replace( "{0}", part );
2035
+ }).join( "|" );
2036
+
2037
+ return new RegExp( "^" + re );
2038
+ }
2039
+
2040
+ function populateProperties( path, value ) {
2041
+
2042
+ // Skip
2043
+ var skipRe = /(timeZoneNames\/zone|supplemental\/metaZones|timeZoneNames\/metazone|timeZoneNames\/regionFormat|timeZoneNames\/gmtFormat)/;
2044
+ if ( skipRe.test( path ) ) {
2045
+ return;
2046
+ }
2047
+
2048
+ if ( !value ) {
2049
+ return;
2050
+ }
2051
+
2052
+ // The `dates` and `calendars` trim's purpose is to reduce properties' key size only.
2053
+ path = path.replace( /^.*\/dates\//, "" ).replace( /calendars\//, "" );
2054
+
2055
+ // Specific filter for "gregorian/dayPeriods/format/wide".
2056
+ if ( path === "gregorian/dayPeriods/format/wide" ) {
2057
+ value = objectFilter( value, /^am|^pm/ );
2058
+ }
2059
+
2060
+ // Transform object into array of pairs [key, /value/], sort by desc value length.
2061
+ if ( isPlainObject( value ) ) {
2062
+ value = Object.keys( value ).map(function( key ) {
2063
+ return [ key, new RegExp( "^" + regexpEscape( looseMatching( value[ key ] ) ) ) ];
2064
+ }).sort(function( a, b ) {
2065
+ return b[ 1 ].source.length - a[ 1 ].source.length;
2066
+ });
2067
+
2068
+ // If typeof value === "string".
2069
+ } else {
2070
+ value = looseMatching( value );
2071
+ }
2072
+ properties[ path ] = value;
2073
+ }
2074
+
2075
+ function regexpSourceSomeTerm( terms ) {
2076
+ return "(" + terms.filter(function( item ) {
2077
+ return item;
2078
+ }).reduce(function( memo, item ) {
2079
+ return memo + "|" + item;
2080
+ }) + ")";
2081
+ }
2082
+
2083
+ cldr.on( "get", populateProperties );
2084
+
2085
+ pattern.match( datePatternRe ).forEach(function( current ) {
2086
+ var aux, chr, daylightTzName, gmtFormat, length, standardTzName;
2087
+
2088
+ chr = current.charAt( 0 );
2089
+ length = current.length;
2090
+
2091
+ if ( chr === "Z" ) {
2092
+ if ( length < 5 ) {
2093
+ chr = "O";
2094
+ length = 4;
2095
+ } else {
2096
+ chr = "X";
2097
+ length = 5;
2098
+ }
2099
+ }
2100
+
2101
+ // z...zzz: "{shortRegion}", eg. "PST" or "PDT".
2102
+ // zzzz: "{regionName} {Standard Time}" or "{regionName} {Daylight Time}",
2103
+ // e.g., "Pacific Standard Time" or "Pacific Daylight Time".
2104
+ // http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
2105
+ if ( chr === "z" ) {
2106
+ standardTzName = dateGetTimeZoneName( length, "standard", timeZone, cldr );
2107
+ daylightTzName = dateGetTimeZoneName( length, "daylight", timeZone, cldr );
2108
+ if ( standardTzName ) {
2109
+ standardTzName = regexpEscape( looseMatching( standardTzName ) );
2110
+ }
2111
+ if ( daylightTzName ) {
2112
+ daylightTzName = regexpEscape( looseMatching( daylightTzName ) );
2113
+ }
2114
+ if ( standardTzName || daylightTzName ) {
2115
+ properties.standardOrDaylightTzName = new RegExp(
2116
+ "^" + regexpSourceSomeTerm([ standardTzName, daylightTzName ])
2117
+ );
2118
+ }
2119
+
2120
+ // Fall through the "O" format in case one name is missing.
2121
+ if ( !standardTzName || !daylightTzName ) {
2122
+ chr = "O";
2123
+ if ( length < 4 ) {
2124
+ length = 1;
2125
+ }
2126
+ }
2127
+ }
2128
+
2129
+ // v...vvv: "{shortRegion}", eg. "PT".
2130
+ // vvvv: "{regionName} {Time}" or "{regionName} {Time}",
2131
+ // e.g., "Pacific Time"
2132
+ // http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
2133
+ if ( chr === "v" ) {
2134
+ if ( length !== 1 && length !== 4 ) {
2135
+ throw createErrorUnsupportedFeature({
2136
+ feature: "timezone pattern `" + pattern + "`"
2137
+ });
2138
+ }
2139
+ var genericTzName = dateGetTimeZoneName( length, "generic", timeZone, cldr );
2140
+ if ( genericTzName ) {
2141
+ properties.genericTzName = new RegExp(
2142
+ "^" + regexpEscape( looseMatching( genericTzName ) )
2143
+ );
2144
+ chr = "O";
2145
+
2146
+ // Fall back to "V" format.
2147
+ } else {
2148
+ chr = "V";
2149
+ length = 4;
2150
+ }
2151
+ }
2152
+
2153
+ switch ( chr ) {
2154
+
2155
+ // Era
2156
+ case "G":
2157
+ cldr.main([
2158
+ "dates/calendars/gregorian/eras",
2159
+ length <= 3 ? "eraAbbr" : ( length === 4 ? "eraNames" : "eraNarrow" )
2160
+ ]);
2161
+ break;
2162
+
2163
+ // Year
2164
+ case "u": // Extended year. Need to be implemented.
2165
+ case "U": // Cyclic year name. Need to be implemented.
2166
+ throw createErrorUnsupportedFeature({
2167
+ feature: "year pattern `" + chr + "`"
2168
+ });
2169
+
2170
+ // Quarter
2171
+ case "Q":
2172
+ case "q":
2173
+ if ( length > 2 ) {
2174
+ cldr.main([
2175
+ "dates/calendars/gregorian/quarters",
2176
+ chr === "Q" ? "format" : "stand-alone",
2177
+ widths[ length - 3 ]
2178
+ ]);
2179
+ }
2180
+ break;
2181
+
2182
+ // Month
2183
+ case "M":
2184
+ case "L":
2185
+
2186
+ // number l=1:{1,2}, l=2:{2}.
2187
+ // lookup l=3...
2188
+ if ( length > 2 ) {
2189
+ cldr.main([
2190
+ "dates/calendars/gregorian/months",
2191
+ chr === "M" ? "format" : "stand-alone",
2192
+ widths[ length - 3 ]
2193
+ ]);
2194
+ }
2195
+ break;
2196
+
2197
+ // Day
2198
+ case "g":
2199
+
2200
+ // Modified Julian day. Need to be implemented.
2201
+ throw createErrorUnsupportedFeature({
2202
+ feature: "Julian day pattern `g`"
2203
+ });
2204
+
2205
+ // Week day
2206
+ case "e":
2207
+ case "c":
2208
+
2209
+ // lookup for length >=3.
2210
+ if ( length <= 2 ) {
2211
+ break;
2212
+ }
2213
+
2214
+ /* falls through */
2215
+ case "E":
2216
+ if ( length === 6 ) {
2217
+
2218
+ // Note: if short day names are not explicitly specified, abbreviated day
2219
+ // names are used instead http://www.unicode.org/reports/tr35/tr35-dates.html#months_days_quarters_eras
2220
+ // eslint-disable-next-line no-unused-expressions
2221
+ cldr.main([
2222
+ "dates/calendars/gregorian/days",
2223
+ [ chr === "c" ? "stand-alone" : "format" ],
2224
+ "short"
2225
+ ]) || cldr.main([
2226
+ "dates/calendars/gregorian/days",
2227
+ [ chr === "c" ? "stand-alone" : "format" ],
2228
+ "abbreviated"
2229
+ ]);
2230
+ } else {
2231
+ cldr.main([
2232
+ "dates/calendars/gregorian/days",
2233
+ [ chr === "c" ? "stand-alone" : "format" ],
2234
+ widths[ length < 3 ? 0 : length - 3 ]
2235
+ ]);
2236
+ }
2237
+ break;
2238
+
2239
+ // Period (AM or PM)
2240
+ case "a":
2241
+ cldr.main(
2242
+ "dates/calendars/gregorian/dayPeriods/format/wide"
2243
+ );
2244
+ break;
2245
+
2246
+ // Zone
2247
+ case "V":
2248
+
2249
+ if ( length === 1 ) {
2250
+ throw createErrorUnsupportedFeature({
2251
+ feature: "timezone pattern `" + pattern + "`"
2252
+ });
2253
+ }
2254
+
2255
+ if ( timeZone ) {
2256
+ if ( length === 2 ) {
2257
+
2258
+ // Skip looseMatching processing since timeZone is a canonical posix value.
2259
+ properties.timeZoneName = timeZone;
2260
+ properties.timeZoneNameRe = new RegExp( "^" + regexpEscape( timeZone ) );
2261
+ break;
2262
+ }
2263
+
2264
+ var timeZoneName,
2265
+ exemplarCity = cldr.main([
2266
+ "dates/timeZoneNames/zone", timeZone, "exemplarCity"
2267
+ ]);
2268
+
2269
+ if ( length === 3 ) {
2270
+ if ( !exemplarCity ) {
2271
+ exemplarCity = cldr.main([
2272
+ "dates/timeZoneNames/zone/Etc/Unknown/exemplarCity"
2273
+ ]);
2274
+ }
2275
+ timeZoneName = exemplarCity;
2276
+ }
2277
+
2278
+ if ( exemplarCity && length === 4 ) {
2279
+ timeZoneName = formatMessage(
2280
+ cldr.main(
2281
+ "dates/timeZoneNames/regionFormat"
2282
+ ),
2283
+ [ exemplarCity ]
2284
+ );
2285
+ }
2286
+
2287
+ if ( timeZoneName ) {
2288
+ timeZoneName = looseMatching( timeZoneName );
2289
+ properties.timeZoneName = timeZoneName;
2290
+ properties.timeZoneNameRe = new RegExp(
2291
+ "^" + regexpEscape( timeZoneName )
2292
+ );
2293
+ }
2294
+ }
2295
+
2296
+ if ( current === "v" ) {
2297
+ length = 1;
2298
+ }
2299
+
2300
+ /* falls through */
2301
+ case "z":
2302
+ case "O":
2303
+ gmtFormat = cldr.main( "dates/timeZoneNames/gmtFormat" );
2304
+ cldr.main( "dates/timeZoneNames/gmtZeroFormat" );
2305
+ cldr.main( "dates/timeZoneNames/hourFormat" );
2306
+ properties[ "timeZoneNames/gmtZeroFormatRe" ] =
2307
+ new RegExp( "^" + regexpEscape( properties[ "timeZoneNames/gmtZeroFormat" ] ) );
2308
+ aux = properties[ "timeZoneNames/hourFormat" ];
2309
+ properties[ "timeZoneNames/hourFormat" ] = (
2310
+ length < 4 ?
2311
+ [ dateTimezoneHourFormatHm( aux, "H" ), dateTimezoneHourFormatH( aux ) ] :
2312
+ [ dateTimezoneHourFormatHm( aux, "HH" ) ]
2313
+ ).map(function( hourFormat ) {
2314
+ return hourFormatRe(
2315
+ hourFormat,
2316
+ gmtFormat,
2317
+ digitsReSource,
2318
+ timeSeparator
2319
+ );
2320
+ });
2321
+
2322
+ /* falls through */
2323
+ case "X":
2324
+ case "x":
2325
+
2326
+ // x: hourFormat("+HH[mm];-HH[mm]")
2327
+ // xx: hourFormat("+HHmm;-HHmm")
2328
+ // xxx: hourFormat("+HH:mm;-HH:mm")
2329
+ // xxxx: hourFormat("+HHmm[ss];-HHmm[ss]")
2330
+ // xxxxx: hourFormat("+HH:mm[:ss];-HH:mm[:ss]")
2331
+ properties.x = [
2332
+ [ "+HHmm;-HHmm", "+HH;-HH" ],
2333
+ [ "+HHmm;-HHmm" ],
2334
+ [ "+HH:mm;-HH:mm" ],
2335
+ [ "+HHmmss;-HHmmss", "+HHmm;-HHmm" ],
2336
+ [ "+HH:mm:ss;-HH:mm:ss", "+HH:mm;-HH:mm" ]
2337
+ ][ length - 1 ].map(function( hourFormat ) {
2338
+ return hourFormatRe( hourFormat );
2339
+ });
2340
+ }
2341
+ });
2342
+
2343
+ cldr.off( "get", populateProperties );
2344
+
2345
+ return properties;
2346
+ };
2347
+
2348
+
2349
+
2350
+
2351
+ /**
2352
+ * dayOfWeek( date, firstDay )
2353
+ *
2354
+ * @date
2355
+ *
2356
+ * @firstDay the result of `dateFirstDayOfWeek( cldr )`
2357
+ *
2358
+ * Return the day of the week normalized by the territory's firstDay [0-6].
2359
+ * Eg for "mon":
2360
+ * - return 0 if territory is GB, or BR, or DE, or FR (week starts on "mon");
2361
+ * - return 1 if territory is US (week starts on "sun");
2362
+ * - return 2 if territory is EG (week starts on "sat");
2363
+ */
2364
+ var dateDayOfWeek = function( date, firstDay ) {
2365
+ return ( date.getDay() - firstDay + 7 ) % 7;
2366
+ };
2367
+
2368
+
2369
+
2370
+
2371
+ /**
2372
+ * distanceInDays( from, to )
2373
+ *
2374
+ * Return the distance in days between from and to Dates.
2375
+ */
2376
+ var dateDistanceInDays = function( from, to ) {
2377
+ var inDays = 864e5;
2378
+ return ( to.getTime() - from.getTime() ) / inDays;
2379
+ };
2380
+
2381
+
2382
+
2383
+
2384
+ /**
2385
+ * dayOfYear
2386
+ *
2387
+ * Return the distance in days of the date to the begin of the year [0-d].
2388
+ */
2389
+ var dateDayOfYear = function( date ) {
2390
+ return Math.floor( dateDistanceInDays( dateStartOf( date, "year" ), date ) );
2391
+ };
2392
+
2393
+
2394
+
2395
+
2396
+ // Invert key and values, e.g., {"year": "yY"} ==> {"y": "year", "Y": "year"}
2397
+ var dateFieldsMap = objectInvert({
2398
+ "era": "G",
2399
+ "year": "yY",
2400
+ "quarter": "qQ",
2401
+ "month": "ML",
2402
+ "week": "wW",
2403
+ "day": "dDF",
2404
+ "weekday": "ecE",
2405
+ "dayperiod": "a",
2406
+ "hour": "hHkK",
2407
+ "minute": "m",
2408
+ "second": "sSA",
2409
+ "zone": "zvVOxX"
2410
+ }, function( object, key, value ) {
2411
+ value.split( "" ).forEach(function( symbol ) {
2412
+ object[ symbol ] = key;
2413
+ });
2414
+ return object;
2415
+ });
2416
+
2417
+
2418
+
2419
+
2420
+ /**
2421
+ * millisecondsInDay
2422
+ */
2423
+ var dateMillisecondsInDay = function( date ) {
2424
+
2425
+ // TODO Handle daylight savings discontinuities
2426
+ return date - dateStartOf( date, "day" );
2427
+ };
2428
+
2429
+
2430
+
2431
+
2432
+ /**
2433
+ * hourFormat( date, format, timeSeparator, formatNumber )
2434
+ *
2435
+ * Return date's timezone offset according to the format passed.
2436
+ * Eg for format when timezone offset is 180:
2437
+ * - "+H;-H": -3
2438
+ * - "+HHmm;-HHmm": -0300
2439
+ * - "+HH:mm;-HH:mm": -03:00
2440
+ * - "+HH:mm:ss;-HH:mm:ss": -03:00:00
2441
+ */
2442
+ var dateTimezoneHourFormat = function( date, format, timeSeparator, formatNumber ) {
2443
+ var absOffset,
2444
+ offset = date.getTimezoneOffset();
2445
+
2446
+ absOffset = Math.abs( offset );
2447
+ formatNumber = formatNumber || {
2448
+ 1: function( value ) {
2449
+ return stringPad( value, 1 );
2450
+ },
2451
+ 2: function( value ) {
2452
+ return stringPad( value, 2 );
2453
+ }
2454
+ };
2455
+
2456
+ return format
2457
+
2458
+ // Pick the correct sign side (+ or -).
2459
+ .split( ";" )[ offset > 0 ? 1 : 0 ]
2460
+
2461
+ // Localize time separator
2462
+ .replace( ":", timeSeparator )
2463
+
2464
+ // Update hours offset.
2465
+ .replace( /HH?/, function( match ) {
2466
+ return formatNumber[ match.length ]( Math.floor( absOffset / 60 ) );
2467
+ })
2468
+
2469
+ // Update minutes offset and return.
2470
+ .replace( /mm/, function() {
2471
+ return formatNumber[ 2 ]( Math.floor( absOffset % 60 ) );
2472
+ })
2473
+
2474
+ // Update minutes offset and return.
2475
+ .replace( /ss/, function() {
2476
+ return formatNumber[ 2 ]( Math.floor( absOffset % 1 * 60 ) );
2477
+ });
2478
+ };
2479
+
2480
+
2481
+
2482
+
2483
+ /**
2484
+ * format( date, properties )
2485
+ *
2486
+ * @date [Date instance].
2487
+ *
2488
+ * @properties
2489
+ *
2490
+ * TODO Support other calendar types.
2491
+ *
2492
+ * Disclosure: this function borrows excerpts of dojo/date/locale.
2493
+ */
2494
+ var dateFormat = function( date, numberFormatters, properties ) {
2495
+ var parts = [];
2496
+
2497
+ var timeSeparator = properties.timeSeparator;
2498
+
2499
+ // create globalize date with given timezone data
2500
+ if ( properties.timeZoneData ) {
2501
+ date = new ZonedDateTime( date, properties.timeZoneData() );
2502
+ }
2503
+
2504
+ properties.pattern.replace( datePatternRe, function( current ) {
2505
+ var aux, dateField, type, value,
2506
+ chr = current.charAt( 0 ),
2507
+ length = current.length;
2508
+
2509
+ if ( chr === "j" ) {
2510
+
2511
+ // Locale preferred hHKk.
2512
+ // http://www.unicode.org/reports/tr35/tr35-dates.html#Time_Data
2513
+ chr = properties.preferredTime;
2514
+ }
2515
+
2516
+ if ( chr === "Z" ) {
2517
+
2518
+ // Z..ZZZ: same as "xxxx".
2519
+ if ( length < 4 ) {
2520
+ chr = "x";
2521
+ length = 4;
2522
+
2523
+ // ZZZZ: same as "OOOO".
2524
+ } else if ( length < 5 ) {
2525
+ chr = "O";
2526
+ length = 4;
2527
+
2528
+ // ZZZZZ: same as "XXXXX"
2529
+ } else {
2530
+ chr = "X";
2531
+ length = 5;
2532
+ }
2533
+ }
2534
+
2535
+ // z...zzz: "{shortRegion}", e.g., "PST" or "PDT".
2536
+ // zzzz: "{regionName} {Standard Time}" or "{regionName} {Daylight Time}",
2537
+ // e.g., "Pacific Standard Time" or "Pacific Daylight Time".
2538
+ if ( chr === "z" ) {
2539
+ if ( date.isDST ) {
2540
+ value = date.isDST() ? properties.daylightTzName : properties.standardTzName;
2541
+ }
2542
+
2543
+ // Fall back to "O" format.
2544
+ if ( !value ) {
2545
+ chr = "O";
2546
+ if ( length < 4 ) {
2547
+ length = 1;
2548
+ }
2549
+ }
2550
+ }
2551
+
2552
+ switch ( chr ) {
2553
+
2554
+ // Era
2555
+ case "G":
2556
+ value = properties.eras[ date.getFullYear() < 0 ? 0 : 1 ];
2557
+ break;
2558
+
2559
+ // Year
2560
+ case "y":
2561
+
2562
+ // Plain year.
2563
+ // The length specifies the padding, but for two letters it also specifies the
2564
+ // maximum length.
2565
+ value = date.getFullYear();
2566
+ if ( length === 2 ) {
2567
+ value = String( value );
2568
+ value = +value.substr( value.length - 2 );
2569
+ }
2570
+ break;
2571
+
2572
+ case "Y":
2573
+
2574
+ // Year in "Week of Year"
2575
+ // The length specifies the padding, but for two letters it also specifies the
2576
+ // maximum length.
2577
+ // yearInWeekofYear = date + DaysInAWeek - (dayOfWeek - firstDay) - minDays
2578
+ value = new Date( date.getTime() );
2579
+ value.setDate(
2580
+ value.getDate() + 7 -
2581
+ dateDayOfWeek( date, properties.firstDay ) -
2582
+ properties.firstDay -
2583
+ properties.minDays
2584
+ );
2585
+ value = value.getFullYear();
2586
+ if ( length === 2 ) {
2587
+ value = String( value );
2588
+ value = +value.substr( value.length - 2 );
2589
+ }
2590
+ break;
2591
+
2592
+ // Quarter
2593
+ case "Q":
2594
+ case "q":
2595
+ value = Math.ceil( ( date.getMonth() + 1 ) / 3 );
2596
+ if ( length > 2 ) {
2597
+ value = properties.quarters[ chr ][ length ][ value ];
2598
+ }
2599
+ break;
2600
+
2601
+ // Month
2602
+ case "M":
2603
+ case "L":
2604
+ value = date.getMonth() + 1;
2605
+ if ( length > 2 ) {
2606
+ value = properties.months[ chr ][ length ][ value ];
2607
+ }
2608
+ break;
2609
+
2610
+ // Week
2611
+ case "w":
2612
+
2613
+ // Week of Year.
2614
+ // woy = ceil( ( doy + dow of 1/1 ) / 7 ) - minDaysStuff ? 1 : 0.
2615
+ // TODO should pad on ww? Not documented, but I guess so.
2616
+ value = dateDayOfWeek( dateStartOf( date, "year" ), properties.firstDay );
2617
+ value = Math.ceil( ( dateDayOfYear( date ) + value ) / 7 ) -
2618
+ ( 7 - value >= properties.minDays ? 0 : 1 );
2619
+ break;
2620
+
2621
+ case "W":
2622
+
2623
+ // Week of Month.
2624
+ // wom = ceil( ( dom + dow of `1/month` ) / 7 ) - minDaysStuff ? 1 : 0.
2625
+ value = dateDayOfWeek( dateStartOf( date, "month" ), properties.firstDay );
2626
+ value = Math.ceil( ( date.getDate() + value ) / 7 ) -
2627
+ ( 7 - value >= properties.minDays ? 0 : 1 );
2628
+ break;
2629
+
2630
+ // Day
2631
+ case "d":
2632
+ value = date.getDate();
2633
+ break;
2634
+
2635
+ case "D":
2636
+ value = dateDayOfYear( date ) + 1;
2637
+ break;
2638
+
2639
+ case "F":
2640
+
2641
+ // Day of Week in month. eg. 2nd Wed in July.
2642
+ value = Math.floor( date.getDate() / 7 ) + 1;
2643
+ break;
2644
+
2645
+ // Week day
2646
+ case "e":
2647
+ case "c":
2648
+ if ( length <= 2 ) {
2649
+
2650
+ // Range is [1-7] (deduced by example provided on documentation)
2651
+ // TODO Should pad with zeros (not specified in the docs)?
2652
+ value = dateDayOfWeek( date, properties.firstDay ) + 1;
2653
+ break;
2654
+ }
2655
+
2656
+ /* falls through */
2657
+ case "E":
2658
+ value = dateWeekDays[ date.getDay() ];
2659
+ value = properties.days[ chr ][ length ][ value ];
2660
+ break;
2661
+
2662
+ // Period (AM or PM)
2663
+ case "a":
2664
+ value = properties.dayPeriods[ date.getHours() < 12 ? "am" : "pm" ];
2665
+ break;
2666
+
2667
+ // Hour
2668
+ case "h": // 1-12
2669
+ value = ( date.getHours() % 12 ) || 12;
2670
+ break;
2671
+
2672
+ case "H": // 0-23
2673
+ value = date.getHours();
2674
+ break;
2675
+
2676
+ case "K": // 0-11
2677
+ value = date.getHours() % 12;
2678
+ break;
2679
+
2680
+ case "k": // 1-24
2681
+ value = date.getHours() || 24;
2682
+ break;
2683
+
2684
+ // Minute
2685
+ case "m":
2686
+ value = date.getMinutes();
2687
+ break;
2688
+
2689
+ // Second
2690
+ case "s":
2691
+ value = date.getSeconds();
2692
+ break;
2693
+
2694
+ case "S":
2695
+ value = Math.round( date.getMilliseconds() * Math.pow( 10, length - 3 ) );
2696
+ break;
2697
+
2698
+ case "A":
2699
+ value = Math.round( dateMillisecondsInDay( date ) * Math.pow( 10, length - 3 ) );
2700
+ break;
2701
+
2702
+ // Zone
2703
+ case "z":
2704
+ break;
2705
+
2706
+ case "v":
2707
+
2708
+ // v...vvv: "{shortRegion}", eg. "PT".
2709
+ // vvvv: "{regionName} {Time}",
2710
+ // e.g., "Pacific Time".
2711
+ if ( properties.genericTzName ) {
2712
+ value = properties.genericTzName;
2713
+ break;
2714
+ }
2715
+
2716
+ /* falls through */
2717
+ case "V":
2718
+
2719
+ //VVVV: "{explarCity} {Time}", e.g., "Los Angeles Time"
2720
+ if ( properties.timeZoneName ) {
2721
+ value = properties.timeZoneName;
2722
+ break;
2723
+ }
2724
+
2725
+ if ( current === "v" ) {
2726
+ length = 1;
2727
+ }
2728
+
2729
+ /* falls through */
2730
+ case "O":
2731
+
2732
+ // O: "{gmtFormat}+H;{gmtFormat}-H" or "{gmtZeroFormat}", eg. "GMT-8" or "GMT".
2733
+ // OOOO: "{gmtFormat}{hourFormat}" or "{gmtZeroFormat}", eg. "GMT-08:00" or "GMT".
2734
+ if ( date.getTimezoneOffset() === 0 ) {
2735
+ value = properties.gmtZeroFormat;
2736
+ } else {
2737
+
2738
+ // If O..OOO and timezone offset has non-zero minutes, show minutes.
2739
+ if ( length < 4 ) {
2740
+ aux = date.getTimezoneOffset();
2741
+ aux = properties.hourFormat[ aux % 60 - aux % 1 === 0 ? 0 : 1 ];
2742
+ } else {
2743
+ aux = properties.hourFormat;
2744
+ }
2745
+
2746
+ value = dateTimezoneHourFormat(
2747
+ date,
2748
+ aux,
2749
+ timeSeparator,
2750
+ numberFormatters
2751
+ );
2752
+ value = properties.gmtFormat.replace( /\{0\}/, value );
2753
+ }
2754
+ break;
2755
+
2756
+ case "X":
2757
+
2758
+ // Same as x*, except it uses "Z" for zero offset.
2759
+ if ( date.getTimezoneOffset() === 0 ) {
2760
+ value = "Z";
2761
+ break;
2762
+ }
2763
+
2764
+ /* falls through */
2765
+ case "x":
2766
+
2767
+ // x: hourFormat("+HH[mm];-HH[mm]")
2768
+ // xx: hourFormat("+HHmm;-HHmm")
2769
+ // xxx: hourFormat("+HH:mm;-HH:mm")
2770
+ // xxxx: hourFormat("+HHmm[ss];-HHmm[ss]")
2771
+ // xxxxx: hourFormat("+HH:mm[:ss];-HH:mm[:ss]")
2772
+ aux = date.getTimezoneOffset();
2773
+
2774
+ // If x and timezone offset has non-zero minutes, use xx (i.e., show minutes).
2775
+ if ( length === 1 && aux % 60 - aux % 1 !== 0 ) {
2776
+ length += 1;
2777
+ }
2778
+
2779
+ // If (xxxx or xxxxx) and timezone offset has zero seconds, use xx or xxx
2780
+ // respectively (i.e., don't show optional seconds).
2781
+ if ( ( length === 4 || length === 5 ) && aux % 1 === 0 ) {
2782
+ length -= 2;
2783
+ }
2784
+
2785
+ value = [
2786
+ "+HH;-HH",
2787
+ "+HHmm;-HHmm",
2788
+ "+HH:mm;-HH:mm",
2789
+ "+HHmmss;-HHmmss",
2790
+ "+HH:mm:ss;-HH:mm:ss"
2791
+ ][ length - 1 ];
2792
+
2793
+ value = dateTimezoneHourFormat( date, value, ":" );
2794
+ break;
2795
+
2796
+ // timeSeparator
2797
+ case ":":
2798
+ value = timeSeparator;
2799
+ break;
2800
+
2801
+ // ' literals.
2802
+ case "'":
2803
+ value = removeLiteralQuotes( current );
2804
+ break;
2805
+
2806
+ // Anything else is considered a literal, including [ ,:/.@#], chinese, japonese, and
2807
+ // arabic characters.
2808
+ default:
2809
+ value = current;
2810
+
2811
+ }
2812
+ if ( typeof value === "number" ) {
2813
+ value = numberFormatters[ length ]( value );
2814
+ }
2815
+
2816
+ dateField = dateFieldsMap[ chr ];
2817
+ type = dateField ? dateField : "literal";
2818
+
2819
+ partsPush( parts, type, value );
2820
+ });
2821
+
2822
+ return parts;
2823
+
2824
+ };
2825
+
2826
+
2827
+
2828
+
2829
+ var dateToPartsFormatterFn = function( numberFormatters, properties ) {
2830
+ return function dateToPartsFormatter( value ) {
2831
+ validateParameterPresence( value, "value" );
2832
+ validateParameterTypeDate( value, "value" );
2833
+
2834
+ return dateFormat( value, numberFormatters, properties );
2835
+ };
2836
+
2837
+ };
2838
+
2839
+
2840
+
2841
+
2842
+ function optionsHasStyle( options ) {
2843
+ return options.skeleton !== undefined ||
2844
+ options.date !== undefined ||
2845
+ options.time !== undefined ||
2846
+ options.datetime !== undefined ||
2847
+ options.raw !== undefined;
2848
+ }
2849
+
2850
+ function validateRequiredCldr( path, value ) {
2851
+ validateCldr( path, value, {
2852
+ skip: [
2853
+ /dates\/calendars\/gregorian\/dateTimeFormats\/availableFormats/,
2854
+ /dates\/calendars\/gregorian\/days\/.*\/short/,
2855
+ /dates\/timeZoneNames\/zone/,
2856
+ /dates\/timeZoneNames\/metazone/,
2857
+ /globalize-iana/,
2858
+ /supplemental\/metaZones/,
2859
+ /supplemental\/timeData\/(?!001)/,
2860
+ /supplemental\/weekData\/(?!001)/
2861
+ ]
2862
+ });
2863
+ }
2864
+
2865
+ function validateOptionsPreset( options ) {
2866
+ validateOptionsPresetEach( "date", options );
2867
+ validateOptionsPresetEach( "time", options );
2868
+ validateOptionsPresetEach( "datetime", options );
2869
+ }
2870
+
2871
+ function validateOptionsPresetEach( type, options ) {
2872
+ var value = options[ type ];
2873
+ validate(
2874
+ "E_INVALID_OPTIONS",
2875
+ "Invalid `{{type}: \"{value}\"}`.",
2876
+ value === undefined || [ "short", "medium", "long", "full" ].indexOf( value ) !== -1,
2877
+ { type: type, value: value }
2878
+ );
2879
+ }
2880
+
2881
+ function validateOptionsSkeleton( pattern, skeleton ) {
2882
+ validate(
2883
+ "E_INVALID_OPTIONS",
2884
+ "Invalid `{skeleton: \"{value}\"}` based on provided CLDR.",
2885
+ skeleton === undefined || ( typeof pattern === "string" && pattern ),
2886
+ { type: "skeleton", value: skeleton }
2887
+ );
2888
+ }
2889
+
2890
+ function validateRequiredIana( timeZone ) {
2891
+ return function( path, value ) {
2892
+
2893
+ if ( !/globalize-iana/.test( path ) ) {
2894
+ return;
2895
+ }
2896
+
2897
+ validate(
2898
+ "E_MISSING_IANA_TZ",
2899
+ "Missing required IANA timezone content for `{timeZone}`: `{path}`.",
2900
+ value,
2901
+ {
2902
+ path: path.replace( /globalize-iana\//, "" ),
2903
+ timeZone: timeZone
2904
+ }
2905
+ );
2906
+ };
2907
+ }
2908
+
2909
+ /**
2910
+ * .loadTimeZone( json )
2911
+ *
2912
+ * @json [JSON]
2913
+ *
2914
+ * Load IANA timezone data.
2915
+ */
2916
+ Globalize.loadTimeZone = function( json ) {
2917
+ var customData = {
2918
+ "globalize-iana": json
2919
+ };
2920
+
2921
+ validateParameterPresence( json, "json" );
2922
+ validateParameterTypePlainObject( json, "json" );
2923
+
2924
+ Cldr.load( customData );
2925
+ };
2926
+
2927
+ /**
2928
+ * .dateFormatter( options )
2929
+ *
2930
+ * @options [Object] see date/expand_pattern for more info.
2931
+ *
2932
+ * Return a date formatter function (of the form below) according to the given options and the
2933
+ * default/instance locale.
2934
+ *
2935
+ * fn( value )
2936
+ *
2937
+ * @value [Date]
2938
+ *
2939
+ * Return a function that formats a date according to the given `format` and the default/instance
2940
+ * locale.
2941
+ */
2942
+ Globalize.dateFormatter =
2943
+ Globalize.prototype.dateFormatter = function( options ) {
2944
+ var args, dateToPartsFormatter, returnFn;
2945
+
2946
+ validateParameterTypePlainObject( options, "options" );
2947
+
2948
+ options = options || {};
2949
+ if ( !optionsHasStyle( options ) ) {
2950
+ options.skeleton = "yMd";
2951
+ }
2952
+ args = [ options ];
2953
+
2954
+ dateToPartsFormatter = this.dateToPartsFormatter( options );
2955
+ returnFn = dateFormatterFn( dateToPartsFormatter );
2956
+ runtimeBind( args, this.cldr, returnFn, [ dateToPartsFormatter ] );
2957
+
2958
+ return returnFn;
2959
+ };
2960
+
2961
+ /**
2962
+ * .dateToPartsFormatter( options )
2963
+ *
2964
+ * @options [Object] see date/expand_pattern for more info.
2965
+ *
2966
+ * Return a date formatter function (of the form below) according to the given options and the
2967
+ * default/instance locale.
2968
+ *
2969
+ * fn( value )
2970
+ *
2971
+ * @value [Date]
2972
+ *
2973
+ * Return a function that formats a date to parts according to the given `format`
2974
+ * and the default/instance
2975
+ * locale.
2976
+ */
2977
+ Globalize.dateToPartsFormatter =
2978
+ Globalize.prototype.dateToPartsFormatter = function( options ) {
2979
+ var args, cldr, numberFormatters, pad, pattern, properties, returnFn,
2980
+ timeZone, ianaListener;
2981
+
2982
+ validateParameterTypePlainObject( options, "options" );
2983
+
2984
+ cldr = this.cldr;
2985
+ options = options || {};
2986
+ if ( !optionsHasStyle( options ) ) {
2987
+ options.skeleton = "yMd";
2988
+ }
2989
+
2990
+ validateOptionsPreset( options );
2991
+ validateDefaultLocale( cldr );
2992
+
2993
+ timeZone = options.timeZone;
2994
+ validateParameterTypeString( timeZone, "options.timeZone" );
2995
+
2996
+ args = [ options ];
2997
+
2998
+ cldr.on( "get", validateRequiredCldr );
2999
+ if ( timeZone ) {
3000
+ ianaListener = validateRequiredIana( timeZone );
3001
+ cldr.on( "get", ianaListener );
3002
+ }
3003
+ try {
3004
+ pattern = dateExpandPattern( options, cldr );
3005
+ validateOptionsSkeleton( pattern, options.skeleton );
3006
+ properties = dateFormatProperties( pattern, cldr, timeZone );
3007
+ } finally {
3008
+ cldr.off( "get", validateRequiredCldr );
3009
+ if ( ianaListener ) {
3010
+ cldr.off( "get", ianaListener );
3011
+ }
3012
+ }
3013
+
3014
+ // Create needed number formatters.
3015
+ numberFormatters = properties.numberFormatters;
3016
+ delete properties.numberFormatters;
3017
+ for ( pad in numberFormatters ) {
3018
+ numberFormatters[ pad ] = this.numberFormatter({
3019
+ raw: numberFormatters[ pad ]
3020
+ });
3021
+ }
3022
+
3023
+ returnFn = dateToPartsFormatterFn( numberFormatters, properties );
3024
+
3025
+ runtimeBind( args, cldr, returnFn, [ numberFormatters, properties ] );
3026
+
3027
+ return returnFn;
3028
+ };
3029
+
3030
+ /**
3031
+ * .dateParser( options )
3032
+ *
3033
+ * @options [Object] see date/expand_pattern for more info.
3034
+ *
3035
+ * Return a function that parses a string date according to the given `formats` and the
3036
+ * default/instance locale.
3037
+ */
3038
+ Globalize.dateParser =
3039
+ Globalize.prototype.dateParser = function( options ) {
3040
+ var args, cldr, numberParser, parseProperties, pattern, returnFn, timeZone,
3041
+ tokenizerProperties;
3042
+
3043
+ validateParameterTypePlainObject( options, "options" );
3044
+
3045
+ cldr = this.cldr;
3046
+ options = options || {};
3047
+ if ( !optionsHasStyle( options ) ) {
3048
+ options.skeleton = "yMd";
3049
+ }
3050
+
3051
+ validateOptionsPreset( options );
3052
+ validateDefaultLocale( cldr );
3053
+
3054
+ timeZone = options.timeZone;
3055
+ validateParameterTypeString( timeZone, "options.timeZone" );
3056
+
3057
+ args = [ options ];
3058
+
3059
+ try {
3060
+ cldr.on( "get", validateRequiredCldr );
3061
+ if ( timeZone ) {
3062
+ cldr.on( "get", validateRequiredIana( timeZone ) );
3063
+ }
3064
+ pattern = dateExpandPattern( options, cldr );
3065
+ validateOptionsSkeleton( pattern, options.skeleton );
3066
+ tokenizerProperties = dateTokenizerProperties( pattern, cldr, timeZone );
3067
+ parseProperties = dateParseProperties( cldr, timeZone );
3068
+ } finally {
3069
+ cldr.off( "get", validateRequiredCldr );
3070
+ if ( timeZone ) {
3071
+ cldr.off( "get", validateRequiredIana( timeZone ) );
3072
+ }
3073
+ }
3074
+ numberParser = this.numberParser({ raw: "0" });
3075
+
3076
+ returnFn = dateParserFn( numberParser, parseProperties, tokenizerProperties );
3077
+
3078
+ runtimeBind( args, cldr, returnFn, [ numberParser, parseProperties, tokenizerProperties ] );
3079
+
3080
+ return returnFn;
3081
+ };
3082
+
3083
+ /**
3084
+ * .formatDate( value, options )
3085
+ *
3086
+ * @value [Date]
3087
+ *
3088
+ * @options [Object] see date/expand_pattern for more info.
3089
+ *
3090
+ * Formats a date or number according to the given options string and the default/instance locale.
3091
+ */
3092
+ Globalize.formatDate =
3093
+ Globalize.prototype.formatDate = function( value, options ) {
3094
+ validateParameterPresence( value, "value" );
3095
+ validateParameterTypeDate( value, "value" );
3096
+
3097
+ return this.dateFormatter( options )( value );
3098
+ };
3099
+
3100
+ /**
3101
+ * .formatDateToParts( value, options )
3102
+ *
3103
+ * @value [Date]
3104
+ *
3105
+ * @options [Object] see date/expand_pattern for more info.
3106
+ *
3107
+ * Formats a date or number to parts according to the given options and the default/instance locale.
3108
+ */
3109
+ Globalize.formatDateToParts =
3110
+ Globalize.prototype.formatDateToParts = function( value, options ) {
3111
+ validateParameterPresence( value, "value" );
3112
+ validateParameterTypeDate( value, "value" );
3113
+
3114
+ return this.dateToPartsFormatter( options )( value );
3115
+ };
3116
+
3117
+ /**
3118
+ * .parseDate( value, options )
3119
+ *
3120
+ * @value [String]
3121
+ *
3122
+ * @options [Object] see date/expand_pattern for more info.
3123
+ *
3124
+ * Return a Date instance or null.
3125
+ */
3126
+ Globalize.parseDate =
3127
+ Globalize.prototype.parseDate = function( value, options ) {
3128
+ validateParameterPresence( value, "value" );
3129
+ validateParameterTypeString( value, "value" );
3130
+
3131
+ return this.dateParser( options )( value );
3132
+ };
3133
+
3134
+ return Globalize;
3135
+
3136
+
3137
+
3138
+
3139
+ }));