1 /* 2 * EthiopicDate.js - Represent a date in the Ethiopic calendar 3 * 4 * Copyright © 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 MathUtils = require("./MathUtils.js"); 22 23 var EthiopicRataDie = require("./EthiopicRataDie.js"); 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 var EthiopicCal = require("./EthiopicCal.js"); 29 30 /** 31 * @class 32 * Construct a new date object for the Ethiopic Calendar. The constructor can be called 33 * with a parameter object that contains any of the following properties: 34 * 35 * <ul> 36 * <li><i>unixtime<i> - sets the time of this instance according to the given 37 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970 (Gregorian). 38 * <li><i>julianday</i> - the Julian Day to set into this date 39 * <li><i>year</i> - any integer 40 * <li><i>month</i> - 1 to 13, where 1 means Maskaram, 2 means Teqemt, etc., and 13 means Paguemen 41 * <li><i>day</i> - 1 to 30 42 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 43 * is always done with an unambiguous 24 hour representation 44 * <li><i>minute</i> - 0 to 59 45 * <li><i>second</i> - 0 to 59 46 * <li><i>millisecond<i> - 0 to 999 47 * <li><i>locale</i> - the TimeZone instance or time zone name as a string 48 * of this ethiopic date. The date/time is kept in the local time. The time zone 49 * is used later if this date is formatted according to a different time zone and 50 * the difference has to be calculated, or when the date format has a time zone 51 * component in it. 52 * <li><i>timezone</i> - the time zone of this instance. If the time zone is not 53 * given, it can be inferred from this locale. For locales that span multiple 54 * time zones, the one with the largest population is chosen as the one that 55 * represents the locale. 56 * 57 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 58 * </ul> 59 * 60 * If called with another Ethiopic date argument, the date components of the given 61 * date are copied into the current one.<p> 62 * 63 * If the constructor is called with no arguments at all or if none of the 64 * properties listed above 65 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 66 * components are 67 * filled in with the current date at the time of instantiation. Note that if 68 * you do not give the time zone when defaulting to the current time and the 69 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 70 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 71 * Mean Time").<p> 72 * 73 * 74 * @constructor 75 * @extends IDate 76 * @param {Object=} params parameters that govern the settings and behaviour of this Ethiopic date 77 */ 78 var EthiopicDate = function(params) { 79 this.cal = new EthiopicCal(); 80 81 params = params || {}; 82 83 if (typeof(params.noinstance) === 'boolean' && params.noinstance) { 84 // for doing inheritance, so don't need to fill in the data. The inheriting class only wants the methods. 85 return; 86 } 87 if (params.timezone) { 88 this.timezone = params.timezone; 89 } 90 if (params.locale) { 91 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 92 } 93 94 if (!this.timezone) { 95 if (this.locale) { 96 new LocaleInfo(this.locale, { 97 sync: params.sync, 98 loadParams: params.loadParams, 99 onLoad: ilib.bind(this, function(li) { 100 this.li = li; 101 this.timezone = li.getTimeZone(); 102 this._init(params); 103 }) 104 }); 105 } else { 106 this.timezone = "local"; 107 this._init(params); 108 } 109 } else { 110 this._init(params); 111 } 112 }; 113 114 EthiopicDate.prototype = new IDate({ noinstance: true }); 115 EthiopicDate.prototype.parent = IDate; 116 EthiopicDate.prototype.constructor = EthiopicDate; 117 118 /** 119 * Initialize this instance 120 * @private 121 */ 122 EthiopicDate.prototype._init = function (params) { 123 new TimeZone({ 124 id: this.timezone, 125 sync: params.sync, 126 loadParams: params.loadParams, 127 onLoad: ilib.bind(this, function(tz) { 128 this.tz = tz; 129 130 if (params.year || params.month || params.day || params.hour || 131 params.minute || params.second || params.millisecond ) { 132 /** 133 * Year in the Ethiopic calendar. 134 * @type number 135 */ 136 this.year = parseInt(params.year, 10) || 0; 137 /** 138 * The month number, ranging from 1 (Maskaram) to 13 (Paguemen). 139 * @type number 140 */ 141 this.month = parseInt(params.month, 10) || 1; 142 /** 143 * The day of the month. This ranges from 1 to 30. 144 * @type number 145 */ 146 this.day = parseInt(params.day, 10) || 1; 147 /** 148 * The hour of the day. This can be a number from 0 to 23, as times are 149 * stored unambiguously in the 24-hour clock. 150 * @type number 151 */ 152 this.hour = parseInt(params.hour, 10) || 0; 153 /** 154 * The minute of the hours. Ranges from 0 to 59. 155 * @type number 156 */ 157 this.minute = parseInt(params.minute, 10) || 0; 158 /** 159 * The second of the minute. Ranges from 0 to 59. 160 * @type number 161 */ 162 this.second = parseInt(params.second, 10) || 0; 163 /** 164 * The millisecond of the second. Ranges from 0 to 999. 165 * @type number 166 */ 167 this.millisecond = parseInt(params.millisecond, 10) || 0; 168 169 /** 170 * The day of the year. Ranges from 1 to 366. 171 * @type number 172 */ 173 this.dayOfYear = parseInt(params.dayOfYear, 10); 174 175 if (typeof(params.dst) === 'boolean') { 176 this.dst = params.dst; 177 } 178 179 this.rd = this.newRd(this); 180 181 // add the time zone offset to the rd to convert to UTC 182 // getOffsetMillis requires that this.year, this.rd, and this.dst 183 // are set in order to figure out which time zone rules apply and 184 // what the offset is at that point in the year 185 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 186 if (this.offset !== 0) { 187 this.rd = this.newRd({ 188 rd: this.rd.getRataDie() - this.offset 189 }); 190 } 191 } 192 193 if (!this.rd) { 194 this.rd = this.newRd(params); 195 this._calcDateComponents(); 196 } 197 198 if (typeof(params.onLoad) === "function") { 199 params.onLoad(this); 200 } 201 }) 202 }); 203 }; 204 205 /** 206 * Return a new RD for this date type using the given params. 207 * @protected 208 * @param {Object=} params the parameters used to create this rata die instance 209 * @returns {RataDie} the new RD instance for the given params 210 */ 211 EthiopicDate.prototype.newRd = function (params) { 212 return new EthiopicRataDie(params); 213 }; 214 215 /** 216 * Return the year for the given RD 217 * @protected 218 * @param {number} rd RD to calculate from 219 * @returns {number} the year for the RD 220 */ 221 EthiopicDate.prototype._calcYear = function(rd) { 222 var year = Math.floor((4*(Math.floor(rd)-1) + 1463)/1461); 223 224 return year; 225 }; 226 227 /** 228 * Calculate date components for the given RD date. 229 * @protected 230 */ 231 EthiopicDate.prototype._calcDateComponents = function () { 232 var remainder, 233 rd = this.rd.getRataDie(); 234 235 this.year = this._calcYear(rd); 236 237 if (typeof(this.offset) === "undefined") { 238 this.year = this._calcYear(rd); 239 240 // now offset the RD by the time zone, then recalculate in case we were 241 // near the year boundary 242 if (!this.tz) { 243 this.tz = new TimeZone({id: this.timezone}); 244 } 245 this.offset = this.tz.getOffsetMillis(this) / 86400000; 246 } 247 248 if (this.offset !== 0) { 249 rd += this.offset; 250 this.year = this._calcYear(rd); 251 } 252 253 var jan1 = this.newRd({ 254 year: this.year, 255 month: 1, 256 day: 1, 257 hour: 0, 258 minute: 0, 259 second: 0, 260 millisecond: 0 261 }); 262 remainder = rd + 1 - jan1.getRataDie(); 263 264 this.month = Math.floor((remainder-1)/30) + 1; 265 remainder = remainder - (this.month-1) * 30; 266 267 this.day = Math.floor(remainder); 268 remainder -= this.day; 269 // now convert to milliseconds for the rest of the calculation 270 remainder = Math.round(remainder * 86400000); 271 272 this.hour = Math.floor(remainder/3600000); 273 remainder -= this.hour * 3600000; 274 275 this.minute = Math.floor(remainder/60000); 276 remainder -= this.minute * 60000; 277 278 this.second = Math.floor(remainder/1000); 279 remainder -= this.second * 1000; 280 281 this.millisecond = remainder; 282 }; 283 284 /** 285 * Return the day of the week of this date. The day of the week is encoded 286 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 287 * 288 * @return {number} the day of the week 289 */ 290 EthiopicDate.prototype.getDayOfWeek = function() { 291 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 292 return MathUtils.mod(rd-4, 7); 293 }; 294 295 /** 296 * Return the name of the calendar that governs this date. 297 * 298 * @return {string} a string giving the name of the calendar 299 */ 300 EthiopicDate.prototype.getCalendar = function() { 301 return "ethiopic"; 302 }; 303 304 //register with the factory method 305 IDate._constructors["ethiopic"] = EthiopicDate; 306 307 module.exports = EthiopicDate; 308