spreadsheet
typeScript/javascript spreadsheet parser, with formulas.
git clone https://git.vogt.world/spreadsheet.git
Log | Files | README.md
← All files
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;