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