最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

date - Eliminating Javascript daylight saving time gap, a cross-browser solution - Stack Overflow

programmeradmin3浏览0评论

After lots of hassle I finally found the actual problem. It's the gap induced by daylight saving and the fact that different browsers act differently if timezone is set on UTC+3:30 (I'm not sure of other timezones).

Here's a snippet to generate the problem (the problem is reproducible if your system's TZ is set to UTC+3:30):

function zeroPad(n) {
  n = n + '';
  return n.length >= 2 ? n : new Array(2 - n.length + 1).join('0') + n;
}

document.write("<table border='1' cellpadding='3'><tr><td>Input date</td><td>Parsed timestamp</td><td>Output date</td></tr>");

var m = 22 * 60;
for (var i=0; i<8; i++) {
  var input = "3/21/2015 " + zeroPad(Math.floor(m / 60)) + ":" + zeroPad(m % 60) + ":00";
  var d = new Date(input);
  var output = d.getFullYear()
    +'-'+zeroPad(d.getMonth()+1)
    +'-'+zeroPad(d.getDate())
    +' '+zeroPad(d.getHours())
    +':'+zeroPad(d.getMinutes())
    +':'+zeroPad(d.getSeconds());
  
  
  document.write("<tr><td>" + input + "</td><td>" + d.getTime() + "</td><td>" + output + "</td></tr>");
  m = m + 15;
}
m = 0;
for (var i=0; i<7; i++) {
  var input = "3/22/2015 " + zeroPad(Math.floor(m / 60)) + ":" + zeroPad(m % 60) + ":00";
  var d = new Date(input);
  var output = d.getFullYear()
    +'-'+zeroPad(d.getMonth()+1)
    +'-'+zeroPad(d.getDate())
    +' '+zeroPad(d.getHours())
    +':'+zeroPad(d.getMinutes())
    +':'+zeroPad(d.getSeconds());
  
  
  document.write("<tr><td>" + input + "</td><td>" + d.getTime() + "</td><td>" + output + "</td></tr>");
  m = m + 15;
}

document.write("</table>");

After lots of hassle I finally found the actual problem. It's the gap induced by daylight saving and the fact that different browsers act differently if timezone is set on UTC+3:30 (I'm not sure of other timezones).

Here's a snippet to generate the problem (the problem is reproducible if your system's TZ is set to UTC+3:30):

function zeroPad(n) {
  n = n + '';
  return n.length >= 2 ? n : new Array(2 - n.length + 1).join('0') + n;
}

document.write("<table border='1' cellpadding='3'><tr><td>Input date</td><td>Parsed timestamp</td><td>Output date</td></tr>");

var m = 22 * 60;
for (var i=0; i<8; i++) {
  var input = "3/21/2015 " + zeroPad(Math.floor(m / 60)) + ":" + zeroPad(m % 60) + ":00";
  var d = new Date(input);
  var output = d.getFullYear()
    +'-'+zeroPad(d.getMonth()+1)
    +'-'+zeroPad(d.getDate())
    +' '+zeroPad(d.getHours())
    +':'+zeroPad(d.getMinutes())
    +':'+zeroPad(d.getSeconds());
  
  
  document.write("<tr><td>" + input + "</td><td>" + d.getTime() + "</td><td>" + output + "</td></tr>");
  m = m + 15;
}
m = 0;
for (var i=0; i<7; i++) {
  var input = "3/22/2015 " + zeroPad(Math.floor(m / 60)) + ":" + zeroPad(m % 60) + ":00";
  var d = new Date(input);
  var output = d.getFullYear()
    +'-'+zeroPad(d.getMonth()+1)
    +'-'+zeroPad(d.getDate())
    +' '+zeroPad(d.getHours())
    +':'+zeroPad(d.getMinutes())
    +':'+zeroPad(d.getSeconds());
  
  
  document.write("<tr><td>" + input + "</td><td>" + d.getTime() + "</td><td>" + output + "</td></tr>");
  m = m + 15;
}

document.write("</table>");

I've run it on Firefox and Chromium and here are what they say:

The parts within the red boxes are the range of times in which the gap happens. My problem is that components like calendars usually depend on date objects with time part set to "00:00:00" and they've got a loop generating new dates by adding a day worth of timestamp to the previous date. So once an object falls into 3/22/2015 00:00:00 it will be considered 3/22/2015 01:00:00 or 21/3/2015 23:00:00 (depending on the browser) and hence the generated dates will be invalid from that point of time forth!

