0

So I know this has been discussed as a problem before, but I'm looking more for some useful input really. I'm writing a calendar, and needed to add dates, and I came across an earlier thread (Add days to JavaScript Date), but it raised more quetions than answers. So I went away to code my own, and I'm close. This is what I've got:

Date.prototype.add = function(period, value, ignoreDaylightSaving)
{
    var Day = this.getDate();
    var Month = this.getMonth();
    var Year = this.getFullYear();
    var Hours = this.getHours();
    var Mins = this.getMinutes();
    var Secs = this.getSeconds();
    var MilliSecs = this.getMilliseconds();
    var TestDate;
    var IgnoreDS = false;
    var TZOffset1 = 0;
    var TZOffset2 = 0;

    if (typeof(ignoreDaylightSaving) == "boolen")
    {
        IgnoreDS = ignoreDaylightSaving;
    }

    switch (period.toUpperCase())
    {
    case "D":
        Day += value;
        break;

    case "M":
        Month += value;
        break;

    case "Y":
        Year += value;
        break;

    case "H":
        Hours += value;
        break;

    case "N":
        Mins += value;
        break;

    case "S":
        Secs += value;
        break;

    case "MS":
        MilliSecs += value;
    }

    // Standardise Daylight Saving cut off.
    if (IgnoreDS == false)
    {
        TestDate = new Date(Year, Month, Day, Hours, Mins, Secs, MilliSecs);
        TZOffset1 = this.getTimezoneOffset();
        TZOffset2 = TestDate.getTimezoneOffset();

        if (value > 0)
        {
            if (this > TestDate)
            {
                if (TZOffset1 == TZOffset2)
                {
                    Hours += 1;
                }
            }
        } else {
            if (this < TestDate)
            {
                if (TZOffset1 == TZOffset2)
                {
                    Hours -= 1;
                }
            }
        }
    }

    return(new Date(Year, Month, Day, Hours, Mins, Secs, MilliSecs));
}

It relies on Javascript to do the calcs for me. Internally, Dates and Times are just numbers, so if you pass a set of values, whether they are in range or not, javascript just multiplies them all out to get the internal number. Only when you apply Date functions to that number will it be converted into a date we recognise.

So my logic:

* split down the Date into its components
* add the value you want to one of the components
* create a new date from the components.

I've tested it with leap years IE and Chrome.

Daylight Saving is another matter. In the UK, British Summer Time begins 1am 30 March 2014. If I set a date of 30 March 2014 00:59:00 and add 2 minutes, Chrome gives me '30 March 2014 00:01:00' (GMT Standard Time), but IE gives me '30 March 2014 02:01:00' (GMT Summer Time).

Both browsers effectively say "you can't have a time of 01:01 in the morning of 30 March 2014 as it doesn't exist". IE advances to the next time (in effect adjusting the clock), whereas Chrome simply knocks you back an hour and keeps you in GMT Standard Time. When Summer Time ends they do it the opposite way round - IE keeps you in Summer Time and Chrome adjusts the clock for you. I've coded around this for going from GMT Standard to GMT Summertime (see the block under Standardise Daylight Saving cut off comment).

It's hard to code around the inconsistency in moving from GMT Summertime to GMT Standard though. I even wrote some javascript to find the change over dates by going through each month, then day then hour. But IE gives a different hour to Chrome. The code:

