name:
dist/Formulas/Date.js
-rw-r--r--
44214
1"use strict";
2exports.__esModule = true;
3/// <reference path="../../node_modules/moment/moment.d.ts"/>
4var moment = require("moment");
5var ArgsChecker_1 = require("../Utilities/ArgsChecker");
6var TypeConverter_1 = require("../Utilities/TypeConverter");
7var Errors_1 = require("../Errors");
8/**
9 * Converts a provided year, month, and day into a date.
10 * @param year - The year component of the date.
11 * @param month - The month component of the date.
12 * @param day - The day component of the date.
13 * @returns {number} newly created date.
14 * @constructor
15 */
16var DATE = function (year, month, day) {
17 var FIRST_YEAR = 1900;
18 ArgsChecker_1.ArgsChecker.checkLength(arguments, 3, "DATE");
19 year = Math.abs(Math.floor(TypeConverter_1.TypeConverter.firstValueAsNumber(year))); // No negative values for year
20 month = Math.floor(TypeConverter_1.TypeConverter.firstValueAsNumber(month)) - 1; // Months are between 0 and 11.
21 day = Math.floor(TypeConverter_1.TypeConverter.firstValueAsNumber(day)) - 1; // Days are also zero-indexed.
22 var m = moment.utc(TypeConverter_1.TypeConverter.ORIGIN_MOMENT)
23 .add(2, "days")
24 .add(year < FIRST_YEAR ? year : year - FIRST_YEAR, 'years') // If the value is less than 1900, assume 1900 as start index for year
25 .add(month, 'months')
26 .add(day, 'days');
27 var dateAsNumber = TypeConverter_1.TypeConverter.momentToDayNumber(m);
28 if (dateAsNumber < 0) {
29 throw new Errors_1.NumError("DATE evaluates to an out of range value " + dateAsNumber
30 + ". It should be greater than or equal to 0.");
31 }
32 return dateAsNumber;
33};
34exports.DATE = DATE;
35/**
36 * Converts a provided date string in a known format to a date value.
37 * @param dateString - The string representing the date. Understood formats include any date format which is
38 * normally auto-converted when entered, without quotation marks, directly into a cell. Understood formats may depend on
39 * region and language settings.
40 * @returns {number} of days since 1900/1/1, inclusively.
41 * @constructor
42 */
43var DATEVALUE = function (dateString) {
44 ArgsChecker_1.ArgsChecker.checkLength(arguments, 1, "DATEVALUE");
45 dateString = TypeConverter_1.TypeConverter.firstValueAsString(dateString);
46 var dateAsNumber;
47 try {
48 dateAsNumber = TypeConverter_1.TypeConverter.stringToDateNumber(dateString);
49 }
50 catch (e) {
51 throw new Errors_1.ValueError("DATEVALUE parameter '" + dateString + "' cannot be parsed to date/time.");
52 }
53 // If we've not been able to parse the date by now, then we cannot parse it at all.
54 return dateAsNumber;
55};
56exports.DATEVALUE = DATEVALUE;
57/**
58 * Returns a date a specified number of months before or after another date.
59 * @param startDate - The date from which to calculate the result.
60 * @param months - The number of months before (negative) or after (positive) start_date to calculate.
61 * @returns {number} date a specified number of months before or after another date
62 * @constructor
63 */
64var EDATE = function (startDate, months) {
65 ArgsChecker_1.ArgsChecker.checkLength(arguments, 2, "EDATE");
66 var startDateNumber = TypeConverter_1.TypeConverter.firstValueAsDateNumber(startDate, true); // tell firstValueAsDateNumber to coerce boolean
67 if (startDateNumber < 0) {
68 throw new Errors_1.NumError("Function EDATE parameter 1 value is " + startDateNumber + ". It should be greater than or equal to 0.");
69 }
70 months = Math.floor(TypeConverter_1.TypeConverter.firstValueAsNumber(months));
71 // While momentToDayNumber will return an inclusive count of days since 1900/1/1, moment.Moment.add assumes exclusive
72 // count of days.
73 return TypeConverter_1.TypeConverter.momentToDayNumber(moment.utc(TypeConverter_1.TypeConverter.ORIGIN_MOMENT).add(startDateNumber, "days").add(months, "months"));
74};
75exports.EDATE = EDATE;
76/**
77 * Returns a date representing the last day of a month which falls a specified number of months before or after another
78 * date.
79 * @param startDate - The date from which to calculate the the result.
80 * @param months - The number of months before (negative) or after (positive) start_date to consider. The last
81 * calendar day of the calculated month is returned.
82 * @returns {number} the last day of a month
83 * @constructor
84 */
85var EOMONTH = function (startDate, months) {
86 ArgsChecker_1.ArgsChecker.checkLength(arguments, 2, "EOMONTH");
87 var startDateNumber = TypeConverter_1.TypeConverter.firstValueAsDateNumber(startDate, true); // tell firstValueAsDateNumber to coerce boolean
88 if (startDateNumber < 0) {
89 throw new Errors_1.NumError("Function EOMONTH parameter 1 value is " + startDateNumber + ". It should be greater than or equal to 0.");
90 }
91 months = Math.floor(TypeConverter_1.TypeConverter.firstValueAsNumber(months));
92 return TypeConverter_1.TypeConverter.momentToDayNumber(moment.utc(TypeConverter_1.TypeConverter.ORIGIN_MOMENT)
93 .add(startDateNumber, "days")
94 .add(months, "months")
95 .endOf("month"));
96};
97exports.EOMONTH = EOMONTH;
98/**
99 * Returns the day of the month that a specific date falls on, in numeric format.
100 * @param date - The date from which to extract the day. Must be a reference to a cell containing a date, a
101 * function returning a date type, or a number.
102 * @returns {number} day of the month
103 * @constructor
104 */
105var DAY = function (date) {
106 ArgsChecker_1.ArgsChecker.checkLength(arguments, 1, "DAY");
107 date = TypeConverter_1.TypeConverter.firstValueAsDateNumber(date, true); // tell firstValueAsDateNumber to coerce boolean
108 if (date < 0) {
109 throw new Errors_1.NumError("Function DAY parameter 1 value is " + date + ". It should be greater than or equal to 0.");
110 }
111 return TypeConverter_1.TypeConverter.numberToMoment(date).date();
112};
113exports.DAY = DAY;
114/**
115 * Returns the number of days between two dates.
116 * @param endDate most recently occurring
117 * @param startDate not most recently occurring
118 * @returns {number} of days between start_date and end_date
119 * @constructor
120 */
121var DAYS = function (endDate, startDate) {
122 ArgsChecker_1.ArgsChecker.checkLength(arguments, 2, "DAYS");
123 endDate = TypeConverter_1.TypeConverter.firstValueAsDateNumber(endDate, true); // tell firstValueAsDateNumber to coerce boolean
124 startDate = TypeConverter_1.TypeConverter.firstValueAsDateNumber(startDate, true); // tell firstValueAsDateNumber to coerce boolean
125 return endDate - startDate;
126};
127exports.DAYS = DAYS;
128/**
129 * Returns the difference between two days based on the 360 day year used in some financial interest calculations.
130 * @param startDate - The start date to consider in the calculation. Must be a reference to a cell containing
131 * a date, a function returning a date type, or a number.
132 * @param endDate - The end date to consider in the calculation. Must be a reference to a cell containing a
133 * date, a function returning a date type, or a number.
134 * @param methodToUse - [ OPTIONAL - 0 by default ] - An indicator of what day count method to use.
135 * 0 indicates the US method - Under the US method, if start_date is the last day of a month, the day of month of
136 * start_date is changed to 30 for the purposes of the calculation. Furthermore if end_date is the last day of a month
137 * and the day of the month of start_date is earlier than the 30th, end_date is changed to the first day of the month
138 * following end_date, otherwise the day of month of end_date is changed to 30.
139 * Any other value indicates the European method - Under the European method, any start_date or end_date that falls on
140 * the 31st of a month has its day of month changed to 30.
141 * @returns {number} of days between two dates
142 * @constructor
143 */
144var DAYS360 = function (startDate, endDate, methodToUse) {
145 ArgsChecker_1.ArgsChecker.checkLengthWithin(arguments, 2, 3, "DAYS360");
146 startDate = TypeConverter_1.TypeConverter.numberToMoment(TypeConverter_1.TypeConverter.firstValueAsDateNumber(startDate, true)); // tell firstValueAsDateNumber to coerce boolean
147 endDate = TypeConverter_1.TypeConverter.numberToMoment(TypeConverter_1.TypeConverter.firstValueAsDateNumber(endDate, true)); // tell firstValueAsDateNumber to coerce boolean
148 methodToUse = methodToUse ? TypeConverter_1.TypeConverter.firstValueAsBoolean(methodToUse) : false;
149 var smd = 31;
150 var emd = 31;
151 var sd = startDate.date();
152 var ed = endDate.date();
153 if (methodToUse) {
154 sd = (sd === 31) ? 30 : sd;
155 ed = (ed === 31) ? 30 : ed;
156 }
157 else {
158 if (startDate.month() === 1) {
159 smd = startDate.daysInMonth();
160 }
161 if (endDate.month() === 1) {
162 emd = endDate.daysInMonth();
163 }
164 sd = (sd === smd) ? 30 : sd;
165 if (sd === 30 || sd === smd) {
166 ed = (ed === emd) ? 30 : ed;
167 }
168 }
169 return 360 * (endDate.year() - startDate.year()) + 30 * (endDate.month() - startDate.month()) + (ed - sd);
170};
171exports.DAYS360 = DAYS360;
172/**
173 * Returns the month of the year a specific date falls in, in numeric format.
174 * @param date - The date from which to extract the month. Must be a reference to a cell containing a date, a
175 * function returning a date type, or a number.
176 * @returns {number} month of the year that the input date falls on.
177 * @constructor
178 */
179var MONTH = function (date) {
180 ArgsChecker_1.ArgsChecker.checkLength(arguments, 1, "MONTH");
181 date = TypeConverter_1.TypeConverter.firstValueAsDateNumber(date, true); // tell firstValueAsDateNumber to coerce boolean
182 if (date < 0) {
183 throw new Errors_1.NumError("Function MONTH parameter 1 value is " + date + ". It should be greater than or equal to 0.");
184 }
185 return TypeConverter_1.TypeConverter.numberToMoment(date).month() + 1;
186};
187exports.MONTH = MONTH;
188/**
189 * Returns the year specified by a given date.
190 * @param date - The date from which to calculate the year. Must be a cell reference to a cell containing a
191 * date, a function returning a date type, or a number.
192 * @returns {number} year of the input date
193 * @constructor
194 */
195var YEAR = function (date) {
196 ArgsChecker_1.ArgsChecker.checkLength(arguments, 1, "YEAR");
197 date = TypeConverter_1.TypeConverter.firstValueAsDateNumber(date, true); // tell firstValueAsDateNumber to coerce boolean
198 if (date < 0) {
199 throw new Errors_1.NumError("Function YEAR parameter 1 value is " + date + ". It should be greater than or equal to 0.");
200 }
201 return TypeConverter_1.TypeConverter.numberToMoment(date).year();
202};
203exports.YEAR = YEAR;
204/**
205 * Returns a number representing the day of the week of the date provided.
206 * @param date - The date for which to determine the day of the week. Must be a reference to a cell containing
207 * a date, a function returning a date type, or a number.
208 * @param offsetType - [ OPTIONAL - 1 by default ] - A number indicating which numbering system to use to represent
209 * weekdays. By default counts starting with Sunday = 1. If type is 1, days are counted from Sunday and the value of
210 * Sunday is 1, therefore the value of Saturday is 7. If type is 2, days are counted from Monday and the value of Monday
211 * is 1, therefore the value of Sunday is 7. If type is 3, days are counted from Monday and the value of Monday is 0,
212 * therefore the value of Sunday is 6.
213 * @returns {number} day of week
214 * @constructor
215 */
216var WEEKDAY = function (date, offsetType) {
217 ArgsChecker_1.ArgsChecker.checkLengthWithin(arguments, 1, 2, "WEEKDAY");
218 date = TypeConverter_1.TypeConverter.firstValueAsDateNumber(date, true); // tell firstValueAsDateNumber to coerce boolean
219 offsetType = offsetType ? TypeConverter_1.TypeConverter.firstValueAsNumber(offsetType) : 1;
220 if (date < 0) {
221 throw new Errors_1.NumError("Function WEEKDAY parameter 1 value is " + date + ". It should be greater than or equal to 0.");
222 }
223 var day = TypeConverter_1.TypeConverter.numberToMoment(date).day();
224 if (offsetType === 1) {
225 return day + 1;
226 }
227 else if (offsetType === 2) {
228 if (day === 0) {
229 return 7;
230 }
231 return day;
232 }
233 else if (offsetType === 3) {
234 if (day === 0) {
235 return 6;
236 }
237 return day - 1;
238 }
239 else {
240 throw new Errors_1.NumError("Function WEEKDAY parameter 2 value " + day + " is out of range.");
241 }
242};
243exports.WEEKDAY = WEEKDAY;
244/**
245 * Returns a number representing the week of the year where the provided date falls. When inputting the date, it is best
246 * to use the DATE function, as text values may return errors.
247 *
248 * Behind the scenes, there are two week numbering "systems" used for this function: System 1 - The first week of the
249 * year is considered to be the week containing January 1, which is numbered week 1. System 2 - The first week of the
250 * year is considered to be the week containing the first Thursday of the year, which is numbered as week 1. System 2 is
251 * the approach specified in ISO 8601, also known as the European system for numbering weeks.
252 *
253 * @param date - The date for which to determine the week number. Must be a reference to a cell containing a
254 * date, a function returning a date type, or a number.
255 * @param shiftType - [ OPTIONAL - default is 1 ] - A number representing the day that a week starts on as well as
256 * the system used for determining the first week of the year (1=Sunday, 2=Monday).
257 * @returns {number} representing week number of year.
258 * @constructor
259 */
260var WEEKNUM = function (date, shiftType) {
261 // Given a moment, an array of days of the week for shifting, will calculate the week number.
262 function calculateWeekNum(dm, shifterArray) {
263 var startOfYear = moment.utc(dm).startOf("year");
264 var weeksCount = 1;
265 var d = moment.utc(dm).startOf("year").add(6 - shifterArray[startOfYear.day()], "days");
266 while (d.isBefore(dm)) {
267 d.add(7, "days");
268 weeksCount++;
269 }
270 return weeksCount;
271 }
272 ArgsChecker_1.ArgsChecker.checkLengthWithin(arguments, 1, 2, "WEEKNUM");
273 date = TypeConverter_1.TypeConverter.firstValueAsDateNumber(date, true); // tell firstValueAsDateNumber to coerce boolean
274 shiftType = shiftType ? TypeConverter_1.TypeConverter.firstValueAsNumber(shiftType) : 1;
275 if (date < 0) {
276 throw new Errors_1.NumError("Function YEAR parameter 1 value is " + date + ". It should be greater than or equal to 0.");
277 }
278 var dm = TypeConverter_1.TypeConverter.numberToMoment(date);
279 var week = dm.week();
280 var dayOfWeek = dm.day(); // between 1 and 7, inclusively
281 if (shiftType === 1) {
282 // If this weekYear is not the same as the year, then we're technically in "week 53"
283 // See https://momentjs.com/docs/#/get-set/week-year/ for more info.
284 if (dm.weekYear() !== dm.year()) {
285 week = dm.weeksInYear() + 1;
286 }
287 return week;
288 }
289 else if (shiftType === 2 || shiftType === 11) {
290 if (dm.weekYear() !== dm.year()) {
291 week = dm.weeksInYear() + 1;
292 }
293 if (dayOfWeek === 0) { // sunday shift back
294 return week - 1;
295 }
296 return week;
297 }
298 else if (shiftType === 12) {
299 if (dm.weekYear() !== dm.year()) {
300 week = dm.weeksInYear() + 1;
301 }
302 if (dayOfWeek <= 1) { // sunday, monday shift back
303 return week - 1;
304 }
305 return week;
306 }
307 else if (shiftType === 13) {
308 if (dm.weekYear() !== dm.year()) {
309 week = dm.weeksInYear() + 1;
310 }
311 if (dayOfWeek <= 2) { // sunday, monday, tuesday shift back
312 return week - 1;
313 }
314 return week;
315 }
316 else if (shiftType === 14) {
317 return calculateWeekNum(dm, [3, 4, 5, 6, 0, 1, 2]);
318 }
319 else if (shiftType === 15) {
320 return calculateWeekNum(dm, [2, 3, 4, 5, 6, 0, 1]);
321 }
322 else if (shiftType === 16) {
323 return calculateWeekNum(dm, [1, 2, 3, 4, 5, 6, 0]);
324 }
325 else if (shiftType === 17) {
326 return calculateWeekNum(dm, [0, 1, 2, 3, 4, 5, 6]);
327 }
328 else if (shiftType === 21) {
329 return dm.isoWeek();
330 }
331 else {
332 throw new Errors_1.NumError("Function WEEKNUM parameter 2 value " + shiftType + " is out of range.");
333 }
334};
335exports.WEEKNUM = WEEKNUM;
336/**
337 * Calculates the number of days, months, or years between two dates.
338 * @param startDate - The start date to consider in the calculation. Must be a reference to a cell containing
339 * a DATE, a function returning a DATE type, or a number.
340 * @param endDate - The end date to consider in the calculation. Must be a reference to a cell containing a
341 * DATE, a function returning a DATE type, or a number.
342 * @param unit - A text abbreviation for unit of time. For example,"M" for month. Accepted values are "Y": the
343 * number of whole years between start_date and end_date, "M": the number of whole months between start_date and
344 * end_date, "D": the number of days between start_date and end_date, "MD": the number of days between start_date and
345 * end_date after subtracting whole months, "YM": the number of whole months between start_date and end_date after
346 * subtracting whole years, "YD": the number of days between start_date and end_date, assuming start_date and end_date
347 * were no more than one year apart.
348 * @returns {number} number of days, months, or years between two dates.
349 * @constructor
350 */
351var DATEDIF = function (startDate, endDate, unit) {
352 ArgsChecker_1.ArgsChecker.checkLength(arguments, 3, "DATEDIF");
353 startDate = TypeConverter_1.TypeConverter.firstValueAsDateNumber(startDate, true);
354 endDate = TypeConverter_1.TypeConverter.firstValueAsDateNumber(endDate, true);
355 unit = TypeConverter_1.TypeConverter.firstValueAsString(unit);
356 var unitClean = unit.toUpperCase();
357 var startMoment = TypeConverter_1.TypeConverter.numberToMoment(startDate);
358 var endMoment = TypeConverter_1.TypeConverter.numberToMoment(endDate);
359 if (startDate > endDate) {
360 throw new Errors_1.NumError("Function DATEDIF parameter 1 (" + startDate.toString() +
361 ") should be on or before Function DATEDIF parameter 2 (" + endDate.toString() + ").");
362 }
363 if (unitClean === "Y") {
364 return Math.floor(endMoment.diff(startMoment, "years"));
365 }
366 else if (unitClean === "M") {
367 return Math.floor(endMoment.diff(startMoment, "months"));
368 }
369 else if (unitClean === "D") {
370 return endDate - startDate;
371 }
372 else if (unitClean === "MD") {
373 var s = startMoment;
374 while (s.isBefore(endMoment)) {
375 s.add(1, "month");
376 }
377 s.subtract(1, "month");
378 var days = endMoment.diff(s, "days");
379 return s.date() === endMoment.date() ? 0 : days;
380 }
381 else if (unitClean === "YM") {
382 var s = startMoment;
383 while (s.isBefore(endMoment)) {
384 s.add(1, "year");
385 }
386 s.subtract(1, "year");
387 var months = Math.floor(endMoment.diff(s, "months"));
388 return months === 12 ? 0 : months;
389 }
390 else if (unitClean === "YD") {
391 var s = startMoment;
392 while (s.isBefore(endMoment)) {
393 s.add(1, "year");
394 }
395 s.subtract(1, "year");
396 var days = Math.floor(endMoment.diff(s, "days"));
397 return days >= 365 ? 0 : days;
398 }
399 else {
400 throw new Errors_1.NumError("Function DATEDIF parameter 3 value is " + unit +
401 ". It should be one of: 'Y', 'M', 'D', 'MD', 'YM', 'YD'.");
402 }
403};
404exports.DATEDIF = DATEDIF;
405/**
406 * Returns the number of years, including fractional years, between two dates using a specified day count convention.
407 *
408 * Further reading:
409 *
410 * * http://christian-fries.de/blog/files/2013-yearfrac.html
411 *
412 * * http://finmath.net/finmath-lib/
413 *
414 * @param startDate - The start date to consider in the calculation. Must be a reference to a cell
415 * containing a date, a function returning a date type, or a number.
416 * @param endDate - The end date to consider in the calculation. Must be a reference to a cell containing
417 * a date, a function returning a date type, or a number.
418 * @param dayCountConvention - [ OPTIONAL - 0 by default ] - An indicator of what day count method to
419 * use.
420 * @returns {number}the number of years, including fractional years, between two dates
421 * @constructor
422 */
423var YEARFRAC = function (startDate, endDate, dayCountConvention) {
424 ArgsChecker_1.ArgsChecker.checkLengthWithin(arguments, 2, 3, "YEARFRAC");
425 startDate = TypeConverter_1.TypeConverter.firstValueAsDateNumber(startDate, true);
426 endDate = TypeConverter_1.TypeConverter.firstValueAsDateNumber(endDate, true);
427 dayCountConvention = dayCountConvention ? TypeConverter_1.TypeConverter.firstValueAsNumber(dayCountConvention) : 0;
428 var s = TypeConverter_1.TypeConverter.numberToMoment(startDate);
429 var e = TypeConverter_1.TypeConverter.numberToMoment(endDate);
430 if (e.isBefore(s)) {
431 var me = moment.utc(e);
432 e = moment.utc(s);
433 s = me;
434 }
435 var syear = s.year();
436 var smonth = s.month();
437 var sday = s.date();
438 var eyear = e.year();
439 var emonth = e.month();
440 var eday = e.date();
441 var feb29Between = function (date1, date2) {
442 // Requires year2 == (year1 + 1) or year2 == year1
443 // Returns TRUE if February 29 is between the two dates (date1 may be February 29), with two possibilities:
444 // year1 is a leap year and date1 <= February 29 of year1
445 // year2 is a leap year and date2 > February 29 of year2
446 var mar1year1 = moment.utc(new Date(date1.year(), 2, 1));
447 if (moment.utc([date1.year()]).isLeapYear() && date1.diff(mar1year1) < 0 && date2.diff(mar1year1) >= 0) {
448 return true;
449 }
450 var mar1year2 = moment.utc(new Date(date2.year(), 2, 1));
451 if (moment.utc([date2.year()]).isLeapYear() && date2.diff(mar1year2) >= 0 && date1.diff(mar1year2) < 0) {
452 return true;
453 }
454 return false;
455 };
456 switch (dayCountConvention) {
457 // US (NASD) 30/360
458 case 0:
459 // Note: if eday == 31, it stays 31 if sday < 30
460 if (sday === 31 && eday === 31) {
461 sday = 30;
462 eday = 30;
463 }
464 else if (sday === 31) {
465 sday = 30;
466 }
467 else if (sday === 30 && eday === 31) {
468 eday = 30;
469 }
470 else if (smonth === 1 && emonth === 1 && s.daysInMonth() === sday && e.daysInMonth() === eday) {
471 sday = 30;
472 eday = 30;
473 }
474 else if (smonth === 1 && s.daysInMonth() === sday) {
475 sday = 30;
476 }
477 return Math.abs(((eday + emonth * 30 + eyear * 360) - (sday + smonth * 30 + syear * 360)) / 360);
478 // Actual/actual
479 case 1:
480 var ylength = 365;
481 if (syear === eyear || ((syear + 1) === eyear) && ((smonth > emonth) || ((smonth === emonth) && (sday >= eday)))) {
482 if (syear === eyear && moment.utc([syear]).isLeapYear()) {
483 ylength = 366;
484 }
485 else if (feb29Between(s, e) || (emonth === 1 && eday === 29)) {
486 ylength = 366;
487 }
488 return Math.abs((endDate - startDate) / ylength);
489 }
490 else {
491 var years = (eyear - syear) + 1;
492 var days = moment.utc([eyear + 1]).startOf("year").diff(moment.utc([syear]).startOf("year"), 'days');
493 var average = days / years;
494 return Math.abs((endDate - startDate) / average);
495 }
496 // Actual/360
497 case 2:
498 return Math.abs(e.diff(s, 'days') / 360);
499 // Actual/365
500 case 3:
501 return Math.abs(e.diff(s, 'days') / 365);
502 // European 30/360
503 case 4:
504 sday = sday === 31 ? 30 : sday;
505 eday = eday === 31 ? 30 : eday;
506 // Remarkably, do NOT change February 28 or February 29 at ALL
507 return Math.abs(((eday + emonth * 30 + eyear * 360) - (sday + smonth * 30 + syear * 360)) / 360);
508 }
509 throw new Errors_1.NumError("Function YEARFRAC parameter 3 value is " + dayCountConvention + ". Valid values are between 0 and 4 inclusive.");
510};
511exports.YEARFRAC = YEARFRAC;
512/**
513 * Returns the fraction of a 24-hour day the time represents.
514 * @param timeString - The string that holds the time representation. Eg: "10am", "10:10", "10:10am", "10:10:11",
515 * or "10:10:11am".
516 * @returns {number} representing the fraction of a 24-hour day
517 * @constructor
518 */
519var TIMEVALUE = function (timeString) {
520 ArgsChecker_1.ArgsChecker.checkLength(arguments, 1, "TIMEVALUE");
521 timeString = TypeConverter_1.TypeConverter.firstValueAsString(timeString);
522 try {
523 return TypeConverter_1.TypeConverter.stringToTimeNumber(timeString);
524 }
525 catch (e) {
526 throw new Errors_1.ValueError("TIMEVALUE parameter '" + timeString + "' cannot be parsed to date/time.");
527 }
528};
529exports.TIMEVALUE = TIMEVALUE;
530var MILLISECONDS_IN_DAY = 86400000;
531/**
532 * Returns the hour component of a specific time, in numeric format.
533 * @param time - The time from which to calculate the hour component. Must be a reference to a cell containing
534 * a date/time, a function returning a date/time type, or a number.
535 * @returns {number}
536 * @constructor
537 */
538var HOUR = function (time) {
539 ArgsChecker_1.ArgsChecker.checkLength(arguments, 1, "HOUR");
540 time = TypeConverter_1.TypeConverter.firstValueAsTimestampNumber(time);
541 if (time % 1 === 0) {
542 return 0;
543 }
544 var m = moment.utc([1900]).add(time * MILLISECONDS_IN_DAY, "milliseconds");
545 return m.hour();
546};
547exports.HOUR = HOUR;
548/**
549 * Returns the minute component of a specific time, in numeric format.
550 * @param time - The time from which to calculate the minute component. Must be a reference to a cell
551 * containing a date/time, a function returning a date/time type, or a number.
552 * @returns {number} minute of the time passed in.
553 * @constructor
554 */
555var MINUTE = function (time) {
556 ArgsChecker_1.ArgsChecker.checkLength(arguments, 1, "MINUTE");
557 time = TypeConverter_1.TypeConverter.firstValueAsTimestampNumber(time);
558 if (time % 1 === 0) {
559 return 0;
560 }
561 var m = moment.utc([1900]).add(time * MILLISECONDS_IN_DAY, "milliseconds");
562 return m.minute();
563};
564exports.MINUTE = MINUTE;
565/**
566 * Returns the second component of a specific time, in numeric format.
567 * @param time - The time from which to calculate the second component. Must be a reference to a cell
568 * containing a date/time, a function returning a date/time type, or a number.
569 * @returns {number} second component of a specific time.
570 * @constructor
571 */
572var SECOND = function (time) {
573 ArgsChecker_1.ArgsChecker.checkLength(arguments, 1, "SECOND");
574 time = TypeConverter_1.TypeConverter.firstValueAsTimestampNumber(time);
575 if (time % 1 === 0) {
576 return 0;
577 }
578 var m = moment.utc([1900]).add(time * MILLISECONDS_IN_DAY, "milliseconds");
579 return m.second();
580};
581exports.SECOND = SECOND;
582/**
583 * Returns the number of net working days between two provided days.
584 * @param startDate - The start date of the period from which to calculate the number of net working days.
585 * @param endDate - The end date of the period from which to calculate the number of net working days.
586 * @param holidays - [ OPTIONAL ] - A range or array constant containing the date serial numbers to consider
587 * holidays. The values provided within an array for holidays must be date serial number values, as returned by N or
588 * date values, as returned by DATE, DATEVALUE or TO_DATE. Values specified by a range should be standard date values or
589 * date serial numbers.
590 * @returns {number} the number of net working days between two provided dates.
591 * @constructor
592 */
593var NETWORKDAYS = function (startDate, endDate, holidays) {
594 ArgsChecker_1.ArgsChecker.checkLengthWithin(arguments, 2, 3, "NETWORKDAYS");
595 startDate = TypeConverter_1.TypeConverter.firstValueAsDateNumber(startDate, true);
596 endDate = TypeConverter_1.TypeConverter.firstValueAsDateNumber(endDate, true);
597 var hasHolidays = (holidays !== undefined);
598 var cleanHolidays = [];
599 if (hasHolidays) {
600 holidays = (holidays instanceof Array) ? holidays : [holidays];
601 if (holidays.length === 0) {
602 throw new Errors_1.RefError("Reference does not exist.");
603 }
604 for (var _i = 0, holidays_1 = holidays; _i < holidays_1.length; _i++) {
605 var holidayDateValue = holidays_1[_i];
606 if (typeof holidayDateValue === "number") {
607 cleanHolidays.push(holidayDateValue);
608 }
609 else {
610 throw new Errors_1.ValueError("NETWORKDAYS expects number values. But '" + holidayDateValue + "' is a " +
611 (typeof holidayDateValue) + " and cannot be coerced to a number.");
612 }
613 }
614 }
615 // Handle cases in which the start date is not before the end date.
616 var didSwap = startDate > endDate;
617 if (didSwap) {
618 var swap = endDate;
619 endDate = startDate;
620 startDate = swap;
621 }
622 var countMoment = moment.utc(TypeConverter_1.TypeConverter.numberToMoment(startDate));
623 var weekendDays = [6, 0]; // Default weekend_days.
624 var days = endDate - startDate + 1;
625 var networkDays = days;
626 var j = 0;
627 while (j < days) {
628 if (weekendDays.indexOf(countMoment.day()) >= 0) {
629 networkDays--;
630 }
631 else if (hasHolidays && cleanHolidays.indexOf(TypeConverter_1.TypeConverter.momentToDayNumber(countMoment)) > -1) {
632 networkDays--;
633 }
634 countMoment.add(1, 'days');
635 j++;
636 }
637 // If the we swapped the start and end date, the result should be a negative number of network days.
638 if (didSwap) {
639 return networkDays * -1;
640 }
641 return networkDays;
642};
643exports.NETWORKDAYS = NETWORKDAYS;
644/**
645 * Returns the number of networking days between two provided days excluding specified weekend days and holidays.
646 * @param startDate - The start date of the period from which to calculate the number of net working days.
647 * @param endDate - The end date of the period from which to calculate the number of net working days.
648 * @param weekend - [ OPTIONAL - 1 by default ] - A number or string representing which days of the week are
649 * considered weekends. String method: weekends can be specified using seven 0’s and 1’s, where the first number in the
650 * set represents Monday and the last number is for Sunday. A zero means that the day is a work day, a 1 means that the
651 * day is a weekend. For example, “0000011” would mean Saturday and Sunday are weekends. Number method: instead of using
652 * the string method above, a single number can be used. 1 = Saturday/Sunday are weekends, 2 = Sunday/Monday, and this
653 * pattern repeats until 7 = Friday/Saturday. 11 = Sunday is the only weekend, 12 = Monday is the only weekend, and this
654 * pattern repeats until 17 = Saturday is the only weekend.
655 * @param holidays - [ OPTIONAL ] - A range or array constant containing the dates to consider as holidays.
656 * The values provided within an array for holidays must be date serial number values, as returned by N or date values,
657 * as returned by DATE, DATEVALUE or TO_DATE. Values specified by a range should be standard date values or date serial
658 * numbers.
659 * @returns {number} of networking days between two provided days
660 * @constructor
661 */
662var NETWORKDAYS$INTL = function (startDate, endDate, weekend, holidays) {
663 ArgsChecker_1.ArgsChecker.checkLengthWithin(arguments, 2, 4, "NETWORKDAYS$INTL");
664 startDate = TypeConverter_1.TypeConverter.firstValueAsDateNumber(startDate, true);
665 endDate = TypeConverter_1.TypeConverter.firstValueAsDateNumber(endDate, true);
666 var weekendDays = [];
667 if (weekend !== undefined) {
668 weekend = TypeConverter_1.TypeConverter.firstValue(weekend);
669 if (typeof weekend === "string") {
670 if (!/^[0-1]{6,}$/.test(weekend)) {
671 throw new Errors_1.NumError("Function NETWORKDAYS.INTL parameter 3 requires a number in the format '0000011'. "
672 + "Actual value is '" + weekend + "'");
673 }
674 var ws = weekend.split("");
675 for (var i = 0; i < ws.length; i++) {
676 if (ws[i] === "1") {
677 weekendDays.push(i === 6 ? 0 : i + 1);
678 }
679 }
680 }
681 else if (typeof weekend === "number") {
682 switch (weekend) {
683 case 1:
684 weekendDays = [0, 6];
685 break;
686 case 2:
687 case 3:
688 case 4:
689 case 5:
690 case 6:
691 case 7:
692 weekendDays = [weekend, weekend - 1];
693 break;
694 case 11:
695 case 12:
696 case 13:
697 case 14:
698 case 15:
699 case 16:
700 case 17:
701 weekendDays = [weekend - 10];
702 break;
703 default:
704 throw new Errors_1.NumError("Function NETWORKDAYS.INTL parameter 3 requires a number in the range 1-7 or 11-17. "
705 + "Actual number is " + weekend + ".");
706 }
707 }
708 else {
709 throw new Errors_1.ValueError("Function NETWORKDAYS.INTL parameter 4 expects number values. But '" + weekend
710 + "' cannot be coerced to a number.");
711 }
712 }
713 else {
714 weekendDays = [0, 6];
715 }
716 var hasHolidays = holidays !== undefined;
717 var cleanHolidays = [];
718 if (hasHolidays) {
719 if (holidays === 0) {
720 throw new Errors_1.RefError("Reference does not exist.");
721 }
722 for (var _i = 0, holidays_2 = holidays; _i < holidays_2.length; _i++) {
723 var holidayDateValue = holidays_2[_i];
724 if (typeof holidayDateValue === "number") {
725 cleanHolidays.push(holidayDateValue);
726 }
727 else {
728 throw new Errors_1.ValueError("NETWORKDAYS.INTL expects number values. But '" + holidayDateValue + "' is a " +
729 (typeof holidayDateValue) + " and cannot be coerced to a number.");
730 }
731 }
732 }
733 // Handle cases in which the start date is not before the end date.
734 var didSwap = startDate > endDate;
735 if (didSwap) {
736 var swap = endDate;
737 endDate = startDate;
738 startDate = swap;
739 }
740 var countMoment = moment.utc(TypeConverter_1.TypeConverter.numberToMoment(startDate));
741 var days = endDate - startDate + 1;
742 var networkDays = days;
743 var j = 0;
744 while (j < days) {
745 if (weekendDays.indexOf(countMoment.day()) >= 0) {
746 networkDays--;
747 }
748 else if (hasHolidays && cleanHolidays.indexOf(TypeConverter_1.TypeConverter.momentToDayNumber(countMoment)) > -1) {
749 networkDays--;
750 }
751 countMoment.add(1, 'days');
752 j++;
753 }
754 // If the we swapped the start and end date, the result should be a negative number of network days.
755 if (didSwap) {
756 return networkDays * -1;
757 }
758 return networkDays;
759};
760exports.NETWORKDAYS$INTL = NETWORKDAYS$INTL;
761/**
762 * Returns the current date and time as a date value.
763 * @returns {number} representing the current date and time.
764 * @constructor
765 */
766var NOW = function () {
767 ArgsChecker_1.ArgsChecker.checkLength(arguments, 0, "NOW");
768 return TypeConverter_1.TypeConverter.momentToNumber(moment.utc());
769};
770exports.NOW = NOW;
771/**
772 * Returns the current date as a date value.
773 * @returns {number} today
774 * @constructor
775 */
776var TODAY = function () {
777 ArgsChecker_1.ArgsChecker.checkLength(arguments, 0, "TODAY");
778 return TypeConverter_1.TypeConverter.momentToNumber(moment.utc().startOf("day"));
779};
780exports.TODAY = TODAY;
781/**
782 * Converts a provided hour, minute, and second into a time. Will silently recalculate numeric time values which fall
783 * outside of valid ranges. Eg: TIME(24, 0, 0) is the same as TIME(0, 0, 0).
784 * @param hours - The hour component of the time.
785 * @param minutes - The minute component of the time.
786 * @param seconds - The second component of the time.
787 * @returns {number} time of day
788 * @constructor
789 */
790var TIME = function (hours, minutes, seconds) {
791 ArgsChecker_1.ArgsChecker.checkLength(arguments, 3, "TIME");
792 hours = Math.floor(TypeConverter_1.TypeConverter.firstValueAsNumber(hours));
793 minutes = Math.floor(TypeConverter_1.TypeConverter.firstValueAsNumber(minutes));
794 seconds = Math.floor(TypeConverter_1.TypeConverter.firstValueAsNumber(seconds));
795 var e = TypeConverter_1.TypeConverter.unitsToTimeNumber(hours, minutes, seconds);
796 if (e < 0) {
797 throw new Errors_1.NumError("TIME evaluates to an out of range value " + e + ". It should be greater than or equal to 0.");
798 }
799 return e;
800};
801exports.TIME = TIME;
802/**
803 * Calculates the end date after a specified number of working days.
804 * @param startDate - The date from which to begin counting.
805 * @param numberOfDays - The number of working days to advance from start_date. If negative, counts backwards. If
806 * not an integer, truncate.
807 * @param holidays - [ OPTIONAL ] - A range or array constant containing the dates to consider holidays. The
808 * values provided within an array for holidays must be date serial number values, as returned by N or date values, as
809 * returned by DATE, DATEVALUE or TO_DATE. Values specified by a range should be standard date values or date serial
810 * numbers.
811 * @returns {number} end date after a specified number of working days.
812 * @constructor
813 */
814var WORKDAY = function (startDate, numberOfDays, holidays) {
815 ArgsChecker_1.ArgsChecker.checkLengthWithin(arguments, 2, 3, "WORKDAY");
816 startDate = TypeConverter_1.TypeConverter.firstValueAsDateNumber(startDate, true);
817 numberOfDays = TypeConverter_1.TypeConverter.firstValueAsNumber(numberOfDays);
818 var hasHolidays = (holidays !== undefined);
819 var cleanHolidays = [];
820 if (hasHolidays !== undefined) {
821 if (holidays instanceof Array) {
822 if (holidays.length === 0) {
823 throw new Errors_1.RefError("Reference does not exist.");
824 }
825 for (var _i = 0, holidays_3 = holidays; _i < holidays_3.length; _i++) {
826 var holidayDateValue = holidays_3[_i];
827 if (typeof holidayDateValue === "number") {
828 cleanHolidays.push(holidayDateValue);
829 }
830 else {
831 throw new Errors_1.ValueError("WORKDAY expects number values. But '" + holidayDateValue + "' is a " +
832 (typeof holidayDateValue) + " and cannot be coerced to a number.");
833 }
834 }
835 }
836 else {
837 cleanHolidays.push(TypeConverter_1.TypeConverter.valueToNumber(holidays));
838 }
839 }
840 var weekendDays = [0, 6];
841 var countMoment = moment.utc(TypeConverter_1.TypeConverter.numberToMoment(startDate));
842 var j = 0;
843 while (j < numberOfDays) {
844 countMoment.add(1, 'days');
845 if (weekendDays.indexOf(countMoment.day()) < 0 && cleanHolidays.indexOf(TypeConverter_1.TypeConverter.momentToDayNumber(countMoment)) < 0) {
846 j++;
847 }
848 }
849 return TypeConverter_1.TypeConverter.momentToDayNumber(countMoment);
850};
851exports.WORKDAY = WORKDAY;
852/**
853 * Calculates the date after a specified number of workdays excluding specified weekend days and holidays.
854 * @param startDate - The date from which to begin counting.
855 * @param numberOfDays - The number of working days to advance from start_date. If negative, counts backwards.
856 * @param weekend - [ OPTIONAL - 1 by default ] - A number or string representing which days of the week are
857 * considered weekends. String method: weekends can be specified using seven 0’s and 1’s, where the first number in the
858 * set represents Monday and the last number is for Sunday. A zero means that the day is a work day, a 1 means that the
859 * day is a weekend. For example, “0000011” would mean Saturday and Sunday are weekends. Number method: instead of using
860 * the string method above, a single number can be used. 1 = Saturday/Sunday are weekends, 2 = Sunday/Monday, and this
861 * pattern repeats until 7 = Friday/Saturday. 11 = Sunday is the only weekend, 12 = Monday is the only weekend, and this
862 * pattern repeats until 17 = Saturday is the only weekend.
863 * @param holidays - [ OPTIONAL ] - A range or array constant containing the dates to consider holidays.
864 * @returns {number}
865 * @constructor
866 */
867var WORKDAY$INTL = function (startDate, numberOfDays, weekend, holidays) {
868 ArgsChecker_1.ArgsChecker.checkLengthWithin(arguments, 2, 3, "WORKDAY$INTL");
869 startDate = TypeConverter_1.TypeConverter.firstValueAsDateNumber(startDate, true);
870 numberOfDays = TypeConverter_1.TypeConverter.firstValueAsNumber(numberOfDays);
871 var weekendDays = [];
872 if (weekend !== undefined) {
873 weekend = TypeConverter_1.TypeConverter.firstValue(weekend);
874 if (typeof weekend === "string") {
875 if (!/^[0-1]{6,}$/.test(weekend)) {
876 throw new Errors_1.NumError("Function WORKDAY.INTL parameter 3 requires a number in the format '0000011'. "
877 + "Actual value is '" + weekend + "'");
878 }
879 var ws = weekend.split("");
880 for (var i = 0; i < ws.length; i++) {
881 if (ws[i] === "1") {
882 weekendDays.push(i === 6 ? 0 : i + 1);
883 }
884 }
885 }
886 else if (typeof weekend === "number") {
887 switch (weekend) {
888 case 1:
889 weekendDays = [0, 6];
890 break;
891 case 2:
892 case 3:
893 case 4:
894 case 5:
895 case 6:
896 case 7:
897 weekendDays = [weekend, weekend - 1];
898 break;
899 case 11:
900 case 12:
901 case 13:
902 case 14:
903 case 15:
904 case 16:
905 case 17:
906 weekendDays = [weekend - 10];
907 break;
908 default:
909 throw new Errors_1.NumError("Function WORKDAY.INTL parameter 3 requires a number in the range 1-7 or 11-17. "
910 + "Actual number is " + weekend + ".");
911 }
912 }
913 else {
914 throw new Errors_1.ValueError("Function WORKDAY.INTL parameter 4 expects number values. But '" + weekend
915 + "' cannot be coerced to a number.");
916 }
917 }
918 else {
919 weekendDays = [0, 6];
920 }
921 var hasHolidays = (holidays !== undefined);
922 var cleanHolidays = [];
923 if (hasHolidays) {
924 if (holidays instanceof Array) {
925 if (holidays.length === 0) {
926 throw new Errors_1.RefError("Reference does not exist.");
927 }
928 for (var _i = 0, holidays_4 = holidays; _i < holidays_4.length; _i++) {
929 var holidayDateValue = holidays_4[_i];
930 if (typeof holidayDateValue === "number") {
931 cleanHolidays.push(holidayDateValue);
932 }
933 else {
934 throw new Errors_1.ValueError("WORKDAY expects number values. But '" + holidayDateValue + "' is a " +
935 (typeof holidayDateValue) + " and cannot be coerced to a number.");
936 }
937 }
938 }
939 else {
940 cleanHolidays.push(TypeConverter_1.TypeConverter.valueToNumber(holidays));
941 }
942 }
943 var countMoment = moment.utc(TypeConverter_1.TypeConverter.numberToMoment(startDate));
944 var j = 0;
945 while (j < numberOfDays) {
946 countMoment.add(1, 'days');
947 if (weekendDays.indexOf(countMoment.day()) < 0 && cleanHolidays.indexOf(TypeConverter_1.TypeConverter.momentToDayNumber(countMoment)) < 0) {
948 j++;
949 }
950 }
951 return TypeConverter_1.TypeConverter.momentToDayNumber(countMoment);
952};
953exports.WORKDAY$INTL = WORKDAY$INTL;