Skip to main content

d8.js is a lightweight JavaScript library, that wraps JS's date object and extends it with lots of features that address common use cases when dealing with dates.

/*!
 * D8 - The next generation Date object for JavaScript
 *
 * https://github.com/panacodalabs/D8
 *
 * Copyright (C) 2011 by Panacoda GmbH
 * MIT Licensed
 *
 * @author Dominik Laubach
 * @version 0.4
 */

// ============================================================================
// Usage
//
// var d1 = new D8(); // var d1 = new Ø();
// var d2 = D8.create('12/31/2011');
//
// d1.format('mm/dd/yyyy');
// // 07/28/2011
//
// d2.isLeapYear();
// // false
//
// d8.isLeapYear(2012);
// // true
//
// d1.getCalendarWeek();
// // 30
//
// d1.timeBetween(d2);
// // 13423729316
//
// d1.timeBetween(d2, 'days');
// // 155
//
// d1.timeBetween(d2, 'days', false);
// // 155.3661575810185
//
// var d3 = d1.addHours(12);
//
// d3.format('mm/dd/yyyy');
// // 07/29/2011
//
// var d4 = d3.tomorrow();
// d4.format('mm/dd/yyyy');
// // 07/30/2011
//
// var dates = d1.getDatesOfCalendarWeek();
// for(var i = 0; i < dates.length; i++) {
//     dates[i].format('mm/dd/yyyy');
// }
// // 07/24/2011
// // 07/25/2011
// // 07/26/2011
// // 07/27/2011
// // 07/28/2011
// // 07/29/2011
// // 07/30/2011
//
// var d5 = D8.getDateByWeekdayAndCalendarWeek(4, 30, 2011);
// d5.format('mm/dd/yyyy');
// // 07/28/2011
// ============================================================================