Date.prototype.getDaylightSavingDate = function(year)
{
    var DSStart = new Date();
    var DSEnd = new Date();

    var MonthLoop;
    var d1 = new Date();
    var d2 = new Date();
    var ThreshholdTZOffsets = new Array();
    var ThreshholdMonths = new Array();
    var ThreshholdDays = new Array();
    var ThreshholdHours = new Array();
    var ThisTZOffset;
    var PrevTZOffset;
    var THCount;


    THCount = 0;
    for (DateLoop=0; DateLoop < 12; DateLoop++)
    {
        d1 = new Date(year, DateLoop, 1);
        ThisTZOffset = d1.getTimezoneOffset();

        d2 = new Date(year, DateLoop-1, 1)
        PrevTZOffset = d2.getTimezoneOffset();

        if (PrevTZOffset != ThisTZOffset)
        {
            ThreshholdMonths[THCount] = DateLoop-1;
            ThreshholdTZOffsets[THCount] = ThisTZOffset;
            THCount += 1;
        }

        PrevTZOffset = ThisTZOffset
    }

    for (DateLoop=0; DateLoop<ThreshholdMonths.length; DateLoop++)
    {
        for (DayLoop=1; DayLoop < 32; DayLoop++)
        {
            d1 = new Date(year, ThreshholdMonths[DateLoop], DayLoop);
            ThisTZOffset = d1.getTimezoneOffset();

            d2 = new Date(year,  ThreshholdMonths[DateLoop], DayLoop-1);
            PrevTZOffset = d2.getTimezoneOffset();

            if (PrevTZOffset != ThisTZOffset)
            {
                ThreshholdDays[DateLoop] = DayLoop-1;
            }

            PrevTZOffset = ThisTZOffset
        }
    }

    for (DateLoop=0; DateLoop<ThreshholdMonths.length; DateLoop++)
    {
        for (HourLoop=0; HourLoop < 23; HourLoop++)
        {
            d1 = new Date(year, ThreshholdMonths[DateLoop], ThreshholdDays[DateLoop], HourLoop, 0, 0);
            ThisTZOffset = d1.getTimezoneOffset();

            d2 = new Date(year, ThreshholdMonths[DateLoop], ThreshholdDays[DateLoop], HourLoop-1, 0, 0);
            PrevTZOffset = d2.getTimezoneOffset();

            if (PrevTZOffset != ThisTZOffset)
            {
                ThreshholdHours[DateLoop] = HourLoop;
            }

            PrevTZOffset = ThisTZOffset
        }
    }

    if (ThreshholdHours.length == 2)
    {
        DSStart = new Date(year, ThreshholdMonths[0], ThreshholdDays[0], ThreshholdHours[0],0,0,0);
        DSEnd = new Date(year, ThreshholdMonths[1], ThreshholdDays[1], ThreshholdHours[1],0,0,0);

        return({start:DSStart,end:DSEnd})
    }
}

If you call this routine in IE and debug it ThreshholdDays[0]=1, and ThreshholdDays[1]=2. In Chrome however it's the other way round: ThreshholdDays[0]=2, and ThreshholdDays[1]=1.

My HTML for calling the routines:

<!DOCTYPE HTML>
<html dir="ltr" lang="en-gb" xmlns="http://www.w3.org/1999/xhtml">

<head>
<title>Test Dates</title>

<script language="javascript" type="text/javascript" src="LIBS/general.js"></script>
</head>

<body>
<div>
<script language="javascript" type="text/javascript">
var d1 = new Date(2014, 9, 26, 2,1,0);
var d2 = d1.add("n", -2, true);
var ds1 = d1.getDaylightSavingDate(2014);

alert(ds1.start + ", " + ds1.end);
alert(d1 + ", " + d2 + ", " + (d2-d1));

</script>
</div>
</body>
</html>

Anybody think of another way of doing this without explicitly coding for each browser?

Community
  • 1
  • 1
Yowser
  • 36
  • 1
  • 4
  • 4
    `typeof(ignoreDaylightSaving) == "boolen"` Boolean is misspelled... – War10ck Mar 14 '14 at 17:37
  • 1
    Just curious why roll your own calendar? There are plenty of free ones you can use. Also, for the date time stuff have you looked at MomentJs? – Mike Cheel Mar 14 '14 at 17:38
  • @Mike. I'm converting proprietry vbscript and ownership must remain with my client, so I can't use any library code. It's got to be pure javascript written in-house and license will be transferred totally to my client. Their rules, not mine ;-) – Yowser Mar 15 '14 at 20:30
  • @War10ck. Thanks for the eagle-eye! Doesn't make a difference to the test as it defaults to false anyway. – Yowser Mar 15 '14 at 20:32

1 Answers1

0