The question is how to detect such date objects and how to treat them?

Share Improve this question edited Mar 15, 2015 at 6:22 Mehran asked Mar 11, 2015 at 14:36 MehranMehran 16.8k31 gold badges139 silver badges245 bronze badges 12
  • 1 @CBroe Using timestamp does not solve anything. "3/22/2015" and 1426969800000 are the same to Javascript and they produce the exact same object. – Mehran Commented Mar 11, 2015 at 19:55
  • 1 I wrote a fiddle: jsfiddle.net/bj98fpxj/1 wich could help you to understand what's happen – F. Hauri - Give Up GitHub Commented Mar 14, 2015 at 14:45
  • 2 There is normaly a gap when changing from winter time to summer time and a overlap when changing back to winter time. – F. Hauri - Give Up GitHub Commented Mar 14, 2015 at 14:58
  • 1 I found this: Ext.Date.parse - problem when parsing dates near daylight time change. – F. Hauri - Give Up GitHub Commented Mar 15, 2015 at 7:36
  • 1 U could test by running: env TZ=US/Central firefox http://stackoverflow.com/a/29048205/1765658 and run my last snippet – F. Hauri - Give Up GitHub Commented Mar 15, 2015 at 9:06
 |  Show 7 more comments

6 Answers 6

Reset to default 17

Using moment.js will save you lots of headache, and is the easiest way to achieve cross-browser compatibility for this sort of thing.

var m = moment.utc("3/22/2015","M/D/YYYY")
var s = m.format("YYYY-MM-DD HH:mm:ss")

Using UTC for this is important since you don't want to be affected by the user's time zone. Otherwise, if your date fell into a DST transition, it could be adjusted to some other value. (You're not really intersted in UTC, you're just using it for stability.)


In response to this part of your updated question:

To simplify the question, I'm looking for a function like this:

function date_decomposition(d) {
    ...
}

console.log(date_decomposition(new Date("3/22/2015 00:00:00")));
=> [2015, 3, 22, 0, 0, 0]

While it's now clear what you are asking for, you must understand it is not possible to achieve your exact requirements, at least not in a cross-browser, cross-region, cross-timezone manner.

  • Each browser has it's own way of implementing the string-to-date parsing. When you use either the constructor new Date(string) or the Date.parse(string) method, you're invoking functionality that is implementation specific.

    There's a chart showing many of the formatting differences here.

  • Even if the implementation were consistent across all environments, you'd have regional formatting and time zone differences to contend with.

    • In the case of regional formatting issues, consider 01/02/2015. Some regions use mm/dd/yyyy ordering and will treat this as January 2nd, while other regions use dd/mm/yyyy ordering and will treat this as February 1st. (Also, some parts of the world use yyyy/mm/dd formatting.)

      Wikipedia has a list and map of where in the world different date formats are used.

    • In the case of time zones, consider that October 19th, 2014 at Midnight (00:00) in Brazil did not exist, and November 2nd, 2014 at Midnight (00:00) in Cuba existed twice.

      The same thing happens on other dates and times in different time zones. From the information you provided, I can deduce that you are in Iran time zone, which uses UTC+03:30 during standard time, and UTC+04:30 during daylight time. Indeed, March 22, 2105 at Midnight (00:00) did not exist in Iran.

      When you try to parse these invalid or ambiguous values, each browser has its own behavior, and there indeed differences between the browsers.

      • For invalid times, some browsers will jump forward an hour, while others will jump backwards an hour.

      • For ambiguous times, some browsers will assume you meant the first (daylight-time) instance, while others will assume you meant the second (standard-time) instance.

Now with all of that said, you can certainly take a Date object and deconstruct its parts, quite simply:

function date_decomposition(d) {
    return [d.getFullYear(), d.getMonth()+1, d.getDate(),
            d.getHours(), d.getMinutes(), d.getSeconds()];
}

But this will always be based on the local time zone where the code is running. You see, inside the Date object, there is just one value - a number representing the elapsed milliseconds since 1970-01-01T00:00:00Z (without leap seconds being considered). That number is UTC-based.

So, in recap, all of the issues you are having are related to the way the string was parsed into the Date object to begin with. No amount of focusing on the output functions will help you to resolve that in a completely safe manner. Whether you use a library or write your own code, you'll need to obtain that original string of data to get the result you are looking for. By the time it's in a Date object, you've lost the information you need to make this work.

