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