]> git.wh0rd.org - tt-rss.git/blame - lib/dojo/date/locale.js.uncompressed.js
modify dojo rebuild script to remove uncompressed files
[tt-rss.git] / lib / dojo / date / locale.js.uncompressed.js
CommitLineData
f0cfe83e
AD
1define("dojo/date/locale", [
2 "../_base/lang",
3 "../_base/array",
4 "../date",
5 /*===== "../_base/declare", =====*/
6 "../cldr/supplemental",
7 "../i18n",
8 "../regexp",
9 "../string",
10 "../i18n!../cldr/nls/gregorian",
11 "module"
12], function(lang, array, date, /*===== declare, =====*/ supplemental, i18n, regexp, string, gregorian, module){
13
14// module:
15// dojo/date/locale
16
17var exports = {
18 // summary:
19 // This modules defines dojo/date/locale, localization methods for Date.
20};
21lang.setObject(module.id.replace(/\//g, "."), exports);
22
23// Localization methods for Date. Honor local customs using locale-dependent dojo.cldr data.
24
25// Load the bundles containing localization information for
26// names and formats
27
28//NOTE: Everything in this module assumes Gregorian calendars.
29// Other calendars will be implemented in separate modules.
30
31 // Format a pattern without literals
32 function formatPattern(dateObject, bundle, options, pattern){
33 return pattern.replace(/([a-z])\1*/ig, function(match){
34 var s, pad,
35 c = match.charAt(0),
36 l = match.length,
37 widthList = ["abbr", "wide", "narrow"];
38 switch(c){
39 case 'G':
40 s = bundle[(l < 4) ? "eraAbbr" : "eraNames"][dateObject.getFullYear() < 0 ? 0 : 1];
41 break;
42 case 'y':
43 s = dateObject.getFullYear();
44 switch(l){
45 case 1:
46 break;
47 case 2:
48 if(!options.fullYear){
49 s = String(s); s = s.substr(s.length - 2);
50 break;
51 }
52 // fallthrough
53 default:
54 pad = true;
55 }
56 break;
57 case 'Q':
58 case 'q':
59 s = Math.ceil((dateObject.getMonth()+1)/3);
60// switch(l){
61// case 1: case 2:
62 pad = true;
63// break;
64// case 3: case 4: // unimplemented
65// }
66 break;
67 case 'M':
68 case 'L':
69 var m = dateObject.getMonth();
70 if(l<3){
71 s = m+1; pad = true;
72 }else{
73 var propM = [
74 "months",
75 c == 'L' ? "standAlone" : "format",
76 widthList[l-3]
77 ].join("-");
78 s = bundle[propM][m];
79 }
80 break;
81 case 'w':
82 var firstDay = 0;
83 s = exports._getWeekOfYear(dateObject, firstDay); pad = true;
84 break;
85 case 'd':
86 s = dateObject.getDate(); pad = true;
87 break;
88 case 'D':
89 s = exports._getDayOfYear(dateObject); pad = true;
90 break;
91 case 'e':
92 case 'c':
93 var d = dateObject.getDay();
94 if(l<2){
95 s = (d - supplemental.getFirstDayOfWeek(options.locale) + 8) % 7
96 break;
97 }
98 // fallthrough
99 case 'E':
100 d = dateObject.getDay();
101 if(l<3){
102 s = d+1; pad = true;
103 }else{
104 var propD = [
105 "days",
106 c == 'c' ? "standAlone" : "format",
107 widthList[l-3]
108 ].join("-");
109 s = bundle[propD][d];
110 }
111 break;
112 case 'a':
113 var timePeriod = dateObject.getHours() < 12 ? 'am' : 'pm';
114 s = options[timePeriod] || bundle['dayPeriods-format-wide-' + timePeriod];
115 break;
116 case 'h':
117 case 'H':
118 case 'K':
119 case 'k':
120 var h = dateObject.getHours();
121 // strange choices in the date format make it impossible to write this succinctly
122 switch (c){
123 case 'h': // 1-12
124 s = (h % 12) || 12;
125 break;
126 case 'H': // 0-23
127 s = h;
128 break;
129 case 'K': // 0-11
130 s = (h % 12);
131 break;
132 case 'k': // 1-24
133 s = h || 24;
134 break;
135 }
136 pad = true;
137 break;
138 case 'm':
139 s = dateObject.getMinutes(); pad = true;
140 break;
141 case 's':
142 s = dateObject.getSeconds(); pad = true;
143 break;
144 case 'S':
145 s = Math.round(dateObject.getMilliseconds() * Math.pow(10, l-3)); pad = true;
146 break;
147 case 'v': // FIXME: don't know what this is. seems to be same as z?
148 case 'z':
149 // We only have one timezone to offer; the one from the browser
150 s = exports._getZone(dateObject, true, options);
151 if(s){break;}
152 l=4;
153 // fallthrough... use GMT if tz not available
154 case 'Z':
155 var offset = exports._getZone(dateObject, false, options);
156 var tz = [
157 (offset<=0 ? "+" : "-"),
158 string.pad(Math.floor(Math.abs(offset)/60), 2),
159 string.pad(Math.abs(offset)% 60, 2)
160 ];
161 if(l==4){
162 tz.splice(0, 0, "GMT");
163 tz.splice(3, 0, ":");
164 }
165 s = tz.join("");
166 break;
167// case 'Y': case 'u': case 'W': case 'F': case 'g': case 'A':
168// console.log(match+" modifier unimplemented");
169 default:
170 throw new Error("dojo.date.locale.format: invalid pattern char: "+pattern);
171 }
172 if(pad){ s = string.pad(s, l); }
173 return s;
174 });
175 }
176
177/*=====
178var __FormatOptions = exports.__FormatOptions = declare(null, {
179 // selector: String
180 // choice of 'time','date' (default: date and time)
181 // formatLength: String
182 // choice of long, short, medium or full (plus any custom additions). Defaults to 'short'
183 // datePattern:String
184 // override pattern with this string
185 // timePattern:String
186 // override pattern with this string
187 // am: String
188 // override strings for am in times
189 // pm: String
190 // override strings for pm in times
191 // locale: String
192 // override the locale used to determine formatting rules
193 // fullYear: Boolean
194 // (format only) use 4 digit years whenever 2 digit years are called for
195 // strict: Boolean
196 // (parse only) strict parsing, off by default
197});
198=====*/
199
200exports._getZone = function(/*Date*/ dateObject, /*boolean*/ getName, /*__FormatOptions?*/ options){
201 // summary:
202 // Returns the zone (or offset) for the given date and options. This
203 // is broken out into a separate function so that it can be overridden
204 // by timezone-aware code.
205 //
206 // dateObject:
207 // the date and/or time being formatted.
208 //
209 // getName:
210 // Whether to return the timezone string (if true), or the offset (if false)
211 //
212 // options:
213 // The options being used for formatting
214 if(getName){
215 return date.getTimezoneName(dateObject);
216 }else{
217 return dateObject.getTimezoneOffset();
218 }
219};
220
221
222exports.format = function(/*Date*/ dateObject, /*__FormatOptions?*/ options){
223 // summary:
224 // Format a Date object as a String, using locale-specific settings.
225 //
226 // description:
227 // Create a string from a Date object using a known localized pattern.
228 // By default, this method formats both date and time from dateObject.
229 // Formatting patterns are chosen appropriate to the locale. Different
230 // formatting lengths may be chosen, with "full" used by default.
231 // Custom patterns may be used or registered with translations using
232 // the dojo/date/locale.addCustomFormats() method.
233 // Formatting patterns are implemented using [the syntax described at
234 // unicode.org](http://www.unicode.org/reports/tr35/tr35-4.html#Date_Format_Patterns)
235 //
236 // dateObject:
237 // the date and/or time to be formatted. If a time only is formatted,
238 // the values in the year, month, and day fields are irrelevant. The
239 // opposite is true when formatting only dates.
240
241 options = options || {};
242
243 var locale = i18n.normalizeLocale(options.locale),
244 formatLength = options.formatLength || 'short',
245 bundle = exports._getGregorianBundle(locale),
246 str = [],
247 sauce = lang.hitch(this, formatPattern, dateObject, bundle, options);
248 if(options.selector == "year"){
249 return _processPattern(bundle["dateFormatItem-yyyy"] || "yyyy", sauce);
250 }
251 var pattern;
252 if(options.selector != "date"){
253 pattern = options.timePattern || bundle["timeFormat-"+formatLength];
254 if(pattern){str.push(_processPattern(pattern, sauce));}
255 }
256 if(options.selector != "time"){
257 pattern = options.datePattern || bundle["dateFormat-"+formatLength];
258 if(pattern){str.push(_processPattern(pattern, sauce));}
259 }
260
261 return str.length == 1 ? str[0] : bundle["dateTimeFormat-"+formatLength].replace(/\'/g,'').replace(/\{(\d+)\}/g,
262 function(match, key){ return str[key]; }); // String
263};
264
265exports.regexp = function(/*__FormatOptions?*/ options){
266 // summary:
267 // Builds the regular needed to parse a localized date
268
269 return exports._parseInfo(options).regexp; // String
270};
271
272exports._parseInfo = function(/*__FormatOptions?*/ options){
273 options = options || {};
274 var locale = i18n.normalizeLocale(options.locale),
275 bundle = exports._getGregorianBundle(locale),
276 formatLength = options.formatLength || 'short',
277 datePattern = options.datePattern || bundle["dateFormat-" + formatLength],
278 timePattern = options.timePattern || bundle["timeFormat-" + formatLength],
279 pattern;
280 if(options.selector == 'date'){
281 pattern = datePattern;
282 }else if(options.selector == 'time'){
283 pattern = timePattern;
284 }else{
285 pattern = bundle["dateTimeFormat-"+formatLength].replace(/\{(\d+)\}/g,
286 function(match, key){ return [timePattern, datePattern][key]; });
287 }
288
289 var tokens = [],
290 re = _processPattern(pattern, lang.hitch(this, _buildDateTimeRE, tokens, bundle, options));
291 return {regexp: re, tokens: tokens, bundle: bundle};
292};
293
294exports.parse = function(/*String*/ value, /*__FormatOptions?*/ options){
295 // summary:
296 // Convert a properly formatted string to a primitive Date object,
297 // using locale-specific settings.
298 //
299 // description:
300 // Create a Date object from a string using a known localized pattern.
301 // By default, this method parses looking for both date and time in the string.
302 // Formatting patterns are chosen appropriate to the locale. Different
303 // formatting lengths may be chosen, with "full" used by default.
304 // Custom patterns may be used or registered with translations using
305 // the dojo/date/locale.addCustomFormats() method.
306 //
307 // Formatting patterns are implemented using [the syntax described at
308 // unicode.org](http://www.unicode.org/reports/tr35/tr35-4.html#Date_Format_Patterns)
309 // When two digit years are used, a century is chosen according to a sliding
310 // window of 80 years before and 20 years after present year, for both `yy` and `yyyy` patterns.
311 // year < 100CE requires strict mode.
312 //
313 // value:
314 // A string representation of a date
315
316 // remove non-printing bidi control chars from input and pattern
317 var controlChars = /[\u200E\u200F\u202A\u202E]/g,
318 info = exports._parseInfo(options),
319 tokens = info.tokens, bundle = info.bundle,
320 re = new RegExp("^" + info.regexp.replace(controlChars, "") + "$",
321 info.strict ? "" : "i"),
322 match = re.exec(value && value.replace(controlChars, ""));
323
324 if(!match){ return null; } // null
325
326 var widthList = ['abbr', 'wide', 'narrow'],
327 result = [1970,0,1,0,0,0,0], // will get converted to a Date at the end
328 amPm = "",
329 valid = array.every(match, function(v, i){
330 if(!i){return true;}
331 var token = tokens[i-1],
332 l = token.length,
333 c = token.charAt(0);
334 switch(c){
335 case 'y':
336 if(l != 2 && options.strict){
337 //interpret year literally, so '5' would be 5 A.D.
338 result[0] = v;
339 }else{
340 if(v<100){
341 v = Number(v);
342 //choose century to apply, according to a sliding window
343 //of 80 years before and 20 years after present year
344 var year = '' + new Date().getFullYear(),
345 century = year.substring(0, 2) * 100,
346 cutoff = Math.min(Number(year.substring(2, 4)) + 20, 99);
347 result[0] = (v < cutoff) ? century + v : century - 100 + v;
348 }else{
349 //we expected 2 digits and got more...
350 if(options.strict){
351 return false;
352 }
353 //interpret literally, so '150' would be 150 A.D.
354 //also tolerate '1950', if 'yyyy' input passed to 'yy' format
355 result[0] = v;
356 }
357 }
358 break;
359 case 'M':
360 case 'L':
361 if(l>2){
362 var months = bundle['months-' +
363 (c == 'L' ? 'standAlone' : 'format') +
364 '-' + widthList[l-3]].concat();
365 if(!options.strict){
366 //Tolerate abbreviating period in month part
367 //Case-insensitive comparison
368 v = v.replace(".","").toLowerCase();
369 months = array.map(months, function(s){ return s.replace(".","").toLowerCase(); } );
370 }
371 v = array.indexOf(months, v);
372 if(v == -1){
373// console.log("dojo/date/locale.parse: Could not parse month name: '" + v + "'.");
374 return false;
375 }
376 }else{
377 v--;
378 }
379 result[1] = v;
380 break;
381 case 'E':
382 case 'e':
383 case 'c':
384 var days = bundle['days-' +
385 (c == 'c' ? 'standAlone' : 'format') +
386 '-' + widthList[l-3]].concat();
387 if(!options.strict){
388 //Case-insensitive comparison
389 v = v.toLowerCase();
390 days = array.map(days, function(d){return d.toLowerCase();});
391 }
392 v = array.indexOf(days, v);
393 if(v == -1){
394// console.log("dojo/date/locale.parse: Could not parse weekday name: '" + v + "'.");
395 return false;
396 }
397
398 //TODO: not sure what to actually do with this input,
399 //in terms of setting something on the Date obj...?
400 //without more context, can't affect the actual date
401 //TODO: just validate?
402 break;
403 case 'D':
404 result[1] = 0;
405 // fallthrough...
406 case 'd':
407 result[2] = v;
408 break;
409 case 'a': //am/pm
410 var am = options.am || bundle['dayPeriods-format-wide-am'],
411 pm = options.pm || bundle['dayPeriods-format-wide-pm'];
412 if(!options.strict){
413 var period = /\./g;
414 v = v.replace(period,'').toLowerCase();
415 am = am.replace(period,'').toLowerCase();
416 pm = pm.replace(period,'').toLowerCase();
417 }
418 if(options.strict && v != am && v != pm){
419// console.log("dojo/date/locale.parse: Could not parse am/pm part.");
420 return false;
421 }
422
423 // we might not have seen the hours field yet, so store the state and apply hour change later
424 amPm = (v == pm) ? 'p' : (v == am) ? 'a' : '';
425 break;
426 case 'K': //hour (1-24)
427 if(v == 24){ v = 0; }
428 // fallthrough...
429 case 'h': //hour (1-12)
430 case 'H': //hour (0-23)
431 case 'k': //hour (0-11)
432 //TODO: strict bounds checking, padding
433 if(v > 23){
434// console.log("dojo/date/locale.parse: Illegal hours value");
435 return false;
436 }
437
438 //in the 12-hour case, adjusting for am/pm requires the 'a' part
439 //which could come before or after the hour, so we will adjust later
440 result[3] = v;
441 break;
442 case 'm': //minutes
443 result[4] = v;
444 break;
445 case 's': //seconds
446 result[5] = v;
447 break;
448 case 'S': //milliseconds
449 result[6] = v;
450// break;
451// case 'w':
452//TODO var firstDay = 0;
453// default:
454//TODO: throw?
455// console.log("dojo/date/locale.parse: unsupported pattern char=" + token.charAt(0));
456 }
457 return true;
458 });
459
460 var hours = +result[3];
461 if(amPm === 'p' && hours < 12){
462 result[3] = hours + 12; //e.g., 3pm -> 15
463 }else if(amPm === 'a' && hours == 12){
464 result[3] = 0; //12am -> 0
465 }
466
467 //TODO: implement a getWeekday() method in order to test
468 //validity of input strings containing 'EEE' or 'EEEE'...
469
470 var dateObject = new Date(result[0], result[1], result[2], result[3], result[4], result[5], result[6]); // Date
471 if(options.strict){
472 dateObject.setFullYear(result[0]);
473 }
474
475 // Check for overflow. The Date() constructor normalizes things like April 32nd...
476 //TODO: why isn't this done for times as well?
477 var allTokens = tokens.join(""),
478 dateToken = allTokens.indexOf('d') != -1,
479 monthToken = allTokens.indexOf('M') != -1;
480
481 if(!valid ||
482 (monthToken && dateObject.getMonth() > result[1]) ||
483 (dateToken && dateObject.getDate() > result[2])){
484 return null;
485 }
486
487 // Check for underflow, due to DST shifts. See #9366
488 // This assumes a 1 hour dst shift correction at midnight
489 // We could compare the timezone offset after the shift and add the difference instead.
490 if((monthToken && dateObject.getMonth() < result[1]) ||
491 (dateToken && dateObject.getDate() < result[2])){
492 dateObject = date.add(dateObject, "hour", 1);
493 }
494
495 return dateObject; // Date
496};
497
498function _processPattern(pattern, applyPattern, applyLiteral, applyAll){
499 //summary: Process a pattern with literals in it
500
501 // Break up on single quotes, treat every other one as a literal, except '' which becomes '
502 var identity = function(x){return x;};
503 applyPattern = applyPattern || identity;
504 applyLiteral = applyLiteral || identity;
505 applyAll = applyAll || identity;
506
507 //split on single quotes (which escape literals in date format strings)
508 //but preserve escaped single quotes (e.g., o''clock)
509 var chunks = pattern.match(/(''|[^'])+/g),
510 literal = pattern.charAt(0) == "'";
511
512 array.forEach(chunks, function(chunk, i){
513 if(!chunk){
514 chunks[i]='';
515 }else{
516 chunks[i]=(literal ? applyLiteral : applyPattern)(chunk.replace(/''/g, "'"));
517 literal = !literal;
518 }
519 });
520 return applyAll(chunks.join(''));
521}
522
523function _buildDateTimeRE(tokens, bundle, options, pattern){
524 pattern = regexp.escapeString(pattern);
525 if(!options.strict){ pattern = pattern.replace(" a", " ?a"); } // kludge to tolerate no space before am/pm
526 return pattern.replace(/([a-z])\1*/ig, function(match){
527 // Build a simple regexp. Avoid captures, which would ruin the tokens list
528 var s,
529 c = match.charAt(0),
530 l = match.length,
531 p2 = '', p3 = '';
532 if(options.strict){
533 if(l > 1){ p2 = '0' + '{'+(l-1)+'}'; }
534 if(l > 2){ p3 = '0' + '{'+(l-2)+'}'; }
535 }else{
536 p2 = '0?'; p3 = '0{0,2}';
537 }
538 switch(c){
539 case 'y':
540 s = '\\d{2,4}';
541 break;
542 case 'M':
543 case 'L':
544 s = (l>2) ? '\\S+?' : '1[0-2]|'+p2+'[1-9]';
545 break;
546 case 'D':
547 s = '[12][0-9][0-9]|3[0-5][0-9]|36[0-6]|'+p2+'[1-9][0-9]|'+p3+'[1-9]';
548 break;
549 case 'd':
550 s = '3[01]|[12]\\d|'+p2+'[1-9]';
551 break;
552 case 'w':
553 s = '[1-4][0-9]|5[0-3]|'+p2+'[1-9]';
554 break;
555 case 'E':
556 case 'e':
557 case 'c':
558 s = '\\S+';
559 break;
560 case 'h': //hour (1-12)
561 s = '1[0-2]|'+p2+'[1-9]';
562 break;
563 case 'k': //hour (0-11)
564 s = '1[01]|'+p2+'\\d';
565 break;
566 case 'H': //hour (0-23)
567 s = '1\\d|2[0-3]|'+p2+'\\d';
568 break;
569 case 'K': //hour (1-24)
570 s = '1\\d|2[0-4]|'+p2+'[1-9]';
571 break;
572 case 'm':
573 case 's':
574 s = '[0-5]\\d';
575 break;
576 case 'S':
577 s = '\\d{'+l+'}';
578 break;
579 case 'a':
580 var am = options.am || bundle['dayPeriods-format-wide-am'],
581 pm = options.pm || bundle['dayPeriods-format-wide-pm'];
582 s = am + '|' + pm;
583 if(!options.strict){
584 if(am != am.toLowerCase()){ s += '|' + am.toLowerCase(); }
585 if(pm != pm.toLowerCase()){ s += '|' + pm.toLowerCase(); }
586 if(s.indexOf('.') != -1){ s += '|' + s.replace(/\./g, ""); }
587 }
588 s = s.replace(/\./g, "\\.");
589 break;
590 default:
591 // case 'v':
592 // case 'z':
593 // case 'Z':
594 s = ".*";
595// console.log("parse of date format, pattern=" + pattern);
596 }
597
598 if(tokens){ tokens.push(match); }
599
600 return "(" + s + ")"; // add capture
601 }).replace(/[\xa0 ]/g, "[\\s\\xa0]"); // normalize whitespace. Need explicit handling of \xa0 for IE.
602}
603
604var _customFormats = [];
605exports.addCustomFormats = function(/*String*/ packageName, /*String*/ bundleName){
606 // summary:
607 // Add a reference to a bundle containing localized custom formats to be
608 // used by date/time formatting and parsing routines.
609 //
610 // description:
611 // The user may add custom localized formats where the bundle has properties following the
612 // same naming convention used by dojo.cldr: `dateFormat-xxxx` / `timeFormat-xxxx`
613 // The pattern string should match the format used by the CLDR.
614 // See dojo/date/locale.format() for details.
615 // The resources must be loaded by dojo.requireLocalization() prior to use
616
617 _customFormats.push({pkg:packageName,name:bundleName});
618};
619
620exports._getGregorianBundle = function(/*String*/ locale){
621 var gregorian = {};
622 array.forEach(_customFormats, function(desc){
623 var bundle = i18n.getLocalization(desc.pkg, desc.name, locale);
624 gregorian = lang.mixin(gregorian, bundle);
625 }, this);
626 return gregorian; /*Object*/
627};
628
629exports.addCustomFormats(module.id.replace(/\/date\/locale$/, ".cldr"),"gregorian");
630
631exports.getNames = function(/*String*/ item, /*String*/ type, /*String?*/ context, /*String?*/ locale){
632 // summary:
633 // Used to get localized strings from dojo.cldr for day or month names.
634 //
635 // item:
636 // 'months' || 'days'
637 // type:
638 // 'wide' || 'abbr' || 'narrow' (e.g. "Monday", "Mon", or "M" respectively, in English)
639 // context:
640 // 'standAlone' || 'format' (default)
641 // locale:
642 // override locale used to find the names
643
644 var label,
645 lookup = exports._getGregorianBundle(locale),
646 props = [item, context, type];
647 if(context == 'standAlone'){
648 var key = props.join('-');
649 label = lookup[key];
650 // Fall back to 'format' flavor of name
651 if(label[0] == 1){ label = undefined; } // kludge, in the absence of real aliasing support in dojo.cldr
652 }
653 props[1] = 'format';
654
655 // return by copy so changes won't be made accidentally to the in-memory model
656 return (label || lookup[props.join('-')]).concat(); /*Array*/
657};
658
659exports.isWeekend = function(/*Date?*/ dateObject, /*String?*/ locale){
660 // summary:
661 // Determines if the date falls on a weekend, according to local custom.
662
663 var weekend = supplemental.getWeekend(locale),
664 day = (dateObject || new Date()).getDay();
665 if(weekend.end < weekend.start){
666 weekend.end += 7;
667 if(day < weekend.start){ day += 7; }
668 }
669 return day >= weekend.start && day <= weekend.end; // Boolean
670};
671
672// These are used only by format and strftime. Do they need to be public? Which module should they go in?
673
674exports._getDayOfYear = function(/*Date*/ dateObject){
675 // summary:
676 // gets the day of the year as represented by dateObject
677 return date.difference(new Date(dateObject.getFullYear(), 0, 1, dateObject.getHours()), dateObject) + 1; // Number
678};
679
680exports._getWeekOfYear = function(/*Date*/ dateObject, /*Number*/ firstDayOfWeek){
681 if(arguments.length == 1){ firstDayOfWeek = 0; } // Sunday
682
683 var firstDayOfYear = new Date(dateObject.getFullYear(), 0, 1).getDay(),
684 adj = (firstDayOfYear - firstDayOfWeek + 7) % 7,
685 week = Math.floor((exports._getDayOfYear(dateObject) + adj - 1) / 7);
686
687 // if year starts on the specified day, start counting weeks at 1
688 if(firstDayOfYear == firstDayOfWeek){ week++; }
689
690 return week; // Number
691};
692
693return exports;
694});