By the way, you might consider watching my Pluralsight course, Date and Time Fundamentals, which covers much of this in even greater detail. Module 7 is entirely about JavaScript and these sorts of gotchas.

JavaScript dates are parsed in the local time of the browser, unless you use the ISO date format "2015-03-22", which will be parsed as UTC. In your case, since you control the server, and you know the dates are actually UTC, you could leave the format the way it is and parse the date, then subtract the timezone offset value from the date to convert it to UTC. The following function should return exactly the results you requested in your last use case. You could obviously modify this to format the string in any way you see fit.:

function date_decomposition(dateString) {
    var local = new Date(dateString);
    var d = new Date(local.valueOf() - (local.getTimezoneOffset() * 60000));

    return [ d.getUTCFullYear(), 
            (d.getUTCMonth() +1 ),
            d.getUTCDate(),
            d.getUTCHours(), 
            d.getUTCMinutes(),
            d.getUTCSeconds() ];
}

The only gotcha about this approach is that if you change your date format on the server to use the ISO 8601 date format, it will still subtract the timezone offset and you'll end up with the wrong output.

To be 100% safe, you should format the date string using the ISO 8601 date format, like "2015-03-22" or "2015-03-22T00:00:00Z". Then you could delete the line that subtracts the timezone offset from the parsed date and it'll work correctly with any ISO date string:

function date_decomposition(dateString) {
    var d = new Date(dateString);

    return [ d.getUTCFullYear(), 
            (d.getUTCMonth() +1 ),
            d.getUTCDate(),
            d.getUTCHours(), 
            d.getUTCMinutes(),
            d.getUTCSeconds() ];
}

Date Parse (1st answer before modified question)

This would parse input and isolate each value.

function dateDecomposition(d) {
    return [ d.getFullYear(),d.getMonth()+1,d.getDate(),
             d.getHours(),d.getMinutes(),d.getSeconds() ];
};
function dateUTCDecomposition(d) {
    return [ d.getUTCFullYear(),d.getUTCMonth()+1,d.getUTCDate(),
             d.getUTCHours(),d.getUTCMinutes(),d.getUTCSeconds() ];
};

function pdte() {
  var ndte=new Date(Date.parse(document.getElementById('datein').value));
  var ar=dateDecomposition(ndte);
  document.getElementById('yyyy').innerHTML=ar[0];
  document.getElementById('mm').innerHTML=ar[1];
  document.getElementById('dd').innerHTML=ar[2];
  document.getElementById('HH').innerHTML=ar[3];
  document.getElementById('MN').innerHTML=ar[4];
  document.getElementById('SS').innerHTML=ar[5];
  ar=dateUTCDecomposition(ndte);
  document.getElementById('Uyyyy').innerHTML=ar[0];
  document.getElementById('Umm').innerHTML=ar[1];
  document.getElementById('Udd').innerHTML=ar[2];
  document.getElementById('UHH').innerHTML=ar[3];
  document.getElementById('UMN').innerHTML=ar[4];
  document.getElementById('USS').innerHTML=ar[5];

  document.getElementById('test').innerHTML='';
  for (var i=1426896000000;i<1427068800000;i+=1800000) {
      ar=dateUTCDecomposition(new Date(i));
      var cmp=Date.parse(ar[0]+"/"+ar[1]+"/"+ar[2]+" "+ar[3]+":"+ar[4]+":"+ar[5]);
      var re=dateDecomposition(new Date(cmp));
      var fail=0;
      for (var l=0;l<6;l++) { if (ar[l]!==re[l]) fail=1 };
      document.getElementById('test').innerHTML+=fail+" -- "+ar.join(":")+'<br />';
  };
};

document.getElementById('datein').addEventListener('change',pdte,true);
window.onload=pdte
<input id="datein" value="2015/03/14 12:34:56" />
<table border=1>
  <tr><td>Locale</td>
    <td id="yyyy"></td>
    <td id="mm"></td>
    <td id="dd"></td>
    <td id="HH"></td>
    <td id="MN"></td>
    <td id="SS"></td>
  </tr><tr><td>UTC</td>
    <td id="Uyyyy"></td>
    <td id="Umm"></td>
    <td id="Udd"></td>
    <td id="UHH"></td>
    <td id="UMN"></td>
    <td id="USS"></td>
    </tr>
  </table>
  <div id="test"></div>

