1 /* 2 * DateFmt.js - Date formatter definition 3 * 4 * Copyright © 2012-2015, 2018, JEDLSoft 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * 16 * See the License for the specific language governing permissions and 17 * limitations under the License. 18 */ 19 20 // !data dateformats sysres 21 22 var ilib = require("./ilib.js"); 23 var Utils = require("./Utils.js"); 24 var JSUtils = require("./JSUtils.js"); 25 26 var Locale = require("./Locale.js"); 27 var LocaleInfo = require("./LocaleInfo.js"); 28 29 var IDate = require("./IDate.js"); 30 var DateFactory = require("./DateFactory.js"); 31 var CalendarFactory = require("./CalendarFactory.js"); 32 33 var ResBundle = require("./ResBundle.js"); 34 var TimeZone = require("./TimeZone.js"); 35 var GregorianCal = require("./GregorianCal.js"); 36 37 var ISet = require("./ISet.js"); 38 39 /** 40 * @class 41 * Create a new date formatter instance. The date formatter is immutable once 42 * it is created, but can format as many different dates as needed with the same 43 * options. Create different date formatter instances for different purposes 44 * and then keep them cached for use later if you have more than one date to 45 * format.<p> 46 * 47 * The options may contain any of the following properties: 48 * 49 * <ul> 50 * <li><i>locale</i> - locale to use when formatting the date/time. If the locale is 51 * not specified, then the default locale of the app or web page will be used. 52 * 53 * <li><i>calendar</i> - the type of calendar to use for this format. The value should 54 * be a sting containing the name of the calendar. Currently, the supported 55 * types are "gregorian", "julian", "arabic", "hebrew", or "chinese". If the 56 * calendar is not specified, then the default calendar for the locale is used. When the 57 * calendar type is specified, then the format method must be called with an instance of 58 * the appropriate date type. (eg. Gregorian calendar means that the format method must 59 * be called with a GregDate instance.) 60 * 61 * <li><i>timezone</i> - time zone to use when formatting times. This may be a time zone 62 * instance or a time zone specifier from the IANA list of time zone database names 63 * (eg. "America/Los_Angeles"), 64 * the string "local", or a string specifying the offset in RFC 822 format. The IANA 65 * list of time zone names can be viewed at 66 * <a href="http://en.wikipedia.org/wiki/List_of_tz_database_time_zones">this page</a>. 67 * If the time zone is given as "local", the offset from UTC as given by 68 * the Javascript system is used. If the offset is given as an RFC 822 style offset 69 * specifier, it will parse that string and use the resulting offset. If the time zone 70 * is not specified, the 71 * default time zone for the locale is used. If both the date object and this formatter 72 * instance contain time zones and those time zones are different from each other, the 73 * formatter will calculate the offset between the time zones and subtract it from the 74 * date before formatting the result for the current time zone. The theory is that a date 75 * object that contains a time zone specifies a specific instant in time that is valid 76 * around the world, whereas a date object without one is a local time and can only be 77 * used for doing things in the local time zone of the user. 78 * 79 * <li><i>type</i> - Specify whether this formatter should format times only, dates only, or 80 * both times and dates together. Valid values are "time", "date", and "datetime". Note that 81 * in some locales, the standard format uses the order "time followed by date" and in others, 82 * the order is exactly opposite, so it is better to create a single "datetime" formatter 83 * than it is to create a time formatter and a date formatter separately and concatenate the 84 * results. A "datetime" formatter will get the order correct for the locale.<p> 85 * 86 * The default type if none is specified in with the type option is "date". 87 * 88 * <li><i>length</i> - Specify the length of the format to use. The length is the approximate size of the 89 * formatted string. 90 * 91 * <ul> 92 * <li><i>short</i> - use a short representation of the time. This is the most compact format possible for the locale. 93 * <li><i>medium</i> - use a medium length representation of the time. This is a slightly longer format. 94 * <li><i>long</i> - use a long representation of the time. This is a fully specified format, but some of the textual 95 * components may still be abbreviated 96 * <li><i>full</i> - use a full representation of the time. This is a fully specified format where all the textual 97 * components are spelled out completely 98 * </ul> 99 * 100 * eg. The "short" format for an en_US date may be "MM/dd/yy", whereas the long format might be "d MMM, yyyy". In the long 101 * format, the month name is textual instead of numeric and is longer, the year is 4 digits instead of 2, and the format 102 * contains slightly more spaces and formatting characters.<p> 103 * 104 * Note that the length parameter does not specify which components are to be formatted. Use the "date" and the "time" 105 * properties to specify the components. Also, very few of the components of a time format differ according to the length, 106 * so this property has little to no affect on time formatting. 107 * 108 * <li><i>date</i> - This property tells 109 * which components of a date format to use. For example, 110 * sometimes you may wish to format a date that only contains the month and date 111 * without the year, such as when displaying a person's yearly birthday. The value 112 * of this property allows you to specify only those components you want to see in the 113 * final output, ordered correctly for the locale. <p> 114 * 115 * Valid values are: 116 * 117 * <ul> 118 * <li><i>dmwy</i> - format all components, weekday, date, month, and year 119 * <li><i>dmy</i> - format only date, month, and year 120 * <li><i>dmw</i> - format only weekday, date, and month 121 * <li><i>dm</i> - format only date and month 122 * <li><i>my</i> - format only month and year 123 * <li><i>dw</i> - format only the weekday and date 124 * <li><i>d</i> - format only the date 125 * <li><i>m</i> - format only the month, in numbers for shorter lengths, and letters for 126 * longer lengths 127 * <li><i>n</i> - format only the month, in letters only for all lengths 128 * <li><i>y</i> - format only the year 129 * </ul> 130 * Default components, if this property is not specified, is "dmy". This property may be specified 131 * but has no affect if the current formatter is for times only.<p> 132 * 133 * As of ilib 12.0, you can now pass ICU style skeletons in this option similar to the ones you 134 * get from <a href="http://icu-project.org/apiref/icu4c432/classDateTimePatternGenerator.html#aa30c251609c1eea5ad60c95fc497251e">DateTimePatternGenerator.getSkeleton()</a>. 135 * It will not extract the length from the skeleton so you still need to pass the length property, 136 * but it will extract the date components. 137 * 138 * <li><i>time</i> - This property gives which components of a time format to use. The time will be formatted 139 * correctly for the locale with only the time components requested. For example, a clock might only display 140 * the hour and minute and not need the seconds or the am/pm component. In this case, the time property should be set 141 * to "hm". <p> 142 * 143 * Valid values for this property are: 144 * 145 * <ul> 146 * <li><i>ahmsz</i> - format the hours, minutes, seconds, am/pm (if using a 12 hour clock), and the time zone 147 * <li><i>ahms</i> - format the hours, minutes, seconds, and am/pm (if using a 12 hour clock) 148 * <li><i>hmsz</i> - format the hours, minutes, seconds, and the time zone 149 * <li><i>hms</i> - format the hours, minutes, and seconds 150 * <li><i>ahmz</i> - format the hours, minutes, am/pm (if using a 12 hour clock), and the time zone 151 * <li><i>ahm</i> - format the hours, minutes, and am/pm (if using a 12 hour clock) 152 * <li><i>hmz</i> - format the hours, minutes, and the time zone 153 * <li><i>ah</i> - format only the hours and am/pm if using a 12 hour clock 154 * <li><i>hm</i> - format only the hours and minutes 155 * <li><i>ms</i> - format only the minutes and seconds 156 * <li><i>h</i> - format only the hours 157 * <li><i>m</i> - format only the minutes 158 * <li><i>s</i> - format only the seconds 159 * </ul> 160 * 161 * If you want to format a length of time instead of a particular instant 162 * in time, use the duration formatter object (DurationFmt) instead because this 163 * formatter is geared towards instants. A date formatter will make sure that each component of the 164 * time is within the normal range 165 * for that component. That is, the minutes will always be between 0 and 59, no matter 166 * what is specified in the date to format. A duration format will allow the number 167 * of minutes to exceed 59 if, for example, you were displaying the length of 168 * a movie of 198 minutes.<p> 169 * 170 * Default value if this property is not specified is "hma".<p> 171 * 172 * As of ilib 12.0, you can now pass ICU style skeletons in this option similar to the ones you 173 * get from <a href="http://icu-project.org/apiref/icu4c432/classDateTimePatternGenerator.html#aa30c251609c1eea5ad60c95fc497251e">DateTimePatternGenerator.getSkeleton()</a>. 174 * It will not extract the length from the skeleton so you still need to pass the length property, 175 * but it will extract the time components. 176 * 177 * <li><i>clock</i> - specify that the time formatter should use a 12 or 24 hour clock. 178 * Valid values are "12" and "24".<p> 179 * 180 * In some locales, both clocks are used. For example, in en_US, the general populace uses 181 * a 12 hour clock with am/pm, but in the US military or in nautical or aeronautical or 182 * scientific writing, it is more common to use a 24 hour clock. This property allows you to 183 * construct a formatter that overrides the default for the locale.<p> 184 * 185 * If this property is not specified, the default is to use the most widely used convention 186 * for the locale. 187 * 188 * <li><i>template</i> - use the given template string as a fixed format when formatting 189 * the date/time. Valid codes to use in a template string are as follows: 190 * 191 * <ul> 192 * <li><i>a</i> - am/pm marker 193 * <li><i>d</i> - 1 or 2 digit date of month, not padded 194 * <li><i>dd</i> - 1 or 2 digit date of month, 0 padded to 2 digits 195 * <li><i>O</i> - ordinal representation of the date of month (eg. "1st", "2nd", etc.) 196 * <li><i>D</i> - 1 to 3 digit day of year 197 * <li><i>DD</i> - 1 to 3 digit day of year, 0 padded to 2 digits 198 * <li><i>DDD</i> - 1 to 3 digit day of year, 0 padded to 3 digits 199 * <li><i>M</i> - 1 or 2 digit month number, not padded 200 * <li><i>MM</i> - 1 or 2 digit month number, 0 padded to 2 digits 201 * <li><i>N</i> - 1 character month name abbreviation 202 * <li><i>NN</i> - 2 character month name abbreviation 203 * <li><i>MMM</i> - 3 character month month name abbreviation 204 * <li><i>MMMM</i> - fully spelled out month name 205 * <li><i>yy</i> - 2 digit year 206 * <li><i>yyyy</i> - 4 digit year 207 * <li><i>E</i> - day-of-week name, abbreviated to a single character 208 * <li><i>EE</i> - day-of-week name, abbreviated to a max of 2 characters 209 * <li><i>EEE</i> - day-of-week name, abbreviated to a max of 3 characters 210 * <li><i>EEEE</i> - day-of-week name fully spelled out 211 * <li><i>G</i> - era designator 212 * <li><i>w</i> - week number in year 213 * <li><i>ww</i> - week number in year, 0 padded to 2 digits 214 * <li><i>W</i> - week in month 215 * <li><i>h</i> - hour (12 followed by 1 to 11) 216 * <li><i>hh</i> - hour (12, followed by 1 to 11), 0 padded to 2 digits 217 * <li><i>k</i> - hour (1 to 24) 218 * <li><i>kk</i> - hour (1 to 24), 0 padded to 2 digits 219 * <li><i>H</i> - hour (0 to 23) 220 * <li><i>HH</i> - hour (0 to 23), 0 padded to 2 digits 221 * <li><i>K</i> - hour (0 to 11) 222 * <li><i>KK</i> - hour (0 to 11), 0 padded to 2 digits 223 * <li><i>m</i> - minute in hour 224 * <li><i>mm</i> - minute in hour, 0 padded to 2 digits 225 * <li><i>s</i> - second in minute 226 * <li><i>ss</i> - second in minute, 0 padded to 2 digits 227 * <li><i>S</i> - millisecond (1 to 3 digits) 228 * <li><i>SSS</i> - millisecond, 0 padded to 3 digits 229 * <li><i>z</i> - general time zone 230 * <li><i>Z</i> - RFC 822 time zone 231 * </ul> 232 * 233 * <li><i>useNative</i> - the flag used to determine whether to use the native script settings 234 * for formatting the numbers. 235 * 236 * <li><i>meridiems</i> - string that specifies what style of meridiems to use with this 237 * format. The choices are "default", "gregorian", "ethiopic", and "chinese". The "default" 238 * style is often the simple Gregorian AM/PM, but the actual style is chosen by the locale. 239 * (For almost all locales, the Gregorian AM/PM style is most frequently used.) 240 * The "ethiopic" style uses 5 different meridiems for "morning", "noon", "afternoon", 241 * "evening", and "night". The "chinese" style uses 7 different meridiems corresponding 242 * to the various parts of the day. N.B. Even for the Chinese locales, the default is "gregorian" 243 * when formatting dates in the Gregorian calendar. 244 * 245 * <li><i>onLoad</i> - a callback function to call when the date format object is fully 246 * loaded. When the onLoad option is given, the DateFmt object will attempt to 247 * load any missing locale data using the ilib loader callback. 248 * When the constructor is done (even if the data is already preassembled), the 249 * onLoad function is called with the current instance as a parameter, so this 250 * callback can be used with preassembled or dynamic loading or a mix of the two. 251 * 252 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 253 * asynchronously. If this option is given as "false", then the "onLoad" 254 * callback must be given, as the instance returned from this constructor will 255 * not be usable for a while. 256 * 257 * <li><i>loadParams</i> - an object containing parameters to pass to the 258 * loader callback function when locale data is missing. The parameters are not 259 * interpretted or modified in any way. They are simply passed along. The object 260 * may contain any property/value pairs as long as the calling code is in 261 * agreement with the loader callback function as to what those parameters mean. 262 * </ul> 263 * 264 * Any substring containing letters within single or double quotes will be used 265 * as-is in the final output and will not be interpretted for codes as above.<p> 266 * 267 * Example: a date format in Spanish might be given as: "'El' d. 'de' MMMM", where 268 * the 'El' and the 'de' are left as-is in the output because they are quoted. Typical 269 * output for this example template might be, "El 5. de Mayo". 270 * 271 * The following options will be used when formatting a date/time with an explicit 272 * template: 273 * 274 * <ul> 275 * <li>locale - the locale is only used for 276 * translations of things like month names or day-of-week names. 277 * <li>calendar - used to translate a date instance into date/time component values 278 * that can be formatted into the template 279 * <li>timezone - used to figure out the offset to add or subtract from the time to 280 * get the final time component values 281 * <li>clock - used to figure out whether to format times with a 12 or 24 hour clock. 282 * If this option is specified, it will override the hours portion of a time format. 283 * That is, "hh" is switched with "HH" and "kk" is switched with "KK" as appropriate. 284 * If this option is not specified, the 12/24 code in the template will dictate whether 285 * to use the 12 or 24 clock, and the 12/24 default in the locale will be ignored. 286 * </ul> 287 * 288 * All other options will be ignored and their corresponding getter methods will 289 * return the empty string.<p> 290 * 291 * 292 * @constructor 293 * @param {Object} options options governing the way this date formatter instance works 294 */ 295 var DateFmt = function(options) { 296 var arr, i, bad, c, comps, 297 sync = true, 298 loadParams = undefined; 299 300 this.locale = new Locale(); 301 this.type = "date"; 302 this.length = "s"; 303 this.dateComponents = "dmy"; 304 this.timeComponents = "ahm"; 305 this.meridiems = "default"; 306 307 options = options || {sync: true}; 308 if (options.locale) { 309 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 310 } 311 312 if (options.type) { 313 if (options.type === 'date' || options.type === 'time' || options.type === 'datetime') { 314 this.type = options.type; 315 } 316 } 317 318 if (options.calendar) { 319 this.calName = options.calendar; 320 } 321 322 if (options.length) { 323 if (options.length === 'short' || 324 options.length === 'medium' || 325 options.length === 'long' || 326 options.length === 'full') { 327 // only use the first char to save space in the json files 328 this.length = options.length.charAt(0); 329 } 330 } 331 332 if (options.date) { 333 arr = options.date.split(""); 334 var dateComps = new ISet(); 335 bad = false; 336 for (i = 0; i < arr.length; i++) { 337 c = arr[i].toLowerCase(); 338 if (c === "e") c = "w"; // map ICU -> ilib 339 if (c !== 'd' && c !== 'm' && c !== 'y' && c !== 'w' && c !== 'n') { 340 // ignore time components and the era 341 if (c !== 'h' && c !== 'm' && c !== 's' && c !== 'a' && c !== 'z' && c !== 'g') { 342 bad = true; 343 break; 344 } 345 } else { 346 dateComps.add(c); 347 } 348 } 349 if (!bad) { 350 comps = dateComps.asArray().sort(function (left, right) { 351 return (left < right) ? -1 : ((right < left) ? 1 : 0); 352 }); 353 this.dateComponents = comps.join(""); 354 } 355 } 356 357 if (options.time) { 358 arr = options.time.split(""); 359 var timeComps = new ISet(); 360 this.badTime = false; 361 for (i = 0; i < arr.length; i++) { 362 c = arr[i].toLowerCase(); 363 if (c !== 'h' && c !== 'm' && c !== 's' && c !== 'a' && c !== 'z') { 364 // ignore the date components 365 if (c !== 'd' && c !== 'm' && c !== 'y' && c !== 'w' && c !== 'e' && c !== 'n' && c !== 'g') { 366 this.badTime = true; 367 break; 368 } 369 } else { 370 timeComps.add(c); 371 } 372 } 373 if (!this.badTime) { 374 comps = timeComps.asArray().sort(function (left, right) { 375 return (left < right) ? -1 : ((right < left) ? 1 : 0); 376 }); 377 this.timeComponents = comps.join(""); 378 } 379 } 380 381 if (options.clock && (options.clock === '12' || options.clock === '24')) { 382 this.clock = options.clock; 383 } 384 385 if (options.template) { 386 // many options are not useful when specifying the template directly, so zero 387 // them out. 388 this.type = ""; 389 this.length = ""; 390 this.dateComponents = ""; 391 this.timeComponents = ""; 392 393 this.template = options.template; 394 } 395 396 if (options.timezone) { 397 if (options.timezone instanceof TimeZone) { 398 this.tz = options.timezone; 399 this.timezone = this.tz.getId(); 400 } else { 401 this.timezone = options.timezone; 402 } 403 } 404 405 if (typeof(options.useNative) === 'boolean') { 406 this.useNative = options.useNative; 407 } 408 409 if (typeof(options.meridiems) !== 'undefined' && 410 (options.meridiems === "chinese" || 411 options.meridiems === "gregorian" || 412 options.meridiems === "ethiopic")) { 413 this.meridiems = options.meridiems; 414 } 415 416 if (typeof(options.sync) !== 'undefined') { 417 sync = (options.sync === true); 418 } 419 420 loadParams = options.loadParams; 421 422 new LocaleInfo(this.locale, { 423 sync: sync, 424 loadParams: loadParams, 425 onLoad: ilib.bind(this, function (li) { 426 this.locinfo = li; 427 428 // get the default calendar name from the locale, and if the locale doesn't define 429 // one, use the hard-coded gregorian as the last resort 430 this.calName = this.calName || this.locinfo.getCalendar() || "gregorian"; 431 if (ilib.isDynCode()) { 432 // If we are running in the dynamic code loading assembly of ilib, the following 433 // will attempt to dynamically load the calendar date class for this calendar. If 434 // it doesn't work, this just goes on and it will use Gregorian instead. 435 DateFactory._dynLoadDate(this.calName); 436 } 437 438 CalendarFactory({ 439 type: this.calName, 440 sync: sync, 441 loadParams: loadParams, 442 onLoad: ilib.bind(this, function(cal) { 443 this.cal = cal; 444 445 if (!this.cal) { 446 // can be synchronous 447 this.cal = new GregorianCal(); 448 } 449 if (this.meridiems === "default") { 450 this.meridiems = li.getMeridiemsStyle(); 451 } 452 453 // load the strings used to translate the components 454 new ResBundle({ 455 locale: this.locale, 456 name: "sysres", 457 sync: sync, 458 loadParams: loadParams, 459 onLoad: ilib.bind(this, function (rb) { 460 this.sysres = rb; 461 462 if (!this.tz) { 463 var timezone = options.timezone; 464 if (!timezone && !options.locale) { 465 timezone = "local"; 466 } 467 468 new TimeZone({ 469 locale: this.locale, 470 id: timezone, 471 sync: sync, 472 loadParams: loadParams, 473 onLoad: ilib.bind(this, function(tz) { 474 this.tz = tz; 475 this._init(options); 476 }) 477 }); 478 } else { 479 this._init(options); 480 } 481 }) 482 }); 483 }) 484 }); 485 }) 486 }); 487 }; 488 489 // used in getLength 490 DateFmt.lenmap = { 491 "s": "short", 492 "m": "medium", 493 "l": "long", 494 "f": "full" 495 }; 496 497 DateFmt.defaultFmt = { 498 "gregorian": { 499 "order": "{date} {time}", 500 "date": { 501 "dmwy": "EEE d/MM/yyyy", 502 "dmy": "d/MM/yyyy", 503 "dmw": "EEE d/MM", 504 "dm": "d/MM", 505 "my": "MM/yyyy", 506 "dw": "EEE d", 507 "d": "dd", 508 "m": "MM", 509 "y": "yyyy", 510 "n": "NN", 511 "w": "EEE" 512 }, 513 "time": { 514 "12": "h:mm:ssa", 515 "24": "H:mm:ss" 516 }, 517 "range": { 518 "c00": "{st} - {et}, {sd}/{sm}/{sy}", 519 "c01": "{sd}/{sm} {st} - {ed}/{em} {et}, {sy}", 520 "c02": "{sd}/{sm} {st} - {ed}/{em} {et}, {sy}", 521 "c03": "{sd}/{sm}/{sy} {st} - {ed}/{em}/{ey} {et}", 522 "c10": "{sd}-{ed}/{sm}/{sy}", 523 "c11": "{sd}/{sm} - {ed}/{em} {sy}", 524 "c12": "{sd}/{sm}/{sy} - {ed}/{em}/{ey}", 525 "c20": "{sm}/{sy} - {em}/{ey}", 526 "c30": "{sy} - {ey}" 527 } 528 }, 529 "islamic": "gregorian", 530 "hebrew": "gregorian", 531 "julian": "gregorian", 532 "buddhist": "gregorian", 533 "persian": "gregorian", 534 "persian-algo": "gregorian", 535 "han": "gregorian" 536 }; 537 538 /** 539 * @static 540 * @private 541 */ 542 DateFmt.monthNameLenMap = { 543 "short" : "N", 544 "medium": "NN", 545 "long": "MMM", 546 "full": "MMMM" 547 }; 548 549 /** 550 * @static 551 * @private 552 */ 553 DateFmt.weekDayLenMap = { 554 "short" : "E", 555 "medium": "EE", 556 "long": "EEE", 557 "full": "EEEE" 558 }; 559 560 /** 561 * Return the range of possible meridiems (times of day like "AM" or 562 * "PM") in this date formatter.<p> 563 * 564 * The options may contain any of the following properties: 565 * 566 * <ul> 567 * <li><i>locale</i> - locale to use when formatting the date/time. If the locale is 568 * not specified, then the default locale of the app or web page will be used. 569 * 570 * <li><i>meridiems</i> - string that specifies what style of meridiems to use with this 571 * format. The choices are "default", "gregorian", "ethiopic", and "chinese". The "default" 572 * style is often the simple Gregorian AM/PM, but the actual style is chosen by the locale. 573 * (For almost all locales, the Gregorian AM/PM style is most frequently used.) 574 * The "ethiopic" style uses 5 different meridiems for "morning", "noon", "afternoon", 575 * "evening", and "night". The "chinese" style uses 7 different meridiems corresponding 576 * to the various parts of the day. N.B. Even for the Chinese locales, the default is "gregorian" 577 * when formatting dates in the Gregorian calendar. 578 * </ul> 579 * 580 * @static 581 * @public 582 * @param {Object} options options governing the way this date formatter instance works for getting meridiems range 583 * @return {Array.<{name:string,start:string,end:string}>} 584 */ 585 DateFmt.getMeridiemsRange = function (options) { 586 options = options || {sync: true}; 587 var args = JSUtils.merge({}, options); 588 args.onLoad = function(fmt) { 589 if (typeof(options.onLoad) === "function") { 590 options.onLoad(fmt.getMeridiemsRange()); 591 } 592 }; 593 var fmt = new DateFmt(args); 594 595 return fmt.getMeridiemsRange(); 596 }; 597 598 DateFmt.prototype = { 599 /** 600 * @private 601 * Finish initializing the formatter object 602 */ 603 _init: function(options) { 604 if (typeof (options.sync) === 'undefined') { 605 options.sync = true; 606 } 607 if (!this.template) { 608 Utils.loadData({ 609 object: "DateFmt", 610 locale: this.locale, 611 name: "dateformats.json", 612 sync: options.sync, 613 loadParams: options.loadParams, 614 callback: ilib.bind(this, function (formats) { 615 var spec = this.locale.getSpec().replace(/-/g, '_'); 616 if (!formats) { 617 formats = ilib.data.dateformats || DateFmt.defaultFmt; 618 } 619 620 if (typeof(this.clock) === 'undefined') { 621 // default to the locale instead 622 this.clock = this.locinfo.getClock(); 623 } 624 625 var ret = this; 626 627 if (typeof(options.sync) === "boolean" && !options.sync) { 628 // in async mode, capture the exception and call the callback with "undefined" 629 try { 630 this._initTemplate(formats); 631 this._massageTemplate(); 632 } catch (e) { 633 ret = undefined; 634 } 635 } else { 636 // in sync mode, allow the exception to percolate upwards 637 this._initTemplate(formats); 638 this._massageTemplate(); 639 } 640 641 if (typeof(options.onLoad) === 'function') { 642 options.onLoad(ret); 643 } 644 }) 645 }); 646 } else { 647 this._massageTemplate(); 648 649 if (typeof(options.onLoad) === 'function') { 650 options.onLoad(this); 651 } 652 } 653 }, 654 655 /** 656 * @protected 657 * @param {string|{ 658 * order:(string|{ 659 * s:string, 660 * m:string, 661 * l:string, 662 * f:string 663 * }), 664 * date:Object.<string, (string|{ 665 * s:string, 666 * m:string, 667 * l:string, 668 * f:string 669 * })>, 670 * time:Object.<string,Object.<string,(string|{ 671 * s:string, 672 * m:string, 673 * l:string, 674 * f:string 675 * })>>, 676 * range:Object.<string, (string|{ 677 * s:string, 678 * m:string, 679 * l:string, 680 * f:string 681 * })> 682 * }} formats 683 */ 684 _initTemplate: function (formats) { 685 if (formats[this.calName]) { 686 var name = formats[this.calName]; 687 // may be an alias to another calendar type 688 this.formats = (typeof(name) === "string") ? formats[name] : name; 689 690 this.template = ""; 691 692 switch (this.type) { 693 case "datetime": 694 this.template = (this.formats && this._getLengthFormat(this.formats.order, this.length)) || "{date} {time}"; 695 this.template = this.template.replace("{date}", this._getFormat(this.formats.date, this.dateComponents, this.length) || ""); 696 this.template = this.template.replace("{time}", this._getFormat(this.formats.time[this.clock], this.timeComponents, this.length) || ""); 697 break; 698 case "date": 699 this.template = this._getFormat(this.formats.date, this.dateComponents, this.length); 700 break; 701 case "time": 702 this.template = this._getFormat(this.formats.time[this.clock], this.timeComponents, this.length); 703 break; 704 } 705 706 // calculate what order the components appear in for this locale 707 this.componentOrder = this._getFormat(this.formats.date, "dmy", "l"). 708 replace(/[^dMy]/g, ""). 709 replace(/y+/, "y"). 710 replace(/d+/, "d"). 711 replace(/M+/, "m"); 712 } else { 713 throw "No formats available for calendar " + this.calName + " in locale " + this.locale.toString(); 714 } 715 }, 716 717 /** 718 * @protected 719 */ 720 _massageTemplate: function () { 721 var i; 722 723 if (this.clock && this.template) { 724 // explicitly set the hours to the requested type 725 var temp = ""; 726 switch (this.clock) { 727 case "24": 728 for (i = 0; i < this.template.length; i++) { 729 if (this.template.charAt(i) == "'") { 730 temp += this.template.charAt(i++); 731 while (i < this.template.length && this.template.charAt(i) !== "'") { 732 temp += this.template.charAt(i++); 733 } 734 if (i < this.template.length) { 735 temp += this.template.charAt(i); 736 } 737 } else if (this.template.charAt(i) == 'K') { 738 temp += 'k'; 739 } else if (this.template.charAt(i) == 'h') { 740 temp += 'H'; 741 } else { 742 temp += this.template.charAt(i); 743 } 744 } 745 this.template = temp; 746 break; 747 case "12": 748 for (i = 0; i < this.template.length; i++) { 749 if (this.template.charAt(i) == "'") { 750 temp += this.template.charAt(i++); 751 while (i < this.template.length && this.template.charAt(i) !== "'") { 752 temp += this.template.charAt(i++); 753 } 754 if (i < this.template.length) { 755 temp += this.template.charAt(i); 756 } 757 } else if (this.template.charAt(i) == 'k') { 758 temp += 'K'; 759 } else if (this.template.charAt(i) == 'H') { 760 temp += 'h'; 761 } else { 762 temp += this.template.charAt(i); 763 } 764 } 765 this.template = temp; 766 break; 767 } 768 } 769 770 // tokenize it now for easy formatting 771 this.templateArr = this._tokenize(this.template); 772 773 var digits; 774 // set up the mapping to native or alternate digits if necessary 775 if (typeof(this.useNative) === "boolean") { 776 if (this.useNative) { 777 digits = this.locinfo.getNativeDigits(); 778 if (digits) { 779 this.digits = digits; 780 } 781 } 782 } else if (this.locinfo.getDigitsStyle() === "native") { 783 digits = this.locinfo.getNativeDigits(); 784 if (digits) { 785 this.useNative = true; 786 this.digits = digits; 787 } 788 } 789 }, 790 791 /** 792 * Convert the template into an array of date components separated by formatting chars. 793 * @protected 794 * @param {string} template Format template to tokenize into components 795 * @return {Array.<string>} a tokenized array of date format components 796 */ 797 _tokenize: function (template) { 798 var i = 0, start, ch, letter, arr = []; 799 800 // console.log("_tokenize: tokenizing template " + template); 801 if (template) { 802 while (i < template.length) { 803 ch = template.charAt(i); 804 start = i; 805 if (ch === "'") { 806 // console.log("found quoted string"); 807 i++; 808 // escaped string - push as-is, then dequote later 809 while (i < template.length && template.charAt(i) !== "'") { 810 i++; 811 } 812 if (i < template.length) { 813 i++; // grab the other quote too 814 } 815 } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { 816 letter = template.charAt(i); 817 // console.log("found letters " + letter); 818 while (i < template.length && ch === letter) { 819 ch = template.charAt(++i); 820 } 821 } else { 822 // console.log("found other"); 823 while (i < template.length && ch !== "'" && (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z')) { 824 ch = template.charAt(++i); 825 } 826 } 827 arr.push(template.substring(start,i)); 828 // console.log("start is " + start + " i is " + i + " and substr is " + template.substring(start,i)); 829 } 830 } 831 return arr; 832 }, 833 834 /** 835 * @protected 836 * @param {Object.<string, (string|{s:string,m:string,l:string,f:string})>} obj Object to search 837 * @param {string} components Format components to search 838 * @param {string} length Length of the requested format 839 * @return {string|undefined} the requested format 840 */ 841 _getFormatInternal: function getFormatInternal(obj, components, length) { 842 if (typeof(components) !== 'undefined' && obj && obj[components]) { 843 return this._getLengthFormat(obj[components], length); 844 } 845 return undefined; 846 }, 847 848 // stand-alone of m (month) is l 849 // stand-alone of d (day) is a 850 // stand-alone of w (weekday) is e 851 // stand-alone of y (year) is r 852 _standAlones: { 853 "m": "l", 854 "d": "a", 855 "w": "e", 856 "y": "r" 857 }, 858 859 /** 860 * @protected 861 * @param {Object.<string, (string|{s:string,m:string,l:string,f:string})>} obj Object to search 862 * @param {string} components Format components to search 863 * @param {string} length Length of the requested format 864 * @return {string|undefined} the requested format 865 */ 866 _getFormat: function getFormat(obj, components, length) { 867 // handle some special cases for stand-alone formats 868 if (components && this._standAlones[components]) { 869 var tmp = this._getFormatInternal(obj, this._standAlones[components], length); 870 if (tmp) { 871 return tmp; 872 } 873 } 874 875 // if no stand-alone format is available, fall back to the regular format 876 return this._getFormatInternal(obj, components, length); 877 }, 878 879 /** 880 * @protected 881 * @param {(string|{s:string,m:string,l:string,f:string})} obj Object to search 882 * @param {string} length Length of the requested format 883 * @return {(string|undefined)} the requested format 884 */ 885 _getLengthFormat: function getLengthFormat(obj, length) { 886 if (typeof(obj) === 'string') { 887 return obj; 888 } else if (obj[length]) { 889 return obj[length]; 890 } 891 return undefined; 892 }, 893 894 /** 895 * Return the locale used with this formatter instance. 896 * @return {Locale} the Locale instance for this formatter 897 */ 898 getLocale: function() { 899 return this.locale; 900 }, 901 902 /** 903 * Return the template string that is used to format date/times for this 904 * formatter instance. This will work, even when the template property is not explicitly 905 * given in the options to the constructor. Without the template option, the constructor 906 * will build the appropriate template according to the options and use that template 907 * in the format method. 908 * 909 * @return {string} the format template for this formatter 910 */ 911 getTemplate: function() { 912 return this.template; 913 }, 914 915 /** 916 * Return the order of the year, month, and date components for the current locale.<p> 917 * 918 * When implementing a date input widget in a UI, it would be useful to know what 919 * order to put the year, month, and date input fields so that it conforms to the 920 * user expectations for the locale. This method gives that order by returning a 921 * string that has a single "y", "m", and "d" character in it in the correct 922 * order.<p> 923 * 924 * For example, the return value "ymd" means that this locale formats the year first, 925 * the month second, and the date third, and "mdy" means that the month is first, 926 * the date is second, and the year is third. Four of the 6 possible permutations 927 * of the three letters have at least one locale that uses that ordering, though some 928 * combinations are far more likely than others. The ones that are not used by any 929 * locales are "dym" and "myd", though new locales are still being added to 930 * CLDR frequently, and possible orderings cannot be predicted. Your code should 931 * support all 6 possibilities, just in case. 932 * 933 * @return {string} a string giving the date component order 934 */ 935 getDateComponentOrder: function() { 936 return this.componentOrder; 937 }, 938 939 /** 940 * Return the type of this formatter. The type is a string that has one of the following 941 * values: "time", "date", "datetime". 942 * @return {string} the type of the formatter 943 */ 944 getType: function() { 945 return this.type; 946 }, 947 948 /** 949 * Return the name of the calendar used to format date/times for this 950 * formatter instance. 951 * @return {string} the name of the calendar used by this formatter 952 */ 953 getCalendar: function () { 954 return this.cal.getType(); 955 }, 956 957 /** 958 * Return the length used to format date/times in this formatter. This is either the 959 * value of the length option to the constructor, or the default value. 960 * 961 * @return {string} the length of formats this formatter returns 962 */ 963 getLength: function () { 964 return DateFmt.lenmap[this.length] || ""; 965 }, 966 967 /** 968 * Return the date components that this formatter formats. This is either the 969 * value of the date option to the constructor, or the default value. If this 970 * formatter is a time-only formatter, this method will return the empty 971 * string. The date component letters may be specified in any order in the 972 * constructor, but this method will reorder the given components to a standard 973 * order. 974 * 975 * @return {string} the date components that this formatter formats 976 */ 977 getDateComponents: function () { 978 return this.dateComponents || ""; 979 }, 980 981 /** 982 * Return the time components that this formatter formats. This is either the 983 * value of the time option to the constructor, or the default value. If this 984 * formatter is a date-only formatter, this method will return the empty 985 * string. The time component letters may be specified in any order in the 986 * constructor, but this method will reorder the given components to a standard 987 * order. 988 * 989 * @return {string} the time components that this formatter formats 990 */ 991 getTimeComponents: function () { 992 return this.timeComponents || ""; 993 }, 994 995 /** 996 * Return the time zone used to format date/times for this formatter 997 * instance. 998 * @return {TimeZone} a time zone object that this formatter is formatting for 999 */ 1000 getTimeZone: function () { 1001 return this.tz; 1002 }, 1003 1004 /** 1005 * Return the clock option set in the constructor. If the clock option was 1006 * not given, the default from the locale is returned instead. 1007 * @return {string} "12" or "24" depending on whether this formatter uses 1008 * the 12-hour or 24-hour clock 1009 */ 1010 getClock: function () { 1011 return this.clock || this.locinfo.getClock(); 1012 }, 1013 1014 /** 1015 * Return the meridiems range in current locale. 1016 * @return {Array.<{name:string,start:string,end:string}>} the range of available meridiems 1017 */ 1018 getMeridiemsRange: function () { 1019 var result; 1020 var _getSysString = function (key) { 1021 return (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)).toString(); 1022 }; 1023 1024 switch (this.meridiems) { 1025 case "chinese": 1026 result = [ 1027 { 1028 name: _getSysString.call(this, "azh0"), 1029 start: "00:00", 1030 end: "05:59" 1031 }, 1032 { 1033 name: _getSysString.call(this, "azh1"), 1034 start: "06:00", 1035 end: "08:59" 1036 }, 1037 { 1038 name: _getSysString.call(this, "azh2"), 1039 start: "09:00", 1040 end: "11:59" 1041 }, 1042 { 1043 name: _getSysString.call(this, "azh3"), 1044 start: "12:00", 1045 end: "12:59" 1046 }, 1047 { 1048 name: _getSysString.call(this, "azh4"), 1049 start: "13:00", 1050 end: "17:59" 1051 }, 1052 { 1053 name: _getSysString.call(this, "azh5"), 1054 start: "18:00", 1055 end: "20:59" 1056 }, 1057 { 1058 name: _getSysString.call(this, "azh6"), 1059 start: "21:00", 1060 end: "23:59" 1061 } 1062 ]; 1063 break; 1064 case "ethiopic": 1065 result = [ 1066 { 1067 name: _getSysString.call(this, "a0-ethiopic"), 1068 start: "00:00", 1069 end: "05:59" 1070 }, 1071 { 1072 name: _getSysString.call(this, "a1-ethiopic"), 1073 start: "06:00", 1074 end: "06:00" 1075 }, 1076 { 1077 name: _getSysString.call(this, "a2-ethiopic"), 1078 start: "06:01", 1079 end: "11:59" 1080 }, 1081 { 1082 name: _getSysString.call(this, "a3-ethiopic"), 1083 start: "12:00", 1084 end: "17:59" 1085 }, 1086 { 1087 name: _getSysString.call(this, "a4-ethiopic"), 1088 start: "18:00", 1089 end: "23:59" 1090 } 1091 ]; 1092 break; 1093 default: 1094 result = [ 1095 { 1096 name: _getSysString.call(this, "a0"), 1097 start: "00:00", 1098 end: "11:59" 1099 }, 1100 { 1101 name: _getSysString.call(this, "a1"), 1102 start: "12:00", 1103 end: "23:59" 1104 } 1105 ]; 1106 break; 1107 } 1108 1109 return result; 1110 }, 1111 1112 /** 1113 * @private 1114 */ 1115 _getTemplate: function (prefix, calendar) { 1116 if (calendar !== "gregorian") { 1117 return prefix + "-" + calendar; 1118 } 1119 return prefix; 1120 }, 1121 1122 /** 1123 * Returns an array of the months of the year, formatted to the optional length specified. 1124 * i.e. ...getMonthsOfYear() OR ...getMonthsOfYear({length: "short"}) 1125 * <p> 1126 * The options parameter may contain any of the following properties: 1127 * 1128 * <ul> 1129 * <li><i>length</i> - length of the names of the months being sought. This may be one of 1130 * "short", "medium", "long", or "full" 1131 * <li><i>date</i> - retrieve the names of the months in the date of the given date 1132 * <li><i>year</i> - retrieve the names of the months in the given year. In some calendars, 1133 * the months have different names depending if that year is a leap year or not. 1134 * </ul> 1135 * 1136 * @param {Object=} options an object-literal that contains any of the above properties 1137 * @return {Array} an array of the names of all of the months of the year in the current calendar 1138 */ 1139 getMonthsOfYear: function(options) { 1140 var length = (options && options.length) || this.getLength(), 1141 template = DateFmt.monthNameLenMap[length], 1142 months = [undefined], 1143 date, 1144 monthCount; 1145 1146 if (options) { 1147 if (options.date) { 1148 date = DateFactory._dateToIlib(options.date); 1149 } 1150 1151 if (options.year) { 1152 date = DateFactory({year: options.year, month: 1, day: 1, type: this.cal.getType()}); 1153 } 1154 } 1155 1156 if (!date) { 1157 date = DateFactory({ 1158 calendar: this.cal.getType() 1159 }); 1160 } 1161 1162 monthCount = this.cal.getNumMonths(date.getYears()); 1163 for (var i = 1; i <= monthCount; i++) { 1164 months[i] = this.sysres.getString(this._getTemplate(template + i, this.cal.getType())).toString(); 1165 } 1166 return months; 1167 }, 1168 1169 /** 1170 * Returns an array of the days of the week, formatted to the optional length specified. 1171 * i.e. ...getDaysOfWeek() OR ...getDaysOfWeek({length: "short"}) 1172 * <p> 1173 * The options parameter may contain any of the following properties: 1174 * 1175 * <ul> 1176 * <li><i>length</i> - length of the names of the months being sought. This may be one of 1177 * "short", "medium", "long", or "full" 1178 * </ul> 1179 * @param {Object=} options an object-literal that contains one key 1180 * "length" with the standard length strings 1181 * @return {Array} an array of all of the names of the days of the week 1182 */ 1183 getDaysOfWeek: function(options) { 1184 var length = (options && options.length) || this.getLength(), 1185 template = DateFmt.weekDayLenMap[length], 1186 days = []; 1187 for (var i = 0; i < 7; i++) { 1188 days[i] = this.sysres.getString(this._getTemplate(template + i, this.cal.getType())).toString(); 1189 } 1190 return days; 1191 }, 1192 1193 1194 /** 1195 * Convert this formatter to a string representation by returning the 1196 * format template. This method delegates to getTemplate. 1197 * 1198 * @return {string} the format template 1199 */ 1200 toString: function() { 1201 return this.getTemplate(); 1202 }, 1203 1204 /** 1205 * @private 1206 * Format a date according to a sequence of components. 1207 * @param {IDate} date a date/time object to format 1208 * @param {Array.<string>} templateArr an array of components to format 1209 * @return {string} the formatted date 1210 */ 1211 _formatTemplate: function (date, templateArr) { 1212 var i, key, temp, tz, str = ""; 1213 for (i = 0; i < templateArr.length; i++) { 1214 switch (templateArr[i]) { 1215 case 'd': 1216 str += (date.day || 1); 1217 break; 1218 case 'dd': 1219 str += JSUtils.pad(date.day || "1", 2); 1220 break; 1221 case 'yy': 1222 temp = "" + ((date.year || 0) % 100); 1223 str += JSUtils.pad(temp, 2); 1224 break; 1225 case 'yyyy': 1226 str += JSUtils.pad(date.year || "0", 4); 1227 break; 1228 case 'M': 1229 str += (date.month || 1); 1230 break; 1231 case 'MM': 1232 str += JSUtils.pad(date.month || "1", 2); 1233 break; 1234 case 'h': 1235 temp = (date.hour || 0) % 12; 1236 if (temp == 0) { 1237 temp = "12"; 1238 } 1239 str += temp; 1240 break; 1241 case 'hh': 1242 temp = (date.hour || 0) % 12; 1243 if (temp == 0) { 1244 temp = "12"; 1245 } 1246 str += JSUtils.pad(temp, 2); 1247 break; 1248 /* 1249 case 'j': 1250 temp = (date.hour || 0) % 12 + 1; 1251 str += temp; 1252 break; 1253 case 'jj': 1254 temp = (date.hour || 0) % 12 + 1; 1255 str += JSUtils.pad(temp, 2); 1256 break; 1257 */ 1258 case 'K': 1259 temp = (date.hour || 0) % 12; 1260 str += temp; 1261 break; 1262 case 'KK': 1263 temp = (date.hour || 0) % 12; 1264 str += JSUtils.pad(temp, 2); 1265 break; 1266 1267 case 'H': 1268 str += (date.hour || "0"); 1269 break; 1270 case 'HH': 1271 str += JSUtils.pad(date.hour || "0", 2); 1272 break; 1273 case 'k': 1274 str += (date.hour == 0 ? "24" : date.hour); 1275 break; 1276 case 'kk': 1277 temp = (date.hour == 0 ? "24" : date.hour); 1278 str += JSUtils.pad(temp, 2); 1279 break; 1280 1281 case 'm': 1282 str += (date.minute || "0"); 1283 break; 1284 case 'mm': 1285 str += JSUtils.pad(date.minute || "0", 2); 1286 break; 1287 case 's': 1288 str += (date.second || "0"); 1289 break; 1290 case 'ss': 1291 str += JSUtils.pad(date.second || "0", 2); 1292 break; 1293 case 'S': 1294 str += (date.millisecond || "0"); 1295 break; 1296 case 'SSS': 1297 str += JSUtils.pad(date.millisecond || "0", 3); 1298 break; 1299 1300 case 'N': 1301 case 'NN': 1302 case 'MMM': 1303 case 'MMMM': 1304 case 'L': 1305 case 'LL': 1306 case 'LLL': 1307 case 'LLLL': 1308 key = templateArr[i] + (date.month || 1); 1309 str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); 1310 break; 1311 1312 case 'E': 1313 case 'EE': 1314 case 'EEE': 1315 case 'EEEE': 1316 case 'c': 1317 case 'cc': 1318 case 'ccc': 1319 case 'cccc': 1320 key = templateArr[i] + date.getDayOfWeek(); 1321 //console.log("finding " + key + " in the resources"); 1322 str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); 1323 break; 1324 1325 case 'a': 1326 switch (this.meridiems) { 1327 case "chinese": 1328 if (date.hour < 6) { 1329 key = "azh0"; // before dawn 1330 } else if (date.hour < 9) { 1331 key = "azh1"; // morning 1332 } else if (date.hour < 12) { 1333 key = "azh2"; // late morning/day before noon 1334 } else if (date.hour < 13) { 1335 key = "azh3"; // noon hour/midday 1336 } else if (date.hour < 18) { 1337 key = "azh4"; // afternoon 1338 } else if (date.hour < 21) { 1339 key = "azh5"; // evening time/dusk 1340 } else { 1341 key = "azh6"; // night time 1342 } 1343 break; 1344 case "ethiopic": 1345 if (date.hour < 6) { 1346 key = "a0-ethiopic"; // morning 1347 } else if (date.hour === 6 && date.minute === 0) { 1348 key = "a1-ethiopic"; // noon 1349 } else if (date.hour >= 6 && date.hour < 12) { 1350 key = "a2-ethiopic"; // afternoon 1351 } else if (date.hour >= 12 && date.hour < 18) { 1352 key = "a3-ethiopic"; // evening 1353 } else if (date.hour >= 18) { 1354 key = "a4-ethiopic"; // night 1355 } 1356 break; 1357 default: 1358 key = date.hour < 12 ? "a0" : "a1"; 1359 break; 1360 } 1361 //console.log("finding " + key + " in the resources"); 1362 str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); 1363 break; 1364 1365 case 'w': 1366 str += date.getWeekOfYear(); 1367 break; 1368 case 'ww': 1369 str += JSUtils.pad(date.getWeekOfYear(), 2); 1370 break; 1371 1372 case 'D': 1373 str += date.getDayOfYear(); 1374 break; 1375 case 'DD': 1376 str += JSUtils.pad(date.getDayOfYear(), 2); 1377 break; 1378 case 'DDD': 1379 str += JSUtils.pad(date.getDayOfYear(), 3); 1380 break; 1381 case 'W': 1382 str += date.getWeekOfMonth(this.locale); 1383 break; 1384 1385 case 'G': 1386 key = "G" + date.getEra(); 1387 str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); 1388 break; 1389 1390 case 'O': 1391 temp = this.sysres.getString("1#1st|2#2nd|3#3rd|21#21st|22#22nd|23#23rd|31#31st|#{num}th", "ordinalChoice"); 1392 str += temp.formatChoice(date.day, {num: date.day}); 1393 break; 1394 1395 case 'z': // general time zone 1396 tz = this.getTimeZone(); // lazy-load the tz 1397 str += tz.getDisplayName(date, "standard"); 1398 break; 1399 case 'Z': // RFC 822 time zone 1400 tz = this.getTimeZone(); // lazy-load the tz 1401 str += tz.getDisplayName(date, "rfc822"); 1402 break; 1403 1404 default: 1405 str += templateArr[i].replace(/'/g, ""); 1406 break; 1407 } 1408 } 1409 1410 if (this.digits) { 1411 str = JSUtils.mapString(str, this.digits); 1412 } 1413 return str; 1414 }, 1415 1416 /** 1417 * Format a particular date instance according to the settings of this 1418 * formatter object. The type of the date instance being formatted must 1419 * correspond exactly to the calendar type with which this formatter was 1420 * constructed. If the types are not compatible, this formatter will 1421 * produce bogus results. 1422 * 1423 * @param {IDate|number|string|Date|JulianDay|null|undefined} dateLike a date-like object to format 1424 * @return {string} the formatted version of the given date instance 1425 */ 1426 format: function (dateLike) { 1427 var thisZoneName = this.tz && this.tz.getId() || "local"; 1428 1429 var date = DateFactory._dateToIlib(dateLike, thisZoneName, this.locale); 1430 1431 if (!date.getCalendar || !(date instanceof IDate)) { 1432 throw "Wrong date type passed to DateFmt.format()"; 1433 } 1434 1435 var dateZoneName = date.timezone || "local"; 1436 1437 // convert to the time zone of this formatter before formatting 1438 if (dateZoneName !== thisZoneName || date.getCalendar() !== this.calName) { 1439 // console.log("Differing time zones date: " + dateZoneName + " and fmt: " + thisZoneName + ". Converting..."); 1440 // this will recalculate the date components based on the new time zone 1441 // and/or convert a date in another calendar to the current calendar before formatting it 1442 var newDate = DateFactory({ 1443 type: this.calName, 1444 timezone: thisZoneName, 1445 julianday: date.getJulianDay() 1446 }); 1447 1448 date = newDate; 1449 } 1450 return this._formatTemplate(date, this.templateArr); 1451 }, 1452 1453 /** 1454 * Return a string that describes a date relative to the given 1455 * reference date. The string returned is text that for the locale that 1456 * was specified when the formatter instance was constructed.<p> 1457 * 1458 * The date can be in the future relative to the reference date or in 1459 * the past, and the formatter will generate the appropriate string.<p> 1460 * 1461 * The text used to describe the relative reference depends on the length 1462 * of time between the date and the reference. If the time was in the 1463 * past, it will use the "ago" phrase, and in the future, it will use 1464 * the "in" phrase. Examples:<p> 1465 * 1466 * <ul> 1467 * <li>within a minute: either "X seconds ago" or "in X seconds" 1468 * <li>within an hour: either "X minutes ago" or "in X minutes" 1469 * <li>within a day: either "X hours ago" or "in X hours" 1470 * <li>within 2 weeks: either "X days ago" or "in X days" 1471 * <li>within 12 weeks (~3 months): either "X weeks ago" or "in X weeks" 1472 * <li>within two years: either "X months ago" or "in X months" 1473 * <li>longer than 2 years: "X years ago" or "in X years" 1474 * </ul> 1475 * 1476 * @param {IDate|number|string|Date|JulianDay|null|undefined} reference a date that the date parameter should be relative to 1477 * @param {IDate|number|string|Date|JulianDay|null|undefined} date a date being formatted 1478 * @throws "Wrong calendar type" when the start or end dates are not the same 1479 * calendar type as the formatter itself 1480 * @return {string} the formatted relative date 1481 */ 1482 formatRelative: function(reference, date) { 1483 reference = DateFactory._dateToIlib(reference); 1484 date = DateFactory._dateToIlib(date); 1485 1486 var referenceRd, dateRd, fmt, diff, absDiff, num; 1487 1488 if (typeof(reference) !== 'object' || !reference.getCalendar || reference.getCalendar() !== this.calName || 1489 typeof(date) !== 'object' || !date.getCalendar || date.getCalendar() !== this.calName) { 1490 throw "Wrong calendar type"; 1491 } 1492 1493 referenceRd = reference.getRataDie(); 1494 dateRd = date.getRataDie(); 1495 1496 diff = referenceRd - dateRd; 1497 absDiff = Math.abs(diff); 1498 1499 if (absDiff < 0.000694444) { 1500 num = Math.round(absDiff * 86400); 1501 switch (this.length) { 1502 case 's': 1503 fmt = diff > 0 ? this.sysres.getString("#{num}s ago") : this.sysres.getString("#in {num}s"); 1504 break; 1505 case 'm': 1506 fmt = diff > 0 ? this.sysres.getString("1#1 sec ago|#{num} sec ago") : this.sysres.getString("1#in 1 sec|#in {num} sec"); 1507 break; 1508 default: 1509 case 'f': 1510 case 'l': 1511 fmt = diff > 0 ? this.sysres.getString("1#1 second ago|#{num} seconds ago") : this.sysres.getString("1#in 1 second|#in {num} seconds"); 1512 break; 1513 } 1514 } else if (absDiff < 0.041666667) { 1515 num = Math.round(absDiff * 1440); 1516 switch (this.length) { 1517 case 's': 1518 fmt = diff > 0 ? this.sysres.getString("#{num}mi ago") : this.sysres.getString("#in {num}mi"); 1519 break; 1520 case 'm': 1521 fmt = diff > 0 ? this.sysres.getString("1#1 min ago|#{num} min ago") : this.sysres.getString("1#in 1 min|#in {num} min"); 1522 break; 1523 default: 1524 case 'f': 1525 case 'l': 1526 fmt = diff > 0 ? this.sysres.getString("1#1 minute ago|#{num} minutes ago") : this.sysres.getString("1#in 1 minute|#in {num} minutes"); 1527 break; 1528 } 1529 } else if (absDiff < 1) { 1530 num = Math.round(absDiff * 24); 1531 switch (this.length) { 1532 case 's': 1533 fmt = diff > 0 ? this.sysres.getString("#{num}h ago") : this.sysres.getString("#in {num}h"); 1534 break; 1535 case 'm': 1536 fmt = diff > 0 ? this.sysres.getString("1#1 hr ago|#{num} hrs ago") : this.sysres.getString("1#in 1 hr|#in {num} hrs"); 1537 break; 1538 default: 1539 case 'f': 1540 case 'l': 1541 fmt = diff > 0 ? this.sysres.getString("1#1 hour ago|#{num} hours ago") : this.sysres.getString("1#in 1 hour|#in {num} hours"); 1542 break; 1543 } 1544 } else if (absDiff < 14) { 1545 num = Math.round(absDiff); 1546 switch (this.length) { 1547 case 's': 1548 fmt = diff > 0 ? this.sysres.getString("#{num}d ago") : this.sysres.getString("#in {num}d"); 1549 break; 1550 case 'm': 1551 fmt = diff > 0 ? this.sysres.getString("1#1 dy ago|#{nudurationm} dys ago") : this.sysres.getString("1#in 1 dy|#in {num} dys"); 1552 break; 1553 default: 1554 case 'f': 1555 case 'l': 1556 fmt = diff > 0 ? this.sysres.getString("1#1 day ago|#{num} days ago") : this.sysres.getString("1#in 1 day|#in {num} days"); 1557 break; 1558 } 1559 } else if (absDiff < 84) { 1560 num = Math.round(absDiff/7); 1561 switch (this.length) { 1562 case 's': 1563 fmt = diff > 0 ? this.sysres.getString("#{num}w ago") : this.sysres.getString("#in {num}w"); 1564 break; 1565 case 'm': 1566 fmt = diff > 0 ? this.sysres.getString("1#1 wk ago|#{num} wks ago") : this.sysres.getString("1#in 1 wk|#in {num} wks"); 1567 break; 1568 default: 1569 case 'f': 1570 case 'l': 1571 fmt = diff > 0 ? this.sysres.getString("1#1 week ago|#{num} weeks ago") : this.sysres.getString("1#in 1 week|#in {num} weeks"); 1572 break; 1573 } 1574 } else if (absDiff < 730) { 1575 num = Math.round(absDiff/30.4); 1576 switch (this.length) { 1577 case 's': 1578 fmt = diff > 0 ? this.sysres.getString("#{num}mo ago") : this.sysres.getString("#in {num}mo"); 1579 break; 1580 case 'm': 1581 fmt = diff > 0 ? this.sysres.getString("1#1 mon ago|#{num} mons ago") : this.sysres.getString("1#in 1 mon|#in {num} mons"); 1582 break; 1583 default: 1584 case 'f': 1585 case 'l': 1586 fmt = diff > 0 ? this.sysres.getString("1#1 month ago|#{num} months ago") : this.sysres.getString("1#in 1 month|#in {num} months"); 1587 break; 1588 } 1589 } else { 1590 num = Math.round(absDiff/365); 1591 switch (this.length) { 1592 case 's': 1593 fmt = diff > 0 ? this.sysres.getString("#{num}y ago") : this.sysres.getString("#in {num}y"); 1594 break; 1595 case 'm': 1596 fmt = diff > 0 ? this.sysres.getString("1#1 yr ago|#{num} yrs ago") : this.sysres.getString("1#in 1 yr|#in {num} yrs"); 1597 break; 1598 default: 1599 case 'f': 1600 case 'l': 1601 fmt = diff > 0 ? this.sysres.getString("1#1 year ago|#{num} years ago") : this.sysres.getString("1#in 1 year|#in {num} years"); 1602 break; 1603 } 1604 } 1605 return fmt.formatChoice(num, {num: num}); 1606 } 1607 }; 1608 1609 module.exports = DateFmt; 1610