var D8 = (function () {

    var date = null;

    function addDays(days) {
        var targetTimeStamp = days * 86400000;
        var targetDate = D8.create(this.date.getTime() + targetTimeStamp);
        var dstOffset;

        if (targetDate.isDST() === false && this.isDST() === true) {
            dstOffset = this.date.getTimezoneOffset() - targetDate.date.getTimezoneOffset();
            targetTimeStamp -= (dstOffset) * 60 * 1000;
        }
        else if (targetDate.isDST() === true && this.isDST() === false) {
            dstOffset = targetDate.date.getTimezoneOffset() - this.date.getTimezoneOffset();
            targetTimeStamp += (dstOffset) * 60 * 1000;
        }

        return this.addMilliseconds(targetTimeStamp);
    }

    function addHours(hours) {
        return this.addMilliseconds(hours * 60 * 60 * 1000);
    }

    function addMinutes(minutes) {
        return this.addMilliseconds(minutes * 60 * 1000);
    }

    function addSeconds(seconds) {
        return this.addMilliseconds(seconds * 1000);
    }

    function addMilliseconds(milliseconds) {
        var date = new D8();
        date.date = new Date(this.getTimestamp() + milliseconds);
        return date;
    }

    function tomorrow() {
        return this.addDays(1);
    }

    function yesterday() {
        return this.addDays(-1);
    }

    function getTimestamp() {
        return this.date.getTime();
    }

    function timeBetween(date, returnType, round) {
        var firstDateInMilliseconds = this.getTimestamp() ? this.getTimestamp() : null;
        var secondDateInMilliseconds = date.getTimestamp() ? date.getTimestamp() : null;
        round = !(round === false);

        if (firstDateInMilliseconds && secondDateInMilliseconds) {
            var timeBetween;
            switch (returnType) {
            case 'days':
                timeBetween = (secondDateInMilliseconds - firstDateInMilliseconds) / (24 * 60 * 60 * 1000);
                break;
            case 'hours':
                timeBetween = (secondDateInMilliseconds - firstDateInMilliseconds) / (60 * 60 * 1000);
                break;
            case 'minutes':
                timeBetween = (secondDateInMilliseconds - firstDateInMilliseconds) / (60 * 1000);
                break;
            case 'seconds':
                timeBetween = (secondDateInMilliseconds - firstDateInMilliseconds) / 1000;
                break;
            case 'milliseconds':
            default:
                timeBetween = (secondDateInMilliseconds - firstDateInMilliseconds);
                break;
            }
            if (round) {
                timeBetween = Math.round(timeBetween);
            }
            return timeBetween;
        }
        else if (firstDateInMilliseconds) {
            throw ('Error: Invalid d8 object passed when calling timeBetween()!');
        }
        else {
            throw ('Error: Trying to access timeBetween() on an invalid d8 object.');
        }
    }

    function getCalendarWeek() {
        year = parseInt(this.format('yyyy'));
        month = parseInt(this.format('m'));
        day = parseInt(this.format('d'));

        var a = Math.floor((14 - (month)) / 12);
        var y = year + 4800 - a;
        var m = (month) + (12 * a) - 3;
        var jd = day + Math.floor(((153 * m) + 2) / 5) + (365 * y) + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400) - 32045;
        var d4 = (jd + 31741 - (jd % 7)) % 146097 % 36524 % 1461;
        var L = Math.floor(d4 / 1460);
        var d1 = ((d4 - L) % 365) + L;
        var calendarWeek = Math.floor(d1 / 7) + 1;

        return calendarWeek;
    }

    function format(format, utc) {
        if (isNaN(this.getTimestamp())) {
            throw ('Error: Trying to access format() on an invalid d8 object.');
        }

        var token = /d{1,4}|D{1}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g;
        var timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g;
        var timezoneClip = /[^-+\dA-Z]/g;
        var pad = function (val, len) {
            val = String(val);
            len = len || 2;
            while (val.length < len) val = "0" + val;
            return val;
        };

        if (arguments.length == 1 && Object.prototype.toString.call(this.date) == "[object String]" && !/\d/.test(this.date)) {
            format = this.date;
            date = undefined;
        }

        var day_names = [
            "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
            "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
        ]
        var month_names = [
            "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
            "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
        ];

        var _ = utc ? "getUTC" : "get";
        var d = this.date[_ + "Date"]();
        var D = this.date[_ + "Day"]();
        var m = this.date[_ + "Month"]();
        var y = this.date[_ + "FullYear"]();
        var H = this.date[_ + "Hours"]();
        var Min = this.date[_ + "Minutes"]();
        var s = this.date[_ + "Seconds"]();
        var L = this.date[_ + "Milliseconds"]();
        var o = utc ? 0 : this.date.getTimezoneOffset();
        var flags = {
            d: d,
            dd: pad(d),
            ddd: day_names[D],
            dddd: day_names[D + 7],
            D: D,
            m: m + 1,
            mm: pad(m + 1),
            mmm: month_names[m],
            mmmm: month_names[m + 12],
            yy: String(y).slice(2),
            yyyy: y,
            h: H % 12 || 12,
            hh: pad(H % 12 || 12),
            H: H,
            HH: pad(H),
            M: Min,
            MM: pad(Min),
            s: s,
            ss: pad(s),
            l: pad(L, 3),
            L: pad(L > 99 ? Math.round(L / 10) : L),
            t: H < 12 ? "a" : "p",
            tt: H < 12 ? "am" : "pm",
            T: H < 12 ? "A" : "P",
            TT: H < 12 ? "AM" : "PM",
            Z: utc ? "UTC" : (String(this).match(timezone) || [""]).pop().replace(timezoneClip, ""),
            o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
            S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
        };

        var result = format.replace(token, function ($0) {
            return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
        });
        if (Number(result) == result) {
            return Number(result);
        }
        return result;
    }

    function getDatesOfCalendarWeek(startWeekOnMonday) {
        year = this.format('yyyy');
        var oneDayInTheFirstWeek = D8.create('01/01/' + year);

        var addRemoveNumber = -7;
        if (oneDayInTheFirstWeek.getCalendarWeek() > 51) {
            addRemoveNumber = 7;
        }
        while (oneDayInTheFirstWeek.getCalendarWeek() !== 1) {
            oneDayInTheFirstWeek = oneDayInTheFirstWeek.addDays(addRemoveNumber);
        }

        var oneDayInTheFirstWeekWeekDay = parseInt(oneDayInTheFirstWeek.format('D'));
        if (startWeekOnMonday) {
            oneDayInTheFirstWeek = oneDayInTheFirstWeekWeekDay == 1 ? oneDayInTheFirstWeek : oneDayInTheFirstWeek.addDays(8 - (oneDayInTheFirstWeekWeekDay == 0 ? 7 : oneDayInTheFirstWeekWeekDay));
        }
        else {
            oneDayInTheFirstWeek = oneDayInTheFirstWeekWeekDay == 0 ? oneDayInTheFirstWeek : oneDayInTheFirstWeek.addDays(7 - oneDayInTheFirstWeekWeekDay);
        }

        var calendarWeek = this.getCalendarWeek();

        var requiredWeek = oneDayInTheFirstWeek.addDays((calendarWeek - 1) * 7);

        var dates = [];
        for (var i = 0; i < 7; i++) {
            var date = requiredWeek.addDays(i);
            date = D8.create(date.format('mm') + '/' + date.format('dd') + '/' + date.format('yyyy'));
            dates.push(date);
        }

        return dates;
    }

    function isLeapYear() {
        var year = Number(this.format('yyyy'));
        return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
    }

    // Daylight Saving Time aka Summertime
    function isDST() {
        var y = D8(new Date()).format('yyyy');

        if (new D8('01.01.' + y).date.getTimezoneOffset() == this.date.getTimezoneOffset()) {
            return false;
        }
        else {
            return true;
        }
    }

    return {
        date: new Date(),
        addDays: addDays,
        addHours: addHours,
        addMinutes: addMinutes,
        addSeconds: addSeconds,
        addMilliseconds: addMilliseconds,
        timeBetween: timeBetween,
        yesterday: yesterday,
        tomorrow: tomorrow,
        getTimestamp: getTimestamp,
        getCalendarWeek: getCalendarWeek,
        format: format,
        getDatesOfCalendarWeek: getDatesOfCalendarWeek,
        isLeapYear: isLeapYear,
        isDST: isDST
    };

});