Testing rule #1

Searching for gaps from year 2000 to year 2015, by 1/2hour steps

function dateDecomposition(d) {
    return [ d.getFullYear(),d.getMonth()+1,d.getDate(),
             d.getHours(),d.getMinutes(),d.getSeconds() ];
};
function dateUTCDecomposition(d) {
    return [ d.getUTCFullYear(),d.getUTCMonth()+1,
             d.getUTCDate(), d.getUTCHours(),
             d.getUTCMinutes(),d.getUTCSeconds() ];
};

for (var i=946684800000;i<1420070400000;i+=1800000) {
      ar=dateUTCDecomposition(new Date(i));
      var cmp=Date.parse(
          ar[0]+"/"+ar[1]+"/"+ar[2]+" "+
          ar[3]+":"+ar[4]+":"+ar[5]);
      var re=dateDecomposition(new Date(cmp));
      var fail=0;
      for (var l=0;l<6;l++) { if (ar[l]!==re[l]) fail=1 };
    if (fail!==0) {
 document.getElementById('test').innerHTML+=fail+
          " -- "+new Date(i)+" -- "+ar.join(":")+'<br />';
    }
}
div#test {
  font-family: mono, monospace, terminal;
  font-size: .8em;
}
   <div id="test"> </div>

At all, this seem not to be a javascript bug, but I found this about ExtJs: Ext.Date.parse - problem when parsing dates near daylight time change.

Second testing rule

As this question was modified several times, there is a test rule working on last 10 years, by 15 minutes steps, for showing gaps AND overlaps:

function dateDecomposition(d) {
    return [ d.getFullYear(),d.getMonth()+1,d.getDate(),
             d.getHours(),d.getMinutes(),d.getSeconds() ];
};
function dateUTCDecomposition(d) {
    return [ d.getUTCFullYear(),d.getUTCMonth()+1,
             d.getUTCDate(), d.getUTCHours(),
             d.getUTCMinutes(),d.getUTCSeconds() ];
};

var end=(Date.now()/900000).toFixed(0)*900000;
var start=end-365250*86400*10;
for (var i=start;i<end;i+=900000) {
  ar=dateUTCDecomposition(new Date(i));
  var cmp=Date.parse(
      ar[0]+"/"+ar[1]+"/"+ar[2]+" "+
      ar[3]+":"+ar[4]+":"+ar[5]);
  var re=dateDecomposition(new Date(cmp));
  var fail=0;
  for (var l=0;l<6;l++) { if (ar[l]!==re[l]) fail++ };
  if (fail!==0) {
    document.getElementById('test').innerHTML+=
      fail+" -- "+new Date(i)+" -- "+ar.join(":")+'<br />';
  } else {
    ar=dateDecomposition(new Date(i));
    var cmp=Date.parse(
          ar[0]+"/"+ar[1]+"/"+ar[2]+" "+
          ar[3]+":"+ar[4]+":"+ar[5]);
    if (cmp != i) {
      document.getElementById('test').innerHTML+=
      fail+" -- "+new Date(i)+" -- "+ar.join(":")+'<br />';
    }
  }
}
div#test {
  font-family: mono, monospace, terminal;
  font-size: .8em;
}
   <div id="test"> </div>

You could test this by running your prefered browser under some differents timezones:

env TZ=Iran firefox http://stackoverflow.com/a/29048205/1765658
env TZ=Iran opera http://stackoverflow.com/a/29048205/1765658
env TZ=Iran chrome http://stackoverflow.com/a/29048205/1765658
env TZ=Iran chromium http://stackoverflow.com/a/29048205/1765658
env TZ=Europe/Berlin firefox http://stackoverflow.com/a/29048205/1765658
env TZ=Europe/Berlin opera http://stackoverflow.com/a/29048205/1765658
env TZ=Europe/Berlin chrome http://stackoverflow.com/a/29048205/1765658
env TZ=US/Central firefox http://stackoverflow.com/a/29048205/1765658
env TZ=US/Central opera http://stackoverflow.com/a/29048205/1765658
env TZ=US/Central chrome http://stackoverflow.com/a/29048205/1765658
env TZ=Asia/Gaza firefox http://stackoverflow.com/a/29048205/1765658
env TZ=Asia/Gaza opera http://stackoverflow.com/a/29048205/1765658
env TZ=Asia/Gaza chromium http://stackoverflow.com/a/29048205/1765658

