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