1 /* 2 * GregorianDate.js - Represent a date in the Gregorian 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 IDate = require("./IDate.js"); 27 var TimeZone = require("./TimeZone.js"); 28 29 var GregorianCal = require("./GregorianCal.js"); 30 var GregRataDie = require("./GregRataDie.js"); 31 32 /** 33 * @class 34 * Construct a new Gregorian date object. The constructor parameters can 35 * contain any of the following properties: 36 * 37 * <ul> 38 * <li><i>unixtime<i> - sets the time of this instance according to the given 39 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 40 * 41 * <li><i>julianday</i> - sets the time of this instance according to the given 42 * Julian Day instance or the Julian Day given as a float 43 * 44 * <li><i>year</i> - any integer, including 0 45 * 46 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 47 * 48 * <li><i>day</i> - 1 to 31 49 * 50 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 51 * is always done with an unambiguous 24 hour representation 52 * 53 * <li><i>minute</i> - 0 to 59 54 * 55 * <li><i>second</i> - 0 to 59 56 * 57 * <li><i>millisecond</i> - 0 to 999 58 * 59 * <li><i>dst</i> - boolean used to specify whether the given time components are 60 * intended to be in daylight time or not. This is only used in the overlap 61 * time when transitioning from DST to standard time, and the time components are 62 * ambiguous. Otherwise at all other times of the year, this flag is ignored. 63 * If you specify the date using unix time (UTC) or a julian day, then the time is 64 * already unambiguous and this flag does not need to be specified. 65 * <p> 66 * For example, in the US, the transition out of daylight savings time 67 * in 2014 happens at Nov 2, 2014 2:00am Daylight Time, when the time falls 68 * back to Nov 2, 2014 1:00am Standard Time. If you give a date/time components as 69 * "Nov 2, 2014 1:30am", then there are two 1:30am times in that day, and you would 70 * have to give the standard flag to indicate which of those two you mean. 71 * (dst=true means daylight time, dst=false means standard time). 72 * 73 * <li><i>timezone</i> - the TimeZone instance or time zone name as a string 74 * of this gregorian date. The date/time is kept in the local time. The time zone 75 * is used later if this date is formatted according to a different time zone and 76 * the difference has to be calculated, or when the date format has a time zone 77 * component in it. 78 * 79 * <li><i>locale</i> - locale for this gregorian date. If the time zone is not 80 * given, it can be inferred from this locale. For locales that span multiple 81 * time zones, the one with the largest population is chosen as the one that 82 * represents the locale. 83 * 84 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 85 * 86 * <li><i>onLoad</i> - a callback function to call when this date object is fully 87 * loaded. When the onLoad option is given, this date object will attempt to 88 * load any missing locale data using the ilib loader callback. 89 * When the constructor is done (even if the data is already preassembled), the 90 * onLoad function is called with the current instance as a parameter, so this 91 * callback can be used with preassembled or dynamic loading or a mix of the two. 92 * 93 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 94 * asynchronously. If this option is given as "false", then the "onLoad" 95 * callback must be given, as the instance returned from this constructor will 96 * not be usable for a while. 97 * 98 * <li><i>loadParams</i> - an object containing parameters to pass to the 99 * loader callback function when locale data is missing. The parameters are not 100 * interpretted or modified in any way. They are simply passed along. The object 101 * may contain any property/value pairs as long as the calling code is in 102 * agreement with the loader callback function as to what those parameters mean. 103 * </ul> 104 * 105 * If the constructor is called with another Gregorian date instance instead of 106 * a parameter block, the other instance acts as a parameter block and its 107 * settings are copied into the current instance.<p> 108 * 109 * If the constructor is called with no arguments at all or if none of the 110 * properties listed above 111 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 112 * components are 113 * filled in with the current date at the time of instantiation. Note that if 114 * you do not give the time zone when defaulting to the current time and the 115 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 116 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 117 * Mean Time").<p> 118 * 119 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 120 * specified in the params, it is assumed that they have the smallest possible 121 * value in the range for the property (zero or one).<p> 122 * 123 * 124 * @constructor 125 * @extends IDate 126 * @param {Object=} params parameters that govern the settings and behaviour of this Gregorian date 127 */ 128 var GregorianDate = function(params) { 129 this.cal = new GregorianCal(); 130 131 params = params || {}; 132 if (typeof(params.noinstance) === 'boolean' && params.noinstance) { 133 // for doing inheritance, so don't need to fill in the data. The 134 // inheriting class only wants the methods. 135 return; 136 } 137 138 if (params.timezone) { 139 this.timezone = params.timezone.toString(); 140 } 141 if (params.locale) { 142 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 143 } 144 145 if (!this.timezone) { 146 if (this.locale) { 147 new LocaleInfo(this.locale, { 148 sync: params.sync, 149 loadParams: params.loadParams, 150 onLoad: ilib.bind(this, function(li) { 151 this.li = li; 152 this.timezone = li.getTimeZone(); 153 this._init(params); 154 }) 155 }); 156 } else { 157 this.timezone = "local"; 158 this._init(params); 159 } 160 } else { 161 this._init(params); 162 } 163 }; 164 165 GregorianDate.prototype = new IDate({noinstance: true}); 166 GregorianDate.prototype.parent = IDate; 167 GregorianDate.prototype.constructor = GregorianDate; 168 169 /** 170 * @private 171 * Initialize this date object 172 */ 173 GregorianDate.prototype._init = function (params) { 174 if (params.year || params.month || params.day || params.hour || 175 params.minute || params.second || params.millisecond ) { 176 this.year = parseInt(params.year, 10) || 0; 177 this.month = parseInt(params.month, 10) || 1; 178 this.day = parseInt(params.day, 10) || 1; 179 this.hour = parseInt(params.hour, 10) || 0; 180 this.minute = parseInt(params.minute, 10) || 0; 181 this.second = parseInt(params.second, 10) || 0; 182 this.millisecond = parseInt(params.millisecond, 10) || 0; 183 if (typeof(params.dst) === 'boolean') { 184 this.dst = params.dst; 185 } 186 this.rd = this.newRd(params); 187 188 // add the time zone offset to the rd to convert to UTC 189 this.offset = 0; 190 if (this.timezone === "local" && typeof(params.dst) === 'undefined') { 191 // if dst is defined, the intrinsic Date object has no way of specifying which version of a time you mean 192 // in the overlap time at the end of DST. Do you mean the daylight 1:30am or the standard 1:30am? In this 193 // case, use the ilib calculations below, which can distinguish between the two properly 194 var d = new Date(this.year, this.month-1, this.day, this.hour, this.minute, this.second, this.millisecond); 195 var hBefore = new Date(this.year, this.month-1, this.day, this.hour - 1, this.minute, this.second, this.millisecond); 196 this.offset = -d.getTimezoneOffset() / 1440; 197 if (d.getTimezoneOffset() < hBefore.getTimezoneOffset()) { 198 var startOffset = -hBefore.getTimezoneOffset() / 1440; 199 this.rd = this.newRd({ 200 rd: this.rd.getRataDie() - startOffset 201 }); 202 } else { 203 this.rd = this.newRd({ 204 rd: this.rd.getRataDie() - this.offset 205 }); 206 } 207 this._init2(params); 208 } else { 209 new TimeZone({ 210 id: this.timezone, 211 sync: params.sync, 212 loadParams: params.loadParams, 213 onLoad: ilib.bind(this, function(tz) { 214 this.tz = tz; 215 216 // getOffsetMillis requires that this.year, this.rd, and this.dst 217 // are set in order to figure out which time zone rules apply and 218 // what the offset is at that point in the year 219 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 220 this.rd = this.newRd({ 221 rd: this.rd.getRataDie() - this.offset 222 }); 223 this._init2(params); 224 }) 225 }); 226 } 227 } else { 228 this._init2(params); 229 } 230 }; 231 232 /** 233 * @private 234 * Finish initializing this date object 235 */ 236 GregorianDate.prototype._init2 = function (params) { 237 if (!this.rd) { 238 this.rd = this.newRd(params); 239 this._calcDateComponents(); 240 } 241 242 if (typeof(params.onLoad) === "function") { 243 params.onLoad(this); 244 } 245 }; 246 247 /** 248 * Return a new RD for this date type using the given params. 249 * @private 250 * @param {Object=} params the parameters used to create this rata die instance 251 * @returns {RataDie} the new RD instance for the given params 252 */ 253 GregorianDate.prototype.newRd = function (params) { 254 return new GregRataDie(params); 255 }; 256 257 /** 258 * Calculates the Gregorian year for a given rd number. 259 * @private 260 * @static 261 */ 262 GregorianDate._calcYear = function(rd) { 263 var days400, 264 days100, 265 days4, 266 years400, 267 years100, 268 years4, 269 years1, 270 year; 271 272 years400 = Math.floor((rd - 1) / 146097); 273 days400 = MathUtils.mod((rd - 1), 146097); 274 years100 = Math.floor(days400 / 36524); 275 days100 = MathUtils.mod(days400, 36524); 276 years4 = Math.floor(days100 / 1461); 277 days4 = MathUtils.mod(days100, 1461); 278 years1 = Math.floor(days4 / 365); 279 280 year = 400 * years400 + 100 * years100 + 4 * years4 + years1; 281 if (years100 !== 4 && years1 !== 4) { 282 year++; 283 } 284 return year; 285 }; 286 287 /** 288 * @private 289 */ 290 GregorianDate.prototype._calcYear = function(rd) { 291 return GregorianDate._calcYear(rd); 292 }; 293 294 /** 295 * Calculate the date components for the current time zone 296 * @private 297 */ 298 GregorianDate.prototype._calcDateComponents = function () { 299 if (this.timezone === "local" && this.rd.getRataDie() >= -99280837 && this.rd.getRataDie() <= 100719163) { 300 // console.log("using js Date to calculate offset"); 301 // use the intrinsic JS Date object to do the tz conversion for us, which 302 // guarantees that it follows the system tz database settings 303 var d = new Date(this.rd.getTimeExtended()); 304 305 /** 306 * Year in the Gregorian calendar. 307 * @type number 308 */ 309 this.year = d.getFullYear(); 310 311 /** 312 * The month number, ranging from 1 (January) to 12 (December). 313 * @type number 314 */ 315 this.month = d.getMonth()+1; 316 317 /** 318 * The day of the month. This ranges from 1 to 31. 319 * @type number 320 */ 321 this.day = d.getDate(); 322 323 /** 324 * The hour of the day. This can be a number from 0 to 23, as times are 325 * stored unambiguously in the 24-hour clock. 326 * @type number 327 */ 328 this.hour = d.getHours(); 329 330 /** 331 * The minute of the hours. Ranges from 0 to 59. 332 * @type number 333 */ 334 this.minute = d.getMinutes(); 335 336 /** 337 * The second of the minute. Ranges from 0 to 59. 338 * @type number 339 */ 340 this.second = d.getSeconds(); 341 342 /** 343 * The millisecond of the second. Ranges from 0 to 999. 344 * @type number 345 */ 346 this.millisecond = d.getMilliseconds(); 347 348 this.offset = -d.getTimezoneOffset() / 1440; 349 } else { 350 // console.log("using ilib to calculate offset. tz is " + this.timezone); 351 // console.log("GregDate._calcDateComponents: date is " + JSON.stringify(this) + " parent is " + JSON.stringify(this.parent) + " and parent.parent is " + JSON.stringify(this.parent.parent)); 352 if (typeof(this.offset) === "undefined") { 353 // console.log("calculating offset"); 354 this.year = this._calcYear(this.rd.getRataDie()); 355 356 // now offset the RD by the time zone, then recalculate in case we were 357 // near the year boundary 358 if (!this.tz) { 359 this.tz = new TimeZone({id: this.timezone}); 360 } 361 this.offset = this.tz.getOffsetMillis(this) / 86400000; 362 // } else { 363 // console.log("offset is already defined somehow. type is " + typeof(this.offset)); 364 // console.trace("Stack is this one"); 365 } 366 // console.log("offset is " + this.offset); 367 var rd = this.rd.getRataDie(); 368 if (this.offset !== 0) { 369 rd += this.offset; 370 } 371 this.year = this._calcYear(rd); 372 373 var yearStartRd = this.newRd({ 374 year: this.year, 375 month: 1, 376 day: 1, 377 cal: this.cal 378 }); 379 380 // remainder is days into the year 381 var remainder = rd - yearStartRd.getRataDie() + 1; 382 383 var cumulative = GregorianCal.prototype.isLeapYear.call(this.cal, this.year) ? 384 GregRataDie.cumMonthLengthsLeap : 385 GregRataDie.cumMonthLengths; 386 387 this.month = SearchUtils.bsearch(Math.floor(remainder), cumulative); 388 remainder = remainder - cumulative[this.month-1]; 389 390 this.day = Math.floor(remainder); 391 remainder -= this.day; 392 // now convert to milliseconds for the rest of the calculation 393 remainder = Math.round(remainder * 86400000); 394 395 this.hour = Math.floor(remainder/3600000); 396 remainder -= this.hour * 3600000; 397 398 this.minute = Math.floor(remainder/60000); 399 remainder -= this.minute * 60000; 400 401 this.second = Math.floor(remainder/1000); 402 remainder -= this.second * 1000; 403 404 this.millisecond = Math.floor(remainder); 405 } 406 }; 407 408 /** 409 * Return the day of the week of this date. The day of the week is encoded 410 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 411 * 412 * @return {number} the day of the week 413 */ 414 GregorianDate.prototype.getDayOfWeek = function() { 415 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 416 return MathUtils.mod(rd, 7); 417 }; 418 419 /** 420 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 421 * 365, regardless of months or weeks, etc. That is, January 1st is day 1, and 422 * December 31st is 365 in regular years, or 366 in leap years. 423 * @return {number} the ordinal day of the year 424 */ 425 GregorianDate.prototype.getDayOfYear = function() { 426 var cumulativeMap = this.cal.isLeapYear(this.year) ? 427 GregRataDie.cumMonthLengthsLeap : 428 GregRataDie.cumMonthLengths; 429 430 return cumulativeMap[this.month-1] + this.day; 431 }; 432 433 /** 434 * Return the era for this date as a number. The value for the era for Gregorian 435 * calendars is -1 for "before the common era" (BCE) and 1 for "the common era" (CE). 436 * BCE dates are any date before Jan 1, 1 CE. In the proleptic Gregorian calendar, 437 * there is a year 0, so any years that are negative or zero are BCE. In the Julian 438 * calendar, there is no year 0. Instead, the calendar goes straight from year -1 to 439 * 1. 440 * @return {number} 1 if this date is in the common era, -1 if it is before the 441 * common era 442 */ 443 GregorianDate.prototype.getEra = function() { 444 return (this.year < 1) ? -1 : 1; 445 }; 446 447 /** 448 * Return the name of the calendar that governs this date. 449 * 450 * @return {string} a string giving the name of the calendar 451 */ 452 GregorianDate.prototype.getCalendar = function() { 453 return "gregorian"; 454 }; 455 456 // register with the factory method 457 IDate._constructors["gregorian"] = GregorianDate; 458 459 module.exports = GregorianDate; 460