[483] | 1 | define([ |
---|
| 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 | |
---|
| 17 | var exports = { |
---|
| 18 | // summary: |
---|
| 19 | // This modules defines dojo/date/locale, localization methods for Date. |
---|
| 20 | }; |
---|
| 21 | lang.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 | /*===== |
---|
| 178 | var __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 | |
---|
| 200 | exports._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 | |
---|
| 222 | exports.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 | |
---|
| 265 | exports.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 | |
---|
| 272 | exports._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 | |
---|
| 294 | exports.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 | |
---|
| 498 | function _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 | |
---|
| 523 | function _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 = '.+?'; // match anything including spaces until the first pattern delimiter is found such as a comma or space |
---|
| 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 | |
---|
| 604 | var _customFormats = []; |
---|
| 605 | exports.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 | |
---|
| 620 | exports._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 | |
---|
| 629 | exports.addCustomFormats(module.id.replace(/\/date\/locale$/, ".cldr"),"gregorian"); |
---|
| 630 | |
---|
| 631 | exports.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 | |
---|
| 659 | exports.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 | |
---|
| 674 | exports._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 | |
---|
| 680 | exports._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 | |
---|
| 693 | return exports; |
---|
| 694 | }); |
---|