1 /* 2 * DateFactory.js - Factory class to create the right subclasses of a date for any 3 * calendar or locale. 4 * 5 * Copyright © 2012-2015, 2018, JEDLSoft 6 * 7 * Licensed under the Apache License, Version 2.0 (the "License"); 8 * you may not use this file except in compliance with the License. 9 * You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, software 14 * distributed under the License is distributed on an "AS IS" BASIS, 15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 * 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 21 var ilib = require("./ilib.js"); 22 var JSUtils = require("./JSUtils.js"); 23 24 var Locale = require("./Locale.js"); 25 var LocaleInfo = require("./LocaleInfo.js"); 26 var JulianDay = require("./JulianDay.js"); 27 var CalendarFactory = require("./CalendarFactory.js"); 28 29 // Statically depend on these even though we don't use them 30 // to guarantee they are loaded into the cache already. 31 var IDate = require("./IDate.js"); 32 33 /** 34 * Factory method to create a new instance of a date subclass.<p> 35 * 36 * The options parameter can be an object that contains the following 37 * properties: 38 * 39 * <ul> 40 * <li><i>type</i> - specify the type/calendar of the date desired. The 41 * list of valid values changes depending on which calendars are 42 * defined. When assembling your iliball.js, include those date type 43 * you wish to use in your program or web page, and they will register 44 * themselves with this factory method. The "gregorian", 45 * and "julian" calendars are all included by default, as they are the 46 * standard calendars for much of the world. If not specified, the type 47 * of the date returned is the one that is appropriate for the locale. 48 * This property may also be given as "calendar" instead of "type". 49 * 50 * <li><i>onLoad</i> - a callback function to call when the date object is fully 51 * loaded. When the onLoad option is given, the date factory will attempt to 52 * load any missing locale data using the ilib loader callback. 53 * When the constructor is done (even if the data is already preassembled), the 54 * onLoad function is called with the current instance as a parameter, so this 55 * callback can be used with preassembled or dynamic loading or a mix of the two. 56 * 57 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 58 * asynchronously. If this option is given as "false", then the "onLoad" 59 * callback must be given, as the instance returned from this constructor will 60 * not be usable for a while. 61 * 62 * <li><i>loadParams</i> - an object containing parameters to pass to the 63 * loader callback function when locale data is missing. The parameters are not 64 * interpretted or modified in any way. They are simply passed along. The object 65 * may contain any property/value pairs as long as the calling code is in 66 * agreement with the loader callback function as to what those parameters mean. 67 * </ul> 68 * 69 * The options object is also passed down to the date constructor, and 70 * thus can contain the the properties as the date object being instantiated. 71 * See the documentation for {@link GregorianDate}, and other 72 * subclasses for more details on other parameter that may be passed in.<p> 73 * 74 * Please note that if you do not give the type parameter, this factory 75 * method will create a date object that is appropriate for the calendar 76 * that is most commonly used in the specified or current ilib locale. 77 * For example, in Thailand, the most common calendar is the Thai solar 78 * calendar. If the current locale is "th-TH" (Thai for Thailand) and you 79 * use this factory method to construct a new date without specifying the 80 * type, it will automatically give you back an instance of 81 * {@link ThaiSolarDate}. This is convenient because you do not 82 * need to know which locales use which types of dates. In fact, you 83 * should always use this factory method to make new date instances unless 84 * you know that you specifically need a date in a particular calendar.<p> 85 * 86 * Also note that when you pass in the date components such as year, month, 87 * day, etc., these components should be appropriate for the given date 88 * being instantiated. That is, in our Thai example in the previous 89 * paragraph, the year and such should be given as a Thai solar year, not 90 * the Gregorian year that you get from the Javascript Date class. In 91 * order to initialize a date instance when you don't know what subclass 92 * will be instantiated for the locale, use a parameter such as "unixtime" 93 * or "julianday" which are unambiguous and based on UTC time, instead of 94 * the year/month/date date components. The date components for that UTC 95 * time will be calculated and the time zone offset will be automatically 96 * factored in. 97 * 98 * @static 99 * @param {Object=} options options controlling the construction of this instance, or 100 * undefined to use the default options 101 * @return {IDate} an instance of a calendar object of the appropriate type 102 */ 103 var DateFactory = function(options) { 104 var locale, 105 type, 106 sync = true, 107 obj; 108 109 if (options) { 110 if (options.locale) { 111 locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 112 } 113 114 type = options.type || options.calendar; 115 116 if (typeof(options.sync) === 'boolean') { 117 sync = options.sync; 118 } 119 } 120 121 if (!locale) { 122 locale = new Locale(); // default locale 123 } 124 125 if (!type) { 126 new LocaleInfo(locale, { 127 sync: sync, 128 loadParams: options && options.loadParams, 129 onLoad: function(info) { 130 type = info.getCalendar(); 131 132 obj = DateFactory._init(type, options); 133 } 134 }); 135 } else { 136 obj = DateFactory._init(type, options); 137 } 138 139 return obj 140 }; 141 142 /** 143 * Map calendar names to classes to initialize in the dynamic code model. 144 * TODO: Need to figure out some way that this doesn't have to be updated by hand. 145 * @private 146 */ 147 DateFactory._dynMap = { 148 "coptic": "Coptic", 149 "ethiopic": "Ethiopic", 150 "gregorian": "Gregorian", 151 "han": "Han", 152 "hebrew": "Hebrew", 153 "islamic": "Islamic", 154 "julian": "Julian", 155 "persian": "Persian", 156 "persian-algo": "PersianAlgo", 157 "thaisolar": "ThaiSolar" 158 }; 159 160 function circumventWebPackDate(x) { 161 return "./" + x + "Date.js"; 162 } 163 164 function circumventWebPackCal(x) { 165 return "./" + x + "Cal.js"; 166 } 167 168 /** 169 * Dynamically load the code for a calendar and calendar class if necessary. 170 * @protected 171 */ 172 DateFactory._dynLoadDate = function (name, fnc) { 173 if (!IDate._constructors[name]) { 174 var entry = DateFactory._dynMap[name]; 175 if (entry) { 176 IDate._constructors[name] = require(fnc(entry)); 177 } 178 } 179 return IDate._constructors[name]; 180 }; 181 182 /** 183 * @protected 184 * @static 185 */ 186 DateFactory._init = function(type, options) { 187 var cons; 188 189 if (ilib.isDynCode()) { 190 DateFactory._dynLoadDate(type, circumventWebPackDate); 191 CalendarFactory._dynLoadCalendar(type, circumventWebPackCal); 192 } 193 194 cons = IDate._constructors[type]; 195 196 // pass the same options through to the constructor so the subclass 197 // has the ability to do something with if it needs to 198 if (!cons && typeof(options.onLoad) === "function") { 199 options.onLoad(undefined); 200 } 201 return cons && new cons(options); 202 }; 203 204 /** 205 * Convert JavaScript Date objects and other types into native Dates. This accepts any 206 * string or number that can be translated by the JavaScript Date class, 207 * (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse) 208 * any JavaScript Date classed object, any IDate subclass, an JulianDay object, an object 209 * containing the normal options to initialize an IDate instance, or null (will 210 * return null or undefined if input is null or undefined). Normal output is 211 * a standard native subclass of the IDate object as appropriate for the locale. 212 * 213 * @static 214 * @protected 215 * @param {IDate|Object|JulianDay|Date|string|number=} inDate The input date object, string or Number. 216 * @param {IString|string=} timezone timezone to use if a new date object is created 217 * @param {Locale|string=} locale locale to use when constructing an IDate 218 * @return {IDate|null|undefined} an IDate subclass equivalent to the given inDate 219 */ 220 DateFactory._dateToIlib = function(inDate, timezone, locale) { 221 if (typeof(inDate) === 'undefined' || inDate === null) { 222 return inDate; 223 } 224 if (inDate instanceof IDate) { 225 return inDate; 226 } 227 if (typeof(inDate) === 'number') { 228 return DateFactory({ 229 unixtime: inDate, 230 timezone: timezone, 231 locale: locale 232 }); 233 } 234 if (typeof(inDate) === 'string') { 235 inDate = new Date(inDate); 236 } 237 if (JSUtils.isDate(inDate)) { 238 return DateFactory({ 239 unixtime: inDate.getTime(), 240 timezone: timezone, 241 locale: locale 242 }); 243 } 244 if (inDate instanceof JulianDay) { 245 return DateFactory({ 246 jd: inDate, 247 timezone: timezone, 248 locale: locale 249 }); 250 } 251 if (typeof(inDate) === 'object') { 252 return DateFactory(inDate); 253 } 254 return DateFactory({ 255 unixtime: inDate.getTime(), 256 timezone: timezone, 257 locale: locale 258 }); 259 }; 260 261 module.exports = DateFactory; 262