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