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