var error = require('./utils/error'); var utils = require('./utils/common'); var d1900 = new Date(Date.UTC(1900, 0, 1)); var WEEK_STARTS = [ undefined, 0, 1, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, 1, 2, 3, 4, 5, 6, 0 ]; var WEEK_TYPES = [ [], [1, 2, 3, 4, 5, 6, 7], [7, 1, 2, 3, 4, 5, 6], [6, 0, 1, 2, 3, 4, 5], [], [], [], [], [], [], [], [7, 1, 2, 3, 4, 5, 6], [6, 7, 1, 2, 3, 4, 5], [5, 6, 7, 1, 2, 3, 4], [4, 5, 6, 7, 1, 2, 3], [3, 4, 5, 6, 7, 1, 2], [2, 3, 4, 5, 6, 7, 1], [1, 2, 3, 4, 5, 6, 7] ]; var WEEKEND_TYPES = [ [], [6, 0], [0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6], undefined, undefined, undefined, [0, 0], [1, 1], [2, 2], [3, 3], [4, 4], [5, 5], [6, 6] ]; exports.DATE = function(year, month, day) { var result; year = utils.parseNumber(year); month = utils.parseNumber(month); day = utils.parseNumber(day); if (utils.anyIsError(year, month, day)) { result = error.value; } else if (year < 0 || month < 0 || day < 0) { result = error.num; } else { result = new Date(year, month - 1, day); } return result; }; exports.DATEVALUE = function(date_text) { var modifier = 2; var date; if (typeof date_text !== 'string') { return error.value; } date = Date.parse(date_text); if (isNaN(date)) { return error.value; } if (date <= -2203891200000) { modifier = 1; } return Math.ceil((date - d1900) / 86400000) + modifier; }; exports.DAY = function(serial_number) { var date = utils.parseDate(serial_number); if (date instanceof Error) { return date; } return date.getDate(); }; exports.DAYS = function(end_date, start_date) { end_date = utils.parseDate(end_date); start_date = utils.parseDate(start_date); if (end_date instanceof Error) { return end_date; } if (start_date instanceof Error) { return start_date; } return serial(end_date) - serial(start_date); }; exports.DAYS360 = function(start_date, end_date, method) { method = utils.parseBool(method); start_date = utils.parseDate(start_date); end_date = utils.parseDate(end_date); if (start_date instanceof Error) { return start_date; } if (end_date instanceof Error) { return end_date; } if (method instanceof Error) { return method; } var sm = start_date.getMonth(); var em = end_date.getMonth(); var sd, ed; if (method) { sd = start_date.getDate() === 31 ? 30 : start_date.getDate(); ed = end_date.getDate() === 31 ? 30 : end_date.getDate(); } else { var smd = new Date(start_date.getFullYear(), sm + 1, 0).getDate(); var emd = new Date(end_date.getFullYear(), em + 1, 0).getDate(); sd = start_date.getDate() === smd ? 30 : start_date.getDate(); if (end_date.getDate() === emd) { if (sd < 30) { em++; ed = 1; } else { ed = 30; } } else { ed = end_date.getDate(); } } return 360 * (end_date.getFullYear() - start_date.getFullYear()) + 30 * (em - sm) + (ed - sd); }; exports.EDATE = function(start_date, months) { start_date = utils.parseDate(start_date); if (start_date instanceof Error) { return start_date; } if (isNaN(months)) { return error.value; } months = parseInt(months, 10); start_date.setMonth(start_date.getMonth() + months); return serial(start_date); }; exports.EOMONTH = function(start_date, months) { start_date = utils.parseDate(start_date); if (start_date instanceof Error) { return start_date; } if (isNaN(months)) { return error.value; } months = parseInt(months, 10); return serial(new Date(start_date.getFullYear(), start_date.getMonth() + months + 1, 0)); }; exports.HOUR = function(serial_number) { serial_number = utils.parseDate(serial_number); if (serial_number instanceof Error) { return serial_number; } return serial_number.getHours(); }; exports.INTERVAL = function (second) { if (typeof second !== 'number' && typeof second !== 'string') { return error.value; } else { second = parseInt(second, 10); } var year = Math.floor(second/946080000); second = second%946080000; var month = Math.floor(second/2592000); second = second%2592000; var day = Math.floor(second/86400); second = second%86400; var hour = Math.floor(second/3600); second = second%3600; var min = Math.floor(second/60); second = second%60; var sec = second; year = (year > 0) ? year + 'Y' : ''; month = (month > 0) ? month + 'M' : ''; day = (day > 0) ? day + 'D' : ''; hour = (hour > 0) ? hour + 'H' : ''; min = (min > 0) ? min + 'M' : ''; sec = (sec > 0) ? sec + 'S' : ''; return 'P' + year + month + day + 'T' + hour + min + sec; }; exports.ISOWEEKNUM = function(date) { date = utils.parseDate(date); if (date instanceof Error) { return date; } date.setHours(0, 0, 0); date.setDate(date.getDate() + 4 - (date.getDay() || 7)); var yearStart = new Date(date.getFullYear(), 0, 1); return Math.ceil((((date - yearStart) / 86400000) + 1) / 7); }; exports.MINUTE = function(serial_number) { serial_number = utils.parseDate(serial_number); if (serial_number instanceof Error) { return serial_number; } return serial_number.getMinutes(); }; exports.MONTH = function(serial_number) { serial_number = utils.parseDate(serial_number); if (serial_number instanceof Error) { return serial_number; } return serial_number.getMonth() + 1; }; exports.NETWORKDAYS = function(start_date, end_date, holidays) { return this.NETWORKDAYS.INTL(start_date, end_date, 1, holidays); }; exports.NETWORKDAYS.INTL = function(start_date, end_date, weekend, holidays) { start_date = utils.parseDate(start_date); if (start_date instanceof Error) { return start_date; } end_date = utils.parseDate(end_date); if (end_date instanceof Error) { return end_date; } if (weekend === undefined) { weekend = WEEKEND_TYPES[1]; } else { weekend = WEEKEND_TYPES[weekend]; } if (!(weekend instanceof Array)) { return error.value; } if (holidays === undefined) { holidays = []; } else if (!(holidays instanceof Array)) { holidays = [holidays]; } for (var i = 0; i < holidays.length; i++) { var h = utils.parseDate(holidays[i]); if (h instanceof Error) { return h; } holidays[i] = h; } var days = (end_date - start_date) / (1000 * 60 * 60 * 24) + 1; var total = days; var day = start_date; for (i = 0; i < days; i++) { var d = (new Date().getTimezoneOffset() > 0) ? day.getUTCDay() : day.getDay(); var dec = false; if (d === weekend[0] || d === weekend[1]) { dec = true; } for (var j = 0; j < holidays.length; j++) { var holiday = holidays[j]; if (holiday.getDate() === day.getDate() && holiday.getMonth() === day.getMonth() && holiday.getFullYear() === day.getFullYear()) { dec = true; break; } } if (dec) { total--; } day.setDate(day.getDate() + 1); } return total; }; exports.NOW = function() { return new Date(); }; exports.SECOND = function(serial_number) { serial_number = utils.parseDate(serial_number); if (serial_number instanceof Error) { return serial_number; } return serial_number.getSeconds(); }; exports.TIME = function(hour, minute, second) { hour = utils.parseNumber(hour); minute = utils.parseNumber(minute); second = utils.parseNumber(second); if (utils.anyIsError(hour, minute, second)) { return error.value; } if (hour < 0 || minute < 0 || second < 0) { return error.num; } return (3600 * hour + 60 * minute + second) / 86400; }; exports.TIMEVALUE = function(time_text) { time_text = utils.parseDate(time_text); if (time_text instanceof Error) { return time_text; } return (3600 * time_text.getHours() + 60 * time_text.getMinutes() + time_text.getSeconds()) / 86400; }; exports.TODAY = function() { return new Date(); }; exports.WEEKDAY = function(serial_number, return_type) { serial_number = utils.parseDate(serial_number); if (serial_number instanceof Error) { return serial_number; } if (return_type === undefined) { return_type = 1; } var day = serial_number.getDay(); return WEEK_TYPES[return_type][day]; }; exports.WEEKNUM = function(serial_number, return_type) { serial_number = utils.parseDate(serial_number); if (serial_number instanceof Error) { return serial_number; } if (return_type === undefined) { return_type = 1; } if (return_type === 21) { return this.ISOWEEKNUM(serial_number); } var week_start = WEEK_STARTS[return_type]; var jan = new Date(serial_number.getFullYear(), 0, 1); var inc = jan.getDay() < week_start ? 1 : 0; jan -= Math.abs(jan.getDay() - week_start) * 24 * 60 * 60 * 1000; return Math.floor(((serial_number - jan) / (1000 * 60 * 60 * 24)) / 7 + 1) + inc; }; exports.WORKDAY = function(start_date, days, holidays) { return this.WORKDAY.INTL(start_date, days, 1, holidays); }; exports.WORKDAY.INTL = function(start_date, days, weekend, holidays) { start_date = utils.parseDate(start_date); if (start_date instanceof Error) { return start_date; } days = utils.parseNumber(days); if (days instanceof Error) { return days; } if (days < 0) { return error.num; } if (weekend === undefined) { weekend = WEEKEND_TYPES[1]; } else { weekend = WEEKEND_TYPES[weekend]; } if (!(weekend instanceof Array)) { return error.value; } if (holidays === undefined) { holidays = []; } else if (!(holidays instanceof Array)) { holidays = [holidays]; } for (var i = 0; i < holidays.length; i++) { var h = utils.parseDate(holidays[i]); if (h instanceof Error) { return h; } holidays[i] = h; } var d = 0; while (d < days) { start_date.setDate(start_date.getDate() + 1); var day = start_date.getDay(); if (day === weekend[0] || day === weekend[1]) { continue; } for (var j = 0; j < holidays.length; j++) { var holiday = holidays[j]; if (holiday.getDate() === start_date.getDate() && holiday.getMonth() === start_date.getMonth() && holiday.getFullYear() === start_date.getFullYear()) { d--; break; } } d++; } return start_date; }; exports.YEAR = function(serial_number) { serial_number = utils.parseDate(serial_number); if (serial_number instanceof Error) { return serial_number; } return serial_number.getFullYear(); }; function isLeapYear(year) { return new Date(year, 1, 29).getMonth() === 1; } // TODO : Use DAYS ? function daysBetween(start_date, end_date) { return Math.ceil((end_date - start_date) / 1000 / 60 / 60 / 24); } exports.YEARFRAC = function(start_date, end_date, basis) { start_date = utils.parseDate(start_date); if (start_date instanceof Error) { return start_date; } end_date = utils.parseDate(end_date); if (end_date instanceof Error) { return end_date; } basis = basis || 0; var sd = start_date.getDate(); var sm = start_date.getMonth() + 1; var sy = start_date.getFullYear(); var ed = end_date.getDate(); var em = end_date.getMonth() + 1; var ey = end_date.getFullYear(); switch (basis) { case 0: // US (NASD) 30/360 if (sd === 31 && ed === 31) { sd = 30; ed = 30; } else if (sd === 31) { sd = 30; } else if (sd === 30 && ed === 31) { ed = 30; } return ((ed + em * 30 + ey * 360) - (sd + sm * 30 + sy * 360)) / 360; case 1: // Actual/actual var feb29Between = function(date1, date2) { var year1 = date1.getFullYear(); var mar1year1 = new Date(year1, 2, 1); if (isLeapYear(year1) && date1 < mar1year1 && date2 >= mar1year1) { return true; } var year2 = date2.getFullYear(); var mar1year2 = new Date(year2, 2, 1); return (isLeapYear(year2) && date2 >= mar1year2 && date1 < mar1year2); }; var ylength = 365; if (sy === ey || ((sy + 1) === ey) && ((sm > em) || ((sm === em) && (sd >= ed)))) { if ((sy === ey && isLeapYear(sy)) || feb29Between(start_date, end_date) || (em === 1 && ed === 29)) { ylength = 366; } return daysBetween(start_date, end_date) / ylength; } var years = (ey - sy) + 1; var days = (new Date(ey + 1, 0, 1) - new Date(sy, 0, 1)) / 1000 / 60 / 60 / 24; var average = days / years; return daysBetween(start_date, end_date) / average; case 2: // Actual/360 return daysBetween(start_date, end_date) / 360; case 3: // Actual/365 return daysBetween(start_date, end_date) / 365; case 4: // European 30/360 return ((ed + em * 30 + ey * 360) - (sd + sm * 30 + sy * 360)) / 360; } }; function serial(date) { var addOn = (date > -2203891200000) ? 2 : 1; return Math.ceil((date - d1900) / 86400000) + addOn; }