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