D8.now = function () {
    var date = new D8();
    date.date = new Date();
    return date;
}

D8.getCalendarWeek = (function (year, month, day) {
    var a = Math.floor((14 - (month)) / 12);
    var y = year + 4800 - a;
    var m = (month) + (12 * a) - 3;
    var jd = day + Math.floor(((153 * m) + 2) / 5) + (365 * y) + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400) - 32045;
    var d4 = (jd + 31741 - (jd % 7)) % 146097 % 36524 % 1461;
    var L = Math.floor(d4 / 1460);
    var d1 = ((d4 - L) % 365) + L;
    var calendarWeek = Math.floor(d1 / 7) + 1;

    return calendarWeek;
});

D8.getCalendarWeekByDate = (function (d) {
    return D8.getCalendarWeek(d.format('yyyy'), d.format('mm'), d.format('dd'));
});

D8.getDatesOfCalendarWeek = (function (calendarWeek, startWeekOnMonday, year) {
    var firstDayOfYear = D8.create('01.01.' + year);

    var inLastYear = firstDayOfYear.addDays(-8);

    var cw = D8.getCalendarWeekByDate(inLastYear);

    while (cw !== 1) {
        inLastYear = inLastYear.addDays(1);
        cw = D8.getCalendarWeekByDate(inLastYear);
    }

    if (!startWeekOnMonday) {
        inLastYear = inLastYear.addDays(-1);
    }

    var requiredWeek = inLastYear.addDays((calendarWeek - 1) * 7);

    var dates = [];
    for (var i = 0; i < 7; i++) {
        var date = requiredWeek.addDays(i);
        date = this.create(date.format('mm') + '/' + date.format('dd') + '/' + date.format('yyyy'));
        dates.push(date);
    }

    return dates;
});

D8.getDateByWeekdayAndCalendarWeek = (function (weekDay, calendarWeek, year) {
    if (calendarWeek && !isNaN(calendarWeek) && ((weekDay && !isNaN(weekDay)) || weekDay === 0)) {
        var dates = this.getDatesOfCalendarWeek(calendarWeek, false, year);
        if (dates && dates.length > 0 && dates[weekDay]) {
            return dates[weekDay];
        }
        else {
            throw ('Error: Day ' + weekDay + ' of calendar week ' + calendarWeek + ' could not be found!');
        }
    }
    else {
        throw ('Error: Please pass a valid calendarWeek and a valid day of the week!');
    }
});

D8.create = function (dateString) {
    var date, regexResult, milliseconds = typeof dateString === 'number' ? dateString : null;

    if (!milliseconds) {
        regexResult = /(\d{1,2})\.(\d{1,2})\.(\d{2,4})/.exec(dateString);
        if (regexResult && regexResult[1] && regexResult[2] && regexResult[3]) {
            date = dateString.split(' ');
            dateString = regexResult[2] + '/' + regexResult[1] + '/' + regexResult[3] + (date[1] ? ' ' + date[1] : '');
        }

        try {
            milliseconds = Date.parse(dateString);
        }
        catch (e) {
            throw 'Error: Invalid dateString \'' + dateString + '\' passed to create().';
        }
    }

    if (!milliseconds) {
        return D8.now();
    }

    date = new D8();
    date.date = new Date(milliseconds);
    return date;
};

D8.isLeapYear = (function (year) {
    if (year) {
        year = Number(year);
        if (year) {
            return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
        }
        else {
            throw 'No valid year (number) passed.';
        }
    }
    else {
        throw 'No year passed.';
    }
});

// Daylight Saving Time aka Summertime
D8.isDST = (function (dateString) {
    var date = D8.create(dateString);
    if (new Date(1).getTimezoneOffset() == date.date.getTimezoneOffset()) {
        return false;
    }
    else {
        return true;
    }
});

var Ø = D8;