1 /* 2 * JulianDate.js - Represent a date in the Julian 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 JulianRataDie = require("./JulianRataDie.js"); 30 var JulianCal = require("./JulianCal.js"); 31 32 /** 33 * @class 34 * Construct a new date object for the Julian Calendar. The constructor can be called 35 * with a parameter object that contains 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 (Gregorian). 40 * <li><i>julianday</i> - the Julian Day to set into this date 41 * <li><i>year</i> - any integer except 0. Years go from -1 (BCE) to 1 (CE), skipping the zero 42 * year which doesn't exist in the Julian calendar 43 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 44 * <li><i>day</i> - 1 to 31 45 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 46 * is always done with an unambiguous 24 hour representation 47 * <li><i>minute</i> - 0 to 59 48 * <li><i>second</i> - 0 to 59 49 * <li><i>millisecond<i> - 0 to 999 50 * <li><i>locale</i> - the TimeZone instance or time zone name as a string 51 * of this julian date. The date/time is kept in the local time. The time zone 52 * is used later if this date is formatted according to a different time zone and 53 * the difference has to be calculated, or when the date format has a time zone 54 * component in it. 55 * <li><i>timezone</i> - the time zone of this instance. If the time zone is not 56 * given, it can be inferred from this locale. For locales that span multiple 57 * time zones, the one with the largest population is chosen as the one that 58 * represents the locale. 59 * 60 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 61 * </ul> 62 * 63 * NB. The <a href="http://en.wikipedia.org/wiki/Julian_date">Julian Day</a> 64 * (JulianDay) object is a <i>different</i> object than a 65 * <a href="http://en.wikipedia.org/wiki/Julian_calendar">date in 66 * the Julian calendar</a> and the two are not to be confused. The Julian Day 67 * object represents time as a number of whole and fractional days since the 68 * beginning of the epoch, whereas a date in the Julian 69 * calendar is a regular date that signifies year, month, day, etc. using the rules 70 * of the Julian calendar. The naming of Julian Days and the Julian calendar are 71 * unfortunately close, and come from history.<p> 72 * 73 * If called with another Julian date argument, the date components of the given 74 * date are copied into the current one.<p> 75 * 76 * If the constructor is called with no arguments at all or if none of the 77 * properties listed above 78 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 79 * components are 80 * filled in with the current date at the time of instantiation. Note that if 81 * you do not give the time zone when defaulting to the current time and the 82 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 83 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 84 * Mean Time").<p> 85 * 86 * 87 * @constructor 88 * @extends IDate 89 * @param {Object=} params parameters that govern the settings and behaviour of this Julian date 90 */ 91 var JulianDate = function(params) { 92 this.cal = new JulianCal(); 93 94 params = params || {}; 95 96 if (params.timezone) { 97 this.timezone = params.timezone; 98 } 99 if (params.locale) { 100 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 101 } 102 103 if (!this.timezone) { 104 if (this.locale) { 105 new LocaleInfo(this.locale, { 106 sync: params.sync, 107 loadParams: params.loadParams, 108 onLoad: ilib.bind(this, function(li) { 109 this.li = li; 110 this.timezone = li.getTimeZone(); 111 this._init(params); 112 }) 113 }); 114 } else { 115 this.timezone = "local"; 116 this._init(params); 117 } 118 } else { 119 this._init(params); 120 } 121 122 }; 123 124 JulianDate.prototype = new IDate({noinstance: true}); 125 JulianDate.prototype.parent = IDate; 126 JulianDate.prototype.constructor = JulianDate; 127 128 /** 129 * @private 130 * Initialize the date 131 */ 132 JulianDate.prototype._init = function (params) { 133 if (params.year || params.month || params.day || params.hour || 134 params.minute || params.second || params.millisecond ) { 135 /** 136 * Year in the Julian calendar. 137 * @type number 138 */ 139 this.year = parseInt(params.year, 10) || 0; 140 /** 141 * The month number, ranging from 1 (January) to 12 (December). 142 * @type number 143 */ 144 this.month = parseInt(params.month, 10) || 1; 145 /** 146 * The day of the month. This ranges from 1 to 31. 147 * @type number 148 */ 149 this.day = parseInt(params.day, 10) || 1; 150 /** 151 * The hour of the day. This can be a number from 0 to 23, as times are 152 * stored unambiguously in the 24-hour clock. 153 * @type number 154 */ 155 this.hour = parseInt(params.hour, 10) || 0; 156 /** 157 * The minute of the hours. Ranges from 0 to 59. 158 * @type number 159 */ 160 this.minute = parseInt(params.minute, 10) || 0; 161 /** 162 * The second of the minute. Ranges from 0 to 59. 163 * @type number 164 */ 165 this.second = parseInt(params.second, 10) || 0; 166 /** 167 * The millisecond of the second. Ranges from 0 to 999. 168 * @type number 169 */ 170 this.millisecond = parseInt(params.millisecond, 10) || 0; 171 172 /** 173 * The day of the year. Ranges from 1 to 383. 174 * @type number 175 */ 176 this.dayOfYear = parseInt(params.dayOfYear, 10); 177 178 if (typeof(params.dst) === 'boolean') { 179 this.dst = params.dst; 180 } 181 182 this.rd = this.newRd(this); 183 184 new TimeZone({ 185 id: this.timezone, 186 sync: params.sync, 187 loadParams: params.loadParams, 188 onLoad: ilib.bind(this, function(tz) { 189 this.tz = tz; 190 // add the time zone offset to the rd to convert to UTC 191 // getOffsetMillis requires that this.year, this.rd, and this.dst 192 // are set in order to figure out which time zone rules apply and 193 // what the offset is at that point in the year 194 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 195 if (this.offset !== 0) { 196 this.rd = this.newRd({ 197 rd: this.rd.getRataDie() - this.offset 198 }); 199 } 200 this._init2(params); 201 }) 202 }); 203 } else { 204 this._init2(params); 205 } 206 }; 207 208 /** 209 * @private 210 * Finish initializing the date 211 */ 212 JulianDate.prototype._init2 = function (params) { 213 if (!this.rd) { 214 this.rd = this.newRd(params); 215 this._calcDateComponents(); 216 } 217 218 if (typeof(params.onLoad) === "function") { 219 params.onLoad(this); 220 } 221 }; 222 223 /** 224 * Return a new RD for this date type using the given params. 225 * @protected 226 * @param {Object=} params the parameters used to create this rata die instance 227 * @returns {RataDie} the new RD instance for the given params 228 */ 229 JulianDate.prototype.newRd = function (params) { 230 return new JulianRataDie(params); 231 }; 232 233 /** 234 * Return the year for the given RD 235 * @protected 236 * @param {number} rd RD to calculate from 237 * @returns {number} the year for the RD 238 */ 239 JulianDate.prototype._calcYear = function(rd) { 240 var year = Math.floor((4*(Math.floor(rd)-1) + 1464)/1461); 241 242 return (year <= 0) ? year - 1 : year; 243 }; 244 245 /** 246 * Calculate date components for the given RD date. 247 * @protected 248 */ 249 JulianDate.prototype._calcDateComponents = function () { 250 var remainder, 251 cumulative, 252 rd = this.rd.getRataDie(); 253 254 this.year = this._calcYear(rd); 255 256 if (typeof(this.offset) === "undefined") { 257 this.year = this._calcYear(rd); 258 259 // now offset the RD by the time zone, then recalculate in case we were 260 // near the year boundary 261 if (!this.tz) { 262 this.tz = new TimeZone({id: this.timezone}); 263 } 264 this.offset = this.tz.getOffsetMillis(this) / 86400000; 265 } 266 267 if (this.offset !== 0) { 268 rd += this.offset; 269 this.year = this._calcYear(rd); 270 } 271 272 var jan1 = this.newRd({ 273 year: this.year, 274 month: 1, 275 day: 1, 276 hour: 0, 277 minute: 0, 278 second: 0, 279 millisecond: 0 280 }); 281 remainder = rd + 1 - jan1.getRataDie(); 282 283 cumulative = this.cal.isLeapYear(this.year) ? 284 JulianCal.cumMonthLengthsLeap : 285 JulianCal.cumMonthLengths; 286 287 this.month = SearchUtils.bsearch(Math.floor(remainder), cumulative); 288 remainder = remainder - cumulative[this.month-1]; 289 290 this.day = Math.floor(remainder); 291 remainder -= this.day; 292 // now convert to milliseconds for the rest of the calculation 293 remainder = Math.round(remainder * 86400000); 294 295 this.hour = Math.floor(remainder/3600000); 296 remainder -= this.hour * 3600000; 297 298 this.minute = Math.floor(remainder/60000); 299 remainder -= this.minute * 60000; 300 301 this.second = Math.floor(remainder/1000); 302 remainder -= this.second * 1000; 303 304 this.millisecond = remainder; 305 }; 306 307 /** 308 * Return the day of the week of this date. The day of the week is encoded 309 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 310 * 311 * @return {number} the day of the week 312 */ 313 JulianDate.prototype.getDayOfWeek = function() { 314 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 315 return MathUtils.mod(rd-2, 7); 316 }; 317 318 /** 319 * Return the name of the calendar that governs this date. 320 * 321 * @return {string} a string giving the name of the calendar 322 */ 323 JulianDate.prototype.getCalendar = function() { 324 return "julian"; 325 }; 326 327 //register with the factory method 328 IDate._constructors["julian"] = JulianDate; 329 330 module.exports = JulianDate; 331