function fastDateFactory() { var DAYS_IN_YEAR = 365; var DAYS_IN_LEAP_YEAR = 366; var DAYS_IN_4_YEARS = DAYS_IN_LEAP_YEAR + DAYS_IN_YEAR * 3; var DAYS_IN_OTHER_FIRST_4_YEARS = DAYS_IN_YEAR * 4; var DAYS_IN_FIRST_100_YEARS = DAYS_IN_4_YEARS * 25; var DAYS_IN_NEXT_100_YEARS = DAYS_IN_OTHER_FIRST_4_YEARS + DAYS_IN_4_YEARS * 24; var DAYS_IN_400_YEARS = DAYS_IN_FIRST_100_YEARS + DAYS_IN_NEXT_100_YEARS * 3; var MONTH_DAYS_AGGREGATED_LEAP = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]; var MONTH_DAYS_AGGREGATED = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]; var FastDate; function stringToYMD(stringDate) { var dateParts; if (!(dateParts = /^(\d{4})-(\d{2})-(\d{2})$/.exec(stringDate))) { throw new Error('Date format invalid'); } return { year: parseInt(dateParts[1], 10), month: parseInt(dateParts[2], 10), day: parseInt(dateParts[3], 10) }; } function daysFromZeroPoint(ymd) { var yearRest = (ymd.year) % 400; /*jshint bitwise: false*/ var leapYears = ((ymd.year - yearRest) / 400) * 97 + ((yearRest + 3) >> 2) - (yearRest > 300 ? 3 : yearRest > 200 ? 2 : yearRest > 100 ? 1 : 0); /*jshint bitwise: true*/ return leapYears * DAYS_IN_LEAP_YEAR + (ymd.year - leapYears) * DAYS_IN_YEAR + (isLeap(ymd.year) ? MONTH_DAYS_AGGREGATED_LEAP[ymd.month - 1] : MONTH_DAYS_AGGREGATED[ymd.month - 1]) + ymd.day - 1; } function isLeap(year) { return (year % 4 === 0) && (year % 100 !== 0) || (year % 400 === 0); } FastDate = function(startDate) { if (this instanceof FastDate) { this.offsetFromZeroPoint = daysFromZeroPoint(stringToYMD(startDate)); } else { return new FastDate(startDate); } }; _.extend( FastDate.prototype, { stringToYMD: function(stringDate) { return stringToYMD(stringDate); }, stringToFastDate: function(stringDate) { return daysFromZeroPoint(this.stringToYMD(stringDate)) - this.offsetFromZeroPoint; }, fastDateToYMD: function(index) { var offset = index + this.offsetFromZeroPoint; var rest; var year; var month = 1; var inFirst100 = false; var inOtherFirst4 = false; var monthDays; rest = offset % DAYS_IN_400_YEARS; year = (offset - rest) / DAYS_IN_400_YEARS * 400; offset = rest; if (rest) { if (offset < DAYS_IN_FIRST_100_YEARS) { inFirst100 = true; } else { rest = (offset - DAYS_IN_FIRST_100_YEARS) % DAYS_IN_NEXT_100_YEARS; year += 100 + (offset - DAYS_IN_FIRST_100_YEARS - rest) / DAYS_IN_NEXT_100_YEARS * 100; offset = rest; } if (rest) { if (!inFirst100) { if (offset < DAYS_IN_OTHER_FIRST_4_YEARS) { inOtherFirst4 = true; } else { rest = (offset - DAYS_IN_OTHER_FIRST_4_YEARS) % DAYS_IN_4_YEARS; year += 4 + (offset - DAYS_IN_OTHER_FIRST_4_YEARS - rest) / DAYS_IN_4_YEARS * 4; offset = rest; } } else { rest = offset % DAYS_IN_4_YEARS; year += (offset - rest) / DAYS_IN_4_YEARS * 4; offset = rest; } if (rest) { if (!inOtherFirst4) { if (offset >= DAYS_IN_LEAP_YEAR) { rest = (offset - DAYS_IN_LEAP_YEAR) % DAYS_IN_YEAR; year += 1 + (offset - DAYS_IN_LEAP_YEAR - rest) / DAYS_IN_YEAR; } } else { rest = offset % DAYS_IN_YEAR; year += (offset - rest) / DAYS_IN_YEAR; } } } } monthDays = isLeap(year) ? MONTH_DAYS_AGGREGATED_LEAP : MONTH_DAYS_AGGREGATED; while (rest > monthDays[month]) { month++; } rest -= monthDays[month - 1] - 1; return { year: year, month: month, day: rest }; }, fastDateToSting: function(index) { var ymd = this.fastDateToYMD(index); return ('000' + ymd.year).substr(-4) + '-' + ('0' + ymd.month).substr(-2) + '-' + ('0' + ymd.day).substr(-2); }, fastDateToDayOfWeek: function(index) { return (index + this.offsetFromZeroPoint + 6) % 7; }, isFastDateWeekend: function(index) { return (index + this.offsetFromZeroPoint) % 7 < 2; } } ); return FastDate; }