1 /* 2 * IslamicDate.js - Represent a date in the Islamic calendar 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 var ilib = require("./ilib.js"); 21 var SearchUtils = require("./SearchUtils.js"); 22 var MathUtils = require("./MathUtils.js"); 23 24 var Locale = require("./Locale.js"); 25 var LocaleInfo = require("./LocaleInfo.js"); 26 var TimeZone = require("./TimeZone.js"); 27 var IDate = require("./IDate.js"); 28 29 var IslamicRataDie = require("./IslamicRataDie.js"); 30 var IslamicCal = require("./IslamicCal.js"); 31 32 /** 33 * @class 34 * Construct a new civil Islamic date object. The constructor can be called 35 * with a params object that can contain the following properties:<p> 36 * 37 * <ul> 38 * <li><i>julianday</i> - the Julian Day to set into this date 39 * <li><i>year</i> - any integer except 0. Years go from -1 (BCE) to 1 (CE), skipping the zero year 40 * <li><i>month</i> - 1 to 12, where 1 means Muharram, 2 means Saffar, etc. 41 * <li><i>day</i> - 1 to 30 42 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 43 * is always done with an unambiguous 24 hour representation 44 * <li><i>minute</i> - 0 to 59 45 * <li><i>second</i> - 0 to 59 46 * <li><i>millisecond</i> - 0 to 999 47 * <li><i>locale</i> - the TimeZone instance or time zone name as a string 48 * of this julian date. The date/time is kept in the local time. The time zone 49 * is used later if this date is formatted according to a different time zone and 50 * the difference has to be calculated, or when the date format has a time zone 51 * component in it. 52 * <li><i>timezone</i> - the time zone of this instance. If the time zone is not 53 * given, it can be inferred from this locale. For locales that span multiple 54 * time zones, the one with the largest population is chosen as the one that 55 * represents the locale. 56 * 57 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 58 * </ul> 59 * 60 * If called with another Islamic date argument, the date components of the given 61 * date are copied into the current one.<p> 62 * 63 * If the constructor is called with no arguments at all or if none of the 64 * properties listed above 65 * from <i>julianday</i> through <i>millisecond</i> are present, then the date 66 * components are 67 * filled in with the current date at the time of instantiation. Note that if 68 * you do not give the time zone when defaulting to the current time and the 69 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 70 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 71 * Mean Time").<p> 72 * 73 * 74 * @constructor 75 * @extends IDate 76 * @param {Object=} params parameters that govern the settings and behaviour of this Islamic date 77 */ 78 var IslamicDate = function(params) { 79 this.cal = new IslamicCal(); 80 81 params = params || {}; 82 83 if (params.timezone) { 84 this.timezone = params.timezone; 85 } 86 if (params.locale) { 87 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 88 } 89 90 if (!this.timezone) { 91 if (this.locale) { 92 new LocaleInfo(this.locale, { 93 sync: params.sync, 94 loadParams: params.loadParams, 95 onLoad: ilib.bind(this, function(li) { 96 this.li = li; 97 this.timezone = li.getTimeZone(); 98 this._init(params); 99 }) 100 }); 101 } else { 102 this.timezone = "local"; 103 this._init(params); 104 } 105 } else { 106 this._init(params); 107 } 108 }; 109 110 IslamicDate.prototype = new IDate({noinstance: true}); 111 IslamicDate.prototype.parent = IDate; 112 IslamicDate.prototype.constructor = IslamicDate; 113 114 /** 115 * Initialize the date 116 * @private 117 */ 118 IslamicDate.prototype._init = function (params) { 119 if (params.year || params.month || params.day || params.hour || 120 params.minute || params.second || params.millisecond ) { 121 /** 122 * Year in the Islamic calendar. 123 * @type number 124 */ 125 this.year = parseInt(params.year, 10) || 0; 126 127 /** 128 * The month number, ranging from 1 to 12 (December). 129 * @type number 130 */ 131 this.month = parseInt(params.month, 10) || 1; 132 133 /** 134 * The day of the month. This ranges from 1 to 30. 135 * @type number 136 */ 137 this.day = parseInt(params.day, 10) || 1; 138 139 /** 140 * The hour of the day. This can be a number from 0 to 23, as times are 141 * stored unambiguously in the 24-hour clock. 142 * @type number 143 */ 144 this.hour = parseInt(params.hour, 10) || 0; 145 146 /** 147 * The minute of the hours. Ranges from 0 to 59. 148 * @type number 149 */ 150 this.minute = parseInt(params.minute, 10) || 0; 151 152 /** 153 * The second of the minute. Ranges from 0 to 59. 154 * @type number 155 */ 156 this.second = parseInt(params.second, 10) || 0; 157 158 /** 159 * The millisecond of the second. Ranges from 0 to 999. 160 * @type number 161 */ 162 this.millisecond = parseInt(params.millisecond, 10) || 0; 163 164 /** 165 * The day of the year. Ranges from 1 to 355. 166 * @type number 167 */ 168 this.dayOfYear = parseInt(params.dayOfYear, 10); 169 170 if (typeof(params.dst) === 'boolean') { 171 this.dst = params.dst; 172 } 173 174 this.rd = this.newRd(this); 175 176 new TimeZone({ 177 id: this.timezone, 178 sync: params.sync, 179 loadParams: params.loadParams, 180 onLoad: ilib.bind(this, function(tz) { 181 this.tz = tz; 182 // add the time zone offset to the rd to convert to UTC 183 // getOffsetMillis requires that this.year, this.rd, and this.dst 184 // are set in order to figure out which time zone rules apply and 185 // what the offset is at that point in the year 186 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 187 if (this.offset !== 0) { 188 this.rd = this.newRd({ 189 rd: this.rd.getRataDie() - this.offset 190 }); 191 } 192 this._init2(params); 193 }) 194 }); 195 } else { 196 this._init2(params); 197 } 198 }; 199 200 /** 201 * @private 202 * Finish initializing this date object 203 */ 204 IslamicDate.prototype._init2 = function (params) { 205 if (!this.rd) { 206 this.rd = this.newRd(params); 207 this._calcDateComponents(); 208 } 209 210 if (typeof(params.onLoad) === "function") { 211 params.onLoad(this); 212 } 213 }; 214 215 /** 216 * the cumulative lengths of each month, for a non-leap year 217 * @private 218 * @const 219 * @type Array.<number> 220 */ 221 IslamicDate.cumMonthLengths = [ 222 0, /* Muharram */ 223 30, /* Saffar */ 224 59, /* Rabi'I */ 225 89, /* Rabi'II */ 226 118, /* Jumada I */ 227 148, /* Jumada II */ 228 177, /* Rajab */ 229 207, /* Sha'ban */ 230 236, /* Ramadan */ 231 266, /* Shawwal */ 232 295, /* Dhu al-Qa'da */ 233 325, /* Dhu al-Hijja */ 234 354 235 ]; 236 237 /** 238 * Number of days difference between RD 0 of the Gregorian calendar and 239 * RD 0 of the Islamic calendar. 240 * @private 241 * @const 242 * @type number 243 */ 244 IslamicDate.GregorianDiff = 227015; 245 246 /** 247 * Return a new RD for this date type using the given params. 248 * @protected 249 * @param {Object=} params the parameters used to create this rata die instance 250 * @returns {RataDie} the new RD instance for the given params 251 */ 252 IslamicDate.prototype.newRd = function (params) { 253 return new IslamicRataDie(params); 254 }; 255 256 /** 257 * Return the year for the given RD 258 * @protected 259 * @param {number} rd RD to calculate from 260 * @returns {number} the year for the RD 261 */ 262 IslamicDate.prototype._calcYear = function(rd) { 263 return Math.floor((30 * rd + 10646) / 10631); 264 }; 265 266 /** 267 * Calculate date components for the given RD date. 268 * @protected 269 */ 270 IslamicDate.prototype._calcDateComponents = function () { 271 var remainder, 272 rd = this.rd.getRataDie(); 273 274 this.year = this._calcYear(rd); 275 276 if (typeof(this.offset) === "undefined") { 277 this.year = this._calcYear(rd); 278 279 // now offset the RD by the time zone, then recalculate in case we were 280 // near the year boundary 281 if (!this.tz) { 282 this.tz = new TimeZone({id: this.timezone}); 283 } 284 this.offset = this.tz.getOffsetMillis(this) / 86400000; 285 } 286 287 if (this.offset !== 0) { 288 rd += this.offset; 289 this.year = this._calcYear(rd); 290 } 291 292 //console.log("IslamicDate.calcComponent: calculating for rd " + rd); 293 //console.log("IslamicDate.calcComponent: year is " + ret.year); 294 var yearStart = this.newRd({ 295 year: this.year, 296 month: 1, 297 day: 1, 298 hour: 0, 299 minute: 0, 300 second: 0, 301 millisecond: 0 302 }); 303 remainder = rd - yearStart.getRataDie() + 1; 304 305 this.dayOfYear = remainder; 306 307 //console.log("IslamicDate.calcComponent: remainder is " + remainder); 308 309 this.month = SearchUtils.bsearch(remainder, IslamicDate.cumMonthLengths); 310 remainder -= IslamicDate.cumMonthLengths[this.month-1]; 311 312 //console.log("IslamicDate.calcComponent: month is " + this.month + " and remainder is " + remainder); 313 314 this.day = Math.floor(remainder); 315 remainder -= this.day; 316 317 //console.log("IslamicDate.calcComponent: day is " + this.day + " and remainder is " + remainder); 318 319 // now convert to milliseconds for the rest of the calculation 320 remainder = Math.round(remainder * 86400000); 321 322 this.hour = Math.floor(remainder/3600000); 323 remainder -= this.hour * 3600000; 324 325 this.minute = Math.floor(remainder/60000); 326 remainder -= this.minute * 60000; 327 328 this.second = Math.floor(remainder/1000); 329 remainder -= this.second * 1000; 330 331 this.millisecond = remainder; 332 }; 333 334 /** 335 * Return the day of the week of this date. The day of the week is encoded 336 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 337 * 338 * @return {number} the day of the week 339 */ 340 IslamicDate.prototype.getDayOfWeek = function() { 341 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 342 return MathUtils.mod(rd-2, 7); 343 }; 344 345 /** 346 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 347 * 354 or 355, regardless of months or weeks, etc. That is, Muharran 1st is day 1, and 348 * Dhu al-Hijja 29 is 354. 349 * @return {number} the ordinal day of the year 350 */ 351 IslamicDate.prototype.getDayOfYear = function() { 352 return IslamicDate.cumMonthLengths[this.month-1] + this.day; 353 }; 354 355 /** 356 * Return the era for this date as a number. The value for the era for Islamic 357 * calendars is -1 for "before the Islamic era" and 1 for "the Islamic era". 358 * Islamic era dates are any date after Muharran 1, 1, which is the same as 359 * July 16, 622 CE in the Gregorian calendar. 360 * 361 * @return {number} 1 if this date is in the common era, -1 if it is before the 362 * common era 363 */ 364 IslamicDate.prototype.getEra = function() { 365 return (this.year < 1) ? -1 : 1; 366 }; 367 368 /** 369 * Return the name of the calendar that governs this date. 370 * 371 * @return {string} a string giving the name of the calendar 372 */ 373 IslamicDate.prototype.getCalendar = function() { 374 return "islamic"; 375 }; 376 377 //register with the factory method 378 IDate._constructors["islamic"] = IslamicDate; 379 380 module.exports = IslamicDate; 381