If you compare the local timezone offsets for a date in June with a date in January you can tell if DST applies.

South of the equator, where DST is used, January is always in DST, north of the equator, June is always in DST.

You can find the month, day, hour and minute that DST and standard time start by advancing the Date object through months, days, hours and minutes in turn, checking the difference in timezone offsets. It is a busy function, but needs only be done once for any year in a calendar app.

The function getDST returns an Array with a Date object representing the first minute of standard time for the given year, the first minute of DST for that year, and the offset difference in minutes.

(The second function returns the date and a message to appear on that date in a calendar)

The best way to test this kind of function is to change the timezone in your computer for as many timezones as you can stand, with use dst enabled.

function getDST(y){
    y= y || new Date().getFullYear();
    var D1= new Date(y, 0, 1), D2= new Date(y, 5, 1), 
    off1= D1.getTimezoneOffset(), off2= D2.getTimezoneOffset(), 
    diff= off2-off1;
    if(diff=== 0) return ;
    var stdTime= D1, dstTime= D2, 
    std1= 11, dst1= 5, 
    stdOffset= off1, dstOffset= off2, 
    isDst, isStd, one= 1;
    if(diff>0){
        diff*= -1;
        stdOffset= off2;
        dstOffset= off1;
        std1= 5;
        dst1= 11;
    }
    function isDst(D){
        return D.getTimezoneOffset()=== dstOffset;
    }
    function isStd(D){
        return D.getTimezoneOffset()=== stdOffset;
    }
    function edgeTz(D, m, check){
        D.setMonth(m);
        while(check(D)) D.setMonth(m--);
        D.setMonth(D.getMonth()+1);
        while(check(D)) D.setDate(D.getDate()-1);
        one= 1;
        while(!check(D)) D.setHours(one++);
        D.setHours(D.getHours()-1);
        one= 1;
        while(!check(D)) D.setMinutes(one++);
        return D;
    }
    stdTime= edgeTz(stdTime, std1, isStd);
    dstTime= edgeTz(dstTime, dst1, isDst);
    return [stdTime, dstTime, diff];
}

// local start time for standard and dst: getDST(2014).join('\n');

//result for EST

Sun Nov 02 2014 02: 00: 00 GMT-0500(Eastern Daylight Time)
Sun Mar 09 2014 03: 00: 00 GMT-0400(Eastern Standard Time)
-60

//calendar note:

function tzReport(y){
    var z= getDST(y);
    var dstMsg, stdMsg, changeStd, changeDst, 
    stdTime= z[0], dstTime= z[1], diff= z[2];
    changeStd= new Date(stdTime);
    changeStd.setDate(changeStd.getDate()+1);
    changeStd.setMinutes(changeStd.getMinutes()-diff);
    stdMsg= 'Standard Time begins. At '+
    changeStd.timeString()+', clocks return to '+stdTime.timeString()+'.';
    changeDst= new Date(dstTime);
    changeDst.setDate(changeDst.getDate()+1);
    changeDst.setMinutes(changeDst.getMinutes()+diff);
    dstMsg= 'DST begins. At '+changeDst.timeString()+
    ', clocks advance to '+dstTime.timeString()+'.';
    return [[stdTime.toLocaleDateString(), stdMsg], 
    [dstTime.toLocaleDateString(), dstMsg]];
}

//Calendar notice: tzReport(2014).join('\n');

Sunday, November 02, 2014, 
Standard Time begins.
At 3: 00 am, clocks return to 2: 00 am.
Sunday, March 09, 2014, DST begins.
At 2: 00 am, clocks advance to 3: 00 am.
kennebec
  • 102,654
  • 32
  • 106
  • 127
  • Thanks a lot for the work. Need to test it a bit. I must admit I didn't try setMonth, setDate and setHours to see if IE & Chrome were inconsistent with each other. Will report back early next week when I'm back on it. – Yowser Mar 15 '14 at 20:45
  • Blimey time flies! Only just got around to fully testing the code and it works in IE, Chrome, Safari, Opera and Firefox. – Yowser Apr 11 '14 at 09:56