1 /* 2 * RataDie.js - Represent the RD date number in the 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 MathUtils = require("./MathUtils.js"); 21 var JSUtils = require("./JSUtils.js"); 22 var JulianDay = require("./JulianDay.js"); 23 24 /** 25 * @class 26 * Construct a new RD date number object. The constructor parameters can 27 * contain any of the following properties: 28 * 29 * <ul> 30 * <li><i>unixtime<i> - sets the time of this instance according to the given 31 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 32 * 33 * <li><i>julianday</i> - sets the time of this instance according to the given 34 * Julian Day instance or the Julian Day given as a float 35 * 36 * <li><i>cycle</i> - any integer giving the number of 60-year cycle in which the date is located. 37 * If the cycle is not given but the year is, it is assumed that the year parameter is a fictitious 38 * linear count of years since the beginning of the epoch, much like other calendars. This linear 39 * count is never used. If both the cycle and year are given, the year is wrapped to the range 0 40 * to 60 and treated as if it were a year in the regular 60-year cycle. 41 * 42 * <li><i>year</i> - any integer, including 0 43 * 44 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 45 * 46 * <li><i>day</i> - 1 to 31 47 * 48 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 49 * is always done with an unambiguous 24 hour representation 50 * 51 * <li><i>minute</i> - 0 to 59 52 * 53 * <li><i>second</i> - 0 to 59 54 * 55 * <li><i>millisecond</i> - 0 to 999 56 * 57 * <li><i>parts</i> - 0 to 1079. Specify the halaqim parts of an hour. Either specify 58 * the parts or specify the minutes, seconds, and milliseconds, but not both. This is only used 59 * in the Hebrew calendar. 60 * 61 * <li><i>minute</i> - 0 to 59 62 * 63 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 64 * </ul> 65 * 66 * If the constructor is called with another date instance instead of 67 * a parameter block, the other instance acts as a parameter block and its 68 * settings are copied into the current instance.<p> 69 * 70 * If the constructor is called with no arguments at all or if none of the 71 * properties listed above are present, then the RD is calculate based on 72 * the current date at the time of instantiation. <p> 73 * 74 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 75 * specified in the params, it is assumed that they have the smallest possible 76 * value in the range for the property (zero or one).<p> 77 * 78 * 79 * @private 80 * @constructor 81 * @param {Object=} params parameters that govern the settings and behaviour of this RD date 82 */ 83 var RataDie = function(params) { 84 if (params) { 85 if (typeof(params.date) !== 'undefined') { 86 // accept JS Date classes or strings 87 var date = params.date; 88 if (!(JSUtils.isDate(date))) { 89 date = new Date(date); // maybe a string initializer? 90 } 91 this._setTime(date.getTime()); 92 } else if (typeof(params.unixtime) !== 'undefined') { 93 this._setTime(parseInt(params.unixtime, 10)); 94 } else if (typeof(params.julianday) !== 'undefined') { 95 // JD time is defined to be UTC 96 this._setJulianDay(parseFloat(params.julianday)); 97 } else if (params.year || params.month || params.day || params.hour || 98 params.minute || params.second || params.millisecond || params.parts || params.cycle) { 99 this._setDateComponents(params); 100 } else if (typeof(params.rd) !== 'undefined') { 101 /** 102 * @type {number} the Rata Die number of this date for this calendar type 103 */ 104 this.rd = (typeof(params.rd) === 'object' && params.rd instanceof RataDie) ? params.rd.rd : params.rd; 105 } 106 } 107 108 if (typeof(this.rd) === 'undefined' || isNaN(this.rd)) { 109 var now = new Date(); 110 this._setTime(now.getTime()); 111 } 112 }; 113 114 /** 115 * @private 116 * @const 117 * @type {number} 118 */ 119 RataDie.gregorianEpoch = 1721424.5; 120 121 RataDie.prototype = { 122 /** 123 * @protected 124 * @type {number} 125 * the difference between a zero Julian day and the zero Gregorian date. 126 */ 127 epoch: RataDie.gregorianEpoch, 128 129 /** 130 * Set the RD of this instance according to the given unix time. Unix time is 131 * the number of milliseconds since midnight on Jan 1, 1970. 132 * 133 * @protected 134 * @param {number} millis the unix time to set this date to in milliseconds 135 */ 136 _setTime: function(millis) { 137 // 2440587.5 is the julian day of midnight Jan 1, 1970, UTC (Gregorian) 138 this._setJulianDay(2440587.5 + millis / 86400000); 139 }, 140 141 /** 142 * Set the date of this instance using a Julian Day. 143 * @protected 144 * @param {number} date the Julian Day to use to set this date 145 */ 146 _setJulianDay: function (date) { 147 var jd = (typeof(date) === 'number') ? new JulianDay(date) : date; 148 // round to the nearest millisecond 149 this.rd = MathUtils.halfup((jd.getDate() - this.epoch) * 86400000) / 86400000; 150 }, 151 152 /** 153 * Return the rd number of the particular day of the week on or before the 154 * given rd. eg. The Sunday on or before the given rd. 155 * @protected 156 * @param {number} rd the rata die date of the reference date 157 * @param {number} dayOfWeek the day of the week that is being sought relative 158 * to the current date 159 * @return {number} the rd of the day of the week 160 */ 161 _onOrBefore: function(rd, dayOfWeek) { 162 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek - 2, 7); 163 }, 164 165 /** 166 * Return the rd number of the particular day of the week on or before the current rd. 167 * eg. The Sunday on or before the current rd. If the offset is given, the calculation 168 * happens in wall time instead of UTC. UTC time may be a day before or day behind 169 * wall time, so it it would give the wrong day of the week if this calculation was 170 * done in UTC time when the caller really wanted wall time. Even though the calculation 171 * may be done in wall time, the return value is nonetheless always given in UTC. 172 * @param {number} dayOfWeek the day of the week that is being sought relative 173 * to the current date 174 * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is 175 * not given 176 * @return {number} the rd of the day of the week 177 */ 178 onOrBefore: function(dayOfWeek, offset) { 179 offset = offset || 0; 180 return this._onOrBefore(this.rd + offset, dayOfWeek) - offset; 181 }, 182 183 /** 184 * Return the rd number of the particular day of the week on or before the current rd. 185 * eg. The Sunday on or before the current rd. If the offset is given, the calculation 186 * happens in wall time instead of UTC. UTC time may be a day before or day behind 187 * wall time, so it it would give the wrong day of the week if this calculation was 188 * done in UTC time when the caller really wanted wall time. Even though the calculation 189 * may be done in wall time, the return value is nonetheless always given in UTC. 190 * @param {number} dayOfWeek the day of the week that is being sought relative 191 * to the reference date 192 * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is 193 * not given 194 * @return {number} the day of the week 195 */ 196 onOrAfter: function(dayOfWeek, offset) { 197 offset = offset || 0; 198 return this._onOrBefore(this.rd+6+offset, dayOfWeek) - offset; 199 }, 200 201 /** 202 * Return the rd number of the particular day of the week before the current rd. 203 * eg. The Sunday before the current rd. If the offset is given, the calculation 204 * happens in wall time instead of UTC. UTC time may be a day before or day behind 205 * wall time, so it it would give the wrong day of the week if this calculation was 206 * done in UTC time when the caller really wanted wall time. Even though the calculation 207 * may be done in wall time, the return value is nonetheless always given in UTC. 208 * @param {number} dayOfWeek the day of the week that is being sought relative 209 * to the reference date 210 * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is 211 * not given 212 * @return {number} the day of the week 213 */ 214 before: function(dayOfWeek, offset) { 215 offset = offset || 0; 216 return this._onOrBefore(this.rd-1+offset, dayOfWeek) - offset; 217 }, 218 219 /** 220 * Return the rd number of the particular day of the week after the current rd. 221 * eg. The Sunday after the current rd. If the offset is given, the calculation 222 * happens in wall time instead of UTC. UTC time may be a day before or day behind 223 * wall time, so it it would give the wrong day of the week if this calculation was 224 * done in UTC time when the caller really wanted wall time. Even though the calculation 225 * may be done in wall time, the return value is nonetheless always given in UTC. 226 * @param {number} dayOfWeek the day of the week that is being sought relative 227 * to the reference date 228 * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is 229 * not given 230 * @return {number} the day of the week 231 */ 232 after: function(dayOfWeek, offset) { 233 offset = offset || 0; 234 return this._onOrBefore(this.rd+7+offset, dayOfWeek) - offset; 235 }, 236 237 /** 238 * Return the unix time equivalent to this Gregorian date instance. Unix time is 239 * the number of milliseconds since midnight on Jan 1, 1970 UTC. This method only 240 * returns a valid number for dates between midnight, Jan 1, 1970 and 241 * Jan 19, 2038 at 3:14:07am when the unix time runs out. If this instance 242 * encodes a date outside of that range, this method will return -1. 243 * 244 * @return {number} a number giving the unix time, or -1 if the date is outside the 245 * valid unix time range 246 */ 247 getTime: function() { 248 // earlier than Jan 1, 1970 249 // or later than Jan 19, 2038 at 3:14:07am 250 var jd = this.getJulianDay(); 251 if (jd < 2440587.5 || jd > 2465442.634803241) { 252 return -1; 253 } 254 255 // avoid the rounding errors in the floating point math by only using 256 // the whole days from the rd, and then calculating the milliseconds directly 257 return Math.round((jd - 2440587.5) * 86400000); 258 }, 259 260 /** 261 * Return the extended unix time equivalent to this Gregorian date instance. Unix time is 262 * the number of milliseconds since midnight on Jan 1, 1970 UTC. Traditionally unix time 263 * (or the type "time_t" in C/C++) is only encoded with a unsigned 32 bit integer, and thus 264 * runs out on Jan 19, 2038. However, most Javascript engines encode numbers well above 265 * 32 bits and the Date object allows you to encode up to 100 million days worth of time 266 * after Jan 1, 1970, and even more interestingly 100 million days worth of time before 267 * Jan 1, 1970 as well. This method returns the number of milliseconds in that extended 268 * range. If this instance encodes a date outside of that range, this method will return 269 * NaN. 270 * 271 * @return {number} a number giving the extended unix time, or NaN if the date is outside 272 * the valid extended unix time range 273 */ 274 getTimeExtended: function() { 275 var jd = this.getJulianDay(); 276 277 // test if earlier than Jan 1, 1970 - 100 million days 278 // or later than Jan 1, 1970 + 100 million days 279 if (jd < -97559412.5 || jd > 102440587.5) { 280 return NaN; 281 } 282 283 // avoid the rounding errors in the floating point math by only using 284 // the whole days from the rd, and then calculating the milliseconds directly 285 return Math.round((jd - 2440587.5) * 86400000); 286 }, 287 288 /** 289 * Return the Julian Day equivalent to this calendar date as a number. 290 * This returns the julian day in UTC. 291 * 292 * @return {number} the julian date equivalent of this date 293 */ 294 getJulianDay: function() { 295 return this.rd + this.epoch; 296 }, 297 298 /** 299 * Return the Rata Die (fixed day) number of this RD date. 300 * 301 * @return {number} the rd date as a number 302 */ 303 getRataDie: function() { 304 return this.rd; 305 } 306 }; 307 308 module.exports = RataDie; 309