代码之家  ›  专栏  ›  技术社区  ›  Mehran

消除跨浏览器解决方案Javascript夏令时差距

  •  5
  • Mehran  · 技术社区  · 9 年前

    经过一番折腾,我终于找到了真正的问题。这是日光节约导致的差距,以及如果时区设置为UTC+3:30(我不确定其他时区),不同浏览器的行为会有所不同。

    以下是生成问题的代码片段(如果系统的TZ设置为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>");

    我在Firefox和Chromium上运行过它,下面是他们说的:

    enter image description here

    红色框内的部分是间隙发生的时间范围。我的问题是,像日历这样的组件通常依赖于时间部分设置为“00:00:00”的日期对象,并且它们有一个循环,通过向上一个日期添加一天的时间戳来生成新的日期。所以一旦物体落入 3/22/2015 00:00:00 这将被考虑 3/22/2015 01:00:00 21/3/2015 23:00:00 (取决于浏览器),因此生成的日期将从此时间点起无效!

    问题是如何检测这样的日期对象以及如何处理它们?

    6 回复  |  直到 9 年前
        1
  •  17
  •   Matt Johnson-Pint    9 年前

    使用 moment.js 这将为您省去很多麻烦,并且是实现这类事情的跨浏览器兼容性的最简单方法。

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

    使用UTC是很重要的,因为您不想受到用户时区的影响。否则,如果您的日期处于DST过渡,则可以将其调整为其他值。(你不是 真正地 对UTC感兴趣,您只是为了保持稳定性。)


    针对您更新问题的这一部分:

    为了简化这个问题,我需要一个这样的函数:

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

    虽然现在你的要求很明确,但你必须明白 不可能 为了达到您的确切要求,至少不要以跨浏览器、跨地区、跨时区的方式。

    说了这么多,你当然可以 Date 对象 并非常简单地解构其部分:

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

    但这将 总是 基于代码运行的本地时区。你看,在 日期 对象,只有一个值-表示自 1970-01-01T00:00:00Z (不考虑闰秒)。该数字基于UTC。

    因此,在总结中,您遇到的所有问题都与字符串解析为 日期 对象开始。再多关注输出函数也无法帮助您以完全安全的方式解决问题。无论您使用库还是编写自己的代码,都需要获取原始数据字符串以获得所需的结果。当它在一个 日期 对象,您已经丢失了完成此操作所需的信息。

    顺便说一下,你可以考虑看我的Pluralsight课程, Date and Time Fundamentals ,其中更详细地介绍了其中的大部分内容。模块7完全是关于JavaScript和这些陷阱。

        2
  •  1
  •   Brian Shamblen    9 年前

    JavaScript日期以浏览器的本地时间解析,除非您使用ISO日期格式 "2015-03-22" ,将解析为UTC。在您的情况下,由于您控制服务器,并且知道日期实际上是UTC,因此可以保留格式并解析日期,然后从日期中减去时区偏移值,将其转换为UTC。下面的函数应该完全返回您在上一个用例中请求的结果。显然,您可以修改它,以任何您认为合适的方式格式化字符串。:

    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() ];
    }
    

    这种方法的唯一缺点是,如果您将服务器上的日期格式更改为使用ISO 8601日期格式,它仍然会减去时区偏移量,结果将是错误的输出。

    为了100%安全,你 应该 使用ISO 8601日期格式格式化日期字符串,如 "2015-03-22" "2015-03-22T00:00:00Z" 。然后,您可以删除从解析的日期中减去时区偏移量的行,它将正确处理任何ISO日期字符串:

    function date_decomposition(dateString) {
        var d = new Date(dateString);
    
        return [ d.getUTCFullYear(), 
                (d.getUTCMonth() +1 ),
                d.getUTCDate(),
                d.getUTCHours(), 
                d.getUTCMinutes(),
                d.getUTCSeconds() ];
    }
    
        3
  •  1
  •   F. Hauri - Give Up GitHub    9 年前

    日期分析(修改问题前的第一个答案)

    这将解析输入并隔离每个值。

    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>

    测试规则#1

    正在搜索 缺口 从2000年到2015年,以1/2小时的步长

    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>

    总之,这似乎不是一个javascript错误,但我发现这是关于 外部J : Ext.Date.parse - problem when parsing dates near daylight time change.

    第二条测试规则

    由于这个问题被修改了好几次,有一个测试规则在过去的10年里,通过15分钟的步骤来显示 缺口 以及 重叠,重叠 :

    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#测试{
    字体系列:单色、单色、终端;
    字体大小:.8em;
    }
    <div id=“test”></div>

    您可以通过在一些不同的时区下运行首选浏览器来测试这一点:

    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
    

    我希望您能够为您的 ExtJS扩展 ,从最后一段中汲取灵感。

    屏幕截图

    TZ=伊朗铬

    TZ=Iran chromium

    TZ=伊朗冰鼬(萤火虫)

    TZ=Iran iceweasel

    TZ=美国/中央铬

    TZ=US/Central chromium

    TZ=美国/中部冰鼬

    TZ=US/Central iceweasel

    TZ=亚洲/加沙铬

    TZ=Asia/Gaza chromium

    TZ=亚洲/加沙冰鼬

    TZ=Asia/Gaza iceweasel

        4
  •  0
  •   robbmj    9 年前

    必须使用可变长度参数构造函数。

    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);
        5
  •  0
  •   Francisco Félix    9 年前

    问题更在于方法。浏览器的行为完全符合预期,因为它们都知道 Upcoming Daylight Saving Time Clock Changes 。因此,您的Chromium浏览器知道,在3月22日当地时间午夜12:00,需要将时钟向前移动到凌晨1:00,而您的Firefox浏览器知道,3月22号当地时间周六至周日午夜,需要将时间向后移动到周六晚上11:00。

    基于UTC定义不同的时区是有原因的。 UTC是一个标准,而不是时区。 如果你想跨浏览器,你需要按照标准生活。坚持UTC,以UTC存储,并以UTC计算所有日期。如果你有一个日历组件,告诉你的组件你是用UTC传递日期的。当您需要向用户显示日期时(因此取决于浏览器),您(如果您未使用组件)或日历组件负责检查夏时制是否有效,并相应调整要显示的日期。

    你怎么知道夏时制是否有效?

    检查此项 article about adding methods to the date object 要做到这一点。代码背后的逻辑是,如果夏时制是否有效,UTC和本地时间之间的时区差异是不同的。如果您只想检查代码,这里有两个函数:

    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();
    }
    
        6
  •  0
  •   PHP Worm... Lylo    9 年前

    我找到了解决你问题的办法

    它会帮你的

    首先,我想解释一下夏时制。

    夏令时: 夏季白天更长;太阳升起得早,落得晚。几十年前,国会决定,晚上比早上有更多的日光,因此他们提出了夏时制。时钟在春季提前一小时,在秋季向后移动一小时。这样,你就不会在凌晨4点看到日出,晚上8点看到日落,而是在夏天的中间,早上5点看到日升,晚上9点看到日落西山。他们认为人们在晚上用更少的电来照明他们的房子,等等。 所以现在,大多数国家在夏天使用夏时制,在冬天使用标准时间。 yahoo link

    首先,我想给你一个解决问题的方法:

    将您的计算机(在win 7测试)时区设置为GMT+3:30,并将日期更改为2015年3月23日。 并在两个浏览器(chromium/mozila)上运行代码。 您将发现相同的结果(即输入时间+1小时)

    是的,问题解决了。

    您将在您的输入时间中添加+1小时,因为2015年3月22日之后,12:00 AM(对于GMT+3:30)白天的灯光节约时间开始了。( you can check it here 也是从这里 here )

    现在,原因是:

    请看一下这个屏幕截图: http://prntscr.com/6j4gdn (赢7) 在这里您可以看到“日光节约时间从2015年3月22日上午12:00开始。

    意味着时间将在2015年3月22日中午12:00提前1小时(仅适用于使用夏时制的国家)

    因此,如果您将时间更改为2015年3月23日,夏令时将开始(例如GMT+3:30) 您将在两个浏览器上找到正确的输出。

    我猜chrome总是使用夏时制(仅适用于那些使用夏时计的国家) 我已经通过改变系统的时间进行了测试(但不是100%确定)

    另一方面,mozila支持夏令时系统。因此,在2015年3月22日中午12:00 GMT+3:30之后,mozila将为您的输入日期时间增加1小时

    我想这对你们所有人都有帮助。