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