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?