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