I hope that you will be able to create your own testing rules for your ExtJS extensions, taking ideas from this last snippet.

Screenshoots

TZ=Iran Chromium

TZ=Iran Iceweasel (firefox)

TZ=US/Central Chromium

TZ=US/Central Iceweasel

TZ=Asia/Gaza Chromium

TZ=Asia/Gaza Iceweasel

You have to use the variable length argument constructor.

function convert(dateStr) {
  var date = dateStr.split('/');
  return new Date(date[2], (date[0] | 0) - 1, date[1]);
}
                  
function formatDate(d) {
  function pad(i) {
    return i < 10 ? '0' + i : i;
  }
  return d.getFullYear()
       + '-' + pad(d.getMonth() + 1)
       + '-' + pad(d.getDate())
       + ' ' + pad(d.getHours()) 
       + ':' + pad(d.getMinutes()) 
       + ':' + pad(d.getSeconds())
}
  
var str = formatDate(convert('3/22/2015'));

document.write(str);

The problem is more on the approach. The browsers are behaving exactly as expected because they are both aware of the Upcoming Daylight Saving Time Clock Changes. So, your Chromium browser knows that at 12:00 Midnight local time in march 22, needs to move the clock forward to to 1:00 AM and your Firefox Browser knows that at Midnight Sat-Sun local time in march 22, needs to move the clock back to to Sat 11:00 PM.

There is a reason for the different time zones to be defined based on UTC. UTC is a standard, not a timezone. If you want to be cross browser you need to live by standards. Stick to UTC, store in UTC and do all date calculations in UTC. If you have a calendar component tell your component you are passing the dates in UTC. When you need to display the date to the user (thus browser dependent) you (if you are not using a component) or your calendar component are the ones responsible to check if daylight savings time is in effect, and adjust the date to be displayed accordingly.

¿How do you know if daylight savings time is in effect?

Check this article about adding methods to the date object to do exactly that. The logic behind the code is that the timezone difference between UTC and Local Time are different if daylight saving time is in effect or not. If you just want to check the code here are the two functions:

Date.prototype.stdTimezoneOffset = function() {
    var jan = new Date(this.getFullYear(), 0, 1);
    var jul = new Date(this.getFullYear(), 6, 1);
    return Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset());
}

Date.prototype.dst = function() {
    return this.getTimezoneOffset() < this.stdTimezoneOffset();
}

I got the solution of your problem

It will surly help you

First of all I would like to explain daylight time.

Daylight time: During the summer days are longer; the sun rises earlier and sets later. Congress decided several decades ago that it would be better to have more daylight in the evening than in the morning, so they came up with daylight savings time. The clocks are shifted ahead one hour in the spring and back one hour in the fall. That way, you don't have sunrise at 4:00 a.m. and sunset at 8:00 p.m., but sunrise at 5:00 a.m. and sunset at 9:00 p.m., in the middle of summer. They figured people would use less electricity to light their houses in the evenings, and so forth. So now, most of the country uses daylight savings time in the summer, and standard time in the winter. yahoo link

Firstly I would like to Give you a solution of your problem:

Set you computer's (Tested on win 7) time zone to GMT+3:30 and change your date to 23-march-2015. and run your code on both browsers (chromium/mozila). you will find the same result (i.e. +1hr to your input time)

yepppii the problem is solved.

you will get +1hr added in your input time, because after March,22 2015 at 12:00 AM (for GMT +3:30) day light Saving time begins. (you can check it here also from here here)

Now the reason for this:

please have a look on this screen-shot : http://prntscr.com/6j4gdn (on win 7) here you can see "The day light Saving time begins on March,22 2015 at 12:00 AM.

Means time will be shifted 1 hr ahead after March,22 2015 at 12:00 AM.(only for those countries which uses daylight saving time system )

so if you changes the time to March,23 2015 the daylight saving period starts for (e.g. GMT+3:30) and you will find correct out put on both browsers.

I guess chrome always uses daylight saving time system (only for those countries which uses daylight saving time system) I have tested it by changing time of my system (but not 100% sure)

on the other hand mozila supports daylight saving time system. So after March,22 2015 at 12:00 AM for GMT+3:30 mozila will add 1hr to your input dateTime

I guess it would be helpful to all of you.

发布评论

评论列表(0)

  1. 暂无评论