/* * GregorianDate.js - Represent a date in the Gregorian calendar * * Copyright © 2012-2015, JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * limitations under the License. */ /* !depends ilib.js IDate.js GregorianCal.js SearchUtils.js MathUtils.js Locale.js LocaleInfo.js JulianDay.js GregRataDie.js TimeZone.js */ var ilib = require("./ilib.js"); var SearchUtils = require("./SearchUtils.js"); var MathUtils = require("./MathUtils.js"); var Locale = require("./Locale.js"); var LocaleInfo = require("./LocaleInfo.js"); var JulianDay = require("./JulianDay.js"); var IDate = require("./IDate.js"); var TimeZone = require("./TimeZone.js"); var Calendar = require("./Calendar.js"); var GregorianCal = require("./GregorianCal.js"); var GregRataDie = require("./GregRataDie.js"); /** * @class * Construct a new Gregorian date object. The constructor parameters can * contain any of the following properties: * *
* For example, in the US, the transition out of daylight savings time * in 2014 happens at Nov 2, 2014 2:00am Daylight Time, when the time falls * back to Nov 2, 2014 1:00am Standard Time. If you give a date/time components as * "Nov 2, 2014 1:30am", then there are two 1:30am times in that day, and you would * have to give the standard flag to indicate which of those two you mean. * (dst=true means daylight time, dst=false means standard time). * *
* * If the constructor is called with no arguments at all or if none of the * properties listed above * from unixtime through millisecond are present, then the date * components are * filled in with the current date at the time of instantiation. Note that if * you do not give the time zone when defaulting to the current time and the * time zone for all of ilib was not set with ilib.setTimeZone(), then the * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich * Mean Time").
* * If any of the properties from year through millisecond are not * specified in the params, it is assumed that they have the smallest possible * value in the range for the property (zero or one).
* * * @constructor * @extends IDate * @param {Object=} params parameters that govern the settings and behaviour of this Gregorian date */ var GregorianDate = function(params) { this.cal = new GregorianCal(); this.timezone = "local"; if (params) { if (typeof(params.noinstance) === 'boolean' && params.noinstance) { // for doing inheritance, so don't need to fill in the data. The inheriting class only wants the methods. return; } if (params.locale) { this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; var li = new LocaleInfo(this.locale); this.timezone = li.getTimeZone(); } if (params.timezone) { this.timezone = params.timezone.toString(); } if (params.year || params.month || params.day || params.hour || params.minute || params.second || params.millisecond ) { this.year = parseInt(params.year, 10) || 0; this.month = parseInt(params.month, 10) || 1; this.day = parseInt(params.day, 10) || 1; this.hour = parseInt(params.hour, 10) || 0; this.minute = parseInt(params.minute, 10) || 0; this.second = parseInt(params.second, 10) || 0; this.millisecond = parseInt(params.millisecond, 10) || 0; if (typeof(params.dst) === 'boolean') { this.dst = params.dst; } this.rd = this.newRd(params); // add the time zone offset to the rd to convert to UTC this.offset = 0; if (this.timezone === "local" && typeof(params.dst) === 'undefined') { // if dst is defined, the intrinsic Date object has no way of specifying which version of a time you mean // in the overlap time at the end of DST. Do you mean the daylight 1:30am or the standard 1:30am? In this // case, use the ilib calculations below, which can distinguish between the two properly var d = new Date(this.year, this.month-1, this.day, this.hour, this.minute, this.second, this.millisecond); this.offset = -d.getTimezoneOffset() / 1440; } else { if (!this.tz) { this.tz = new TimeZone({id: this.timezone}); } // getOffsetMillis requires that this.year, this.rd, and this.dst // are set in order to figure out which time zone rules apply and // what the offset is at that point in the year this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; } if (this.offset !== 0) { this.rd = this.newRd({ rd: this.rd.getRataDie() - this.offset }); } } } if (!this.rd) { this.rd = this.newRd(params); this._calcDateComponents(); } }; GregorianDate.prototype = new IDate({noinstance: true}); GregorianDate.prototype.parent = IDate; GregorianDate.prototype.constructor = GregorianDate; /** * Return a new RD for this date type using the given params. * @private * @param {Object=} params the parameters used to create this rata die instance * @returns {RataDie} the new RD instance for the given params */ GregorianDate.prototype.newRd = function (params) { return new GregRataDie(params); }; /** * Calculates the Gregorian year for a given rd number. * @private * @static */ GregorianDate._calcYear = function(rd) { var days400, days100, days4, years400, years100, years4, years1, year; years400 = Math.floor((rd - 1) / 146097); days400 = MathUtils.mod((rd - 1), 146097); years100 = Math.floor(days400 / 36524); days100 = MathUtils.mod(days400, 36524); years4 = Math.floor(days100 / 1461); days4 = MathUtils.mod(days100, 1461); years1 = Math.floor(days4 / 365); year = 400 * years400 + 100 * years100 + 4 * years4 + years1; if (years100 !== 4 && years1 !== 4) { year++; } return year; }; /** * @private */ GregorianDate.prototype._calcYear = function(rd) { return GregorianDate._calcYear(rd); }; /** * Calculate the date components for the current time zone * @private */ GregorianDate.prototype._calcDateComponents = function () { if (this.timezone === "local" && this.rd.getRataDie() >= -99280837 && this.rd.getRataDie() <= 100719163) { // console.log("using js Date to calculate offset"); // use the intrinsic JS Date object to do the tz conversion for us, which // guarantees that it follows the system tz database settings var d = new Date(this.rd.getTimeExtended()); /** * Year in the Gregorian calendar. * @type number */ this.year = d.getFullYear(); /** * The month number, ranging from 1 (January) to 12 (December). * @type number */ this.month = d.getMonth()+1; /** * The day of the month. This ranges from 1 to 31. * @type number */ this.day = d.getDate(); /** * The hour of the day. This can be a number from 0 to 23, as times are * stored unambiguously in the 24-hour clock. * @type number */ this.hour = d.getHours(); /** * The minute of the hours. Ranges from 0 to 59. * @type number */ this.minute = d.getMinutes(); /** * The second of the minute. Ranges from 0 to 59. * @type number */ this.second = d.getSeconds(); /** * The millisecond of the second. Ranges from 0 to 999. * @type number */ this.millisecond = d.getMilliseconds(); this.offset = -d.getTimezoneOffset() / 1440; } else { // console.log("using ilib to calculate offset. tz is " + this.timezone); // console.log("GregDate._calcDateComponents: date is " + JSON.stringify(this) + " parent is " + JSON.stringify(this.parent) + " and parent.parent is " + JSON.stringify(this.parent.parent)); if (typeof(this.offset) === "undefined") { // console.log("calculating offset"); this.year = this._calcYear(this.rd.getRataDie()); // now offset the RD by the time zone, then recalculate in case we were // near the year boundary if (!this.tz) { this.tz = new TimeZone({id: this.timezone}); } this.offset = this.tz.getOffsetMillis(this) / 86400000; // } else { // console.log("offset is already defined somehow. type is " + typeof(this.offset)); // console.trace("Stack is this one"); } // console.log("offset is " + this.offset); var rd = this.rd.getRataDie(); if (this.offset !== 0) { rd += this.offset; } this.year = this._calcYear(rd); var yearStartRd = this.newRd({ year: this.year, month: 1, day: 1, cal: this.cal }); // remainder is days into the year var remainder = rd - yearStartRd.getRataDie() + 1; var cumulative = GregorianCal.prototype.isLeapYear.call(this.cal, this.year) ? GregRataDie.cumMonthLengthsLeap : GregRataDie.cumMonthLengths; this.month = SearchUtils.bsearch(Math.floor(remainder), cumulative); remainder = remainder - cumulative[this.month-1]; this.day = Math.floor(remainder); remainder -= this.day; // now convert to milliseconds for the rest of the calculation remainder = Math.round(remainder * 86400000); this.hour = Math.floor(remainder/3600000); remainder -= this.hour * 3600000; this.minute = Math.floor(remainder/60000); remainder -= this.minute * 60000; this.second = Math.floor(remainder/1000); remainder -= this.second * 1000; this.millisecond = Math.floor(remainder); } }; /** * Return the day of the week of this date. The day of the week is encoded * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. * * @return {number} the day of the week */ GregorianDate.prototype.getDayOfWeek = function() { var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); return MathUtils.mod(rd, 7); }; /** * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to * 365, regardless of months or weeks, etc. That is, January 1st is day 1, and * December 31st is 365 in regular years, or 366 in leap years. * @return {number} the ordinal day of the year */ GregorianDate.prototype.getDayOfYear = function() { var cumulativeMap = this.cal.isLeapYear(this.year) ? GregRataDie.cumMonthLengthsLeap : GregRataDie.cumMonthLengths; return cumulativeMap[this.month-1] + this.day; }; /** * Return the era for this date as a number. The value for the era for Gregorian * calendars is -1 for "before the common era" (BCE) and 1 for "the common era" (CE). * BCE dates are any date before Jan 1, 1 CE. In the proleptic Gregorian calendar, * there is a year 0, so any years that are negative or zero are BCE. In the Julian * calendar, there is no year 0. Instead, the calendar goes straight from year -1 to * 1. * @return {number} 1 if this date is in the common era, -1 if it is before the * common era */ GregorianDate.prototype.getEra = function() { return (this.year < 1) ? -1 : 1; }; /** * Return the name of the calendar that governs this date. * * @return {string} a string giving the name of the calendar */ GregorianDate.prototype.getCalendar = function() { return "gregorian"; }; // register with the factory method IDate._constructors["gregorian"] = GregorianDate; module.exports = GregorianDate;