通过DST过渡观察DateTime数据并获得不一致的结果

时间:2017-10-04 21:43:42

标签: php datetime timezone dst

围绕DST区域更改在PHP中操作日期/时间时间戳会显示不一致。

我一直试图理解这几个小时,并认为其他人可能更好地了解出了什么问题。

基本上我从时间戳减去几秒钟并在DST更改期间递增,我发现它被击中和未命中。一秒钟回来有时会起作用。 3秒钟会发现其他问题。 似乎大多数时候美国时区的变化都是正确报道的。但其他人永远不正确。

我已将大部分工作留在下面的代码中,并将其注释掉某些部分并取消注释其他部分。

输出格式很详细,以尽可能多地揭示DateTime对象。

代码已注释,但基本上我们检查了几个时区,并在每个时区检查转换数据。这一切都在第一个foreach的顶部完成。

$time ~~我们想要检查时区转换的时间(最初设置为UTC,是的我知道时间戳只是UTC,我在谈论DTZ零件)。 $time仅在文件顶部设置一次。

$timezone ~~使用相关时区加载的DateTimeZone对象最初只在foreach的顶部设置一次,它出现在其他各个地方以展开我发现的错误问题。

$transitions ~~你猜对了,时区对象在时间戳(时间)周围的转换。

$slice 是一个数组变量,用于将转换分解为命名组件,以便于阅读。

$tz... 时区转换数据:
$tzCurrent 当前缩写的时区名称(如BST,AEDT,PDR等)
$tzCurrentDST 是否在DST中,或者现在没有。是1。是的 $tzNew 转换后的新时区缩写
转换发生时 $tzNewTimestamp $tzNewDST 是新的转换DST与否 $tzNewTimeString 转换的UTC时间字符串输出

$changover 这是我用来运行转换数据测试的DateTime对象的版本。

我要做的第一件事是设置$changover对象。由于在声明时无法使用时区设置unix时间戳,因此我只需在timestamp处使用zero UTC声明它。然后我明确设置时间戳和时区。反转时区和时间戳行的顺序将改变输出(无论如何都不是完全正确的):

      $changeover = new DateTime("@0");     // *** force DTZ to UTC (just to be safe)
      $changeover->setTimestamp($tzNewTimestamp) - $secondsBack);
      $changeover->setTimezone($timezone);

接下来我输出一些数据用于信息,然后我们围绕转换时间进入循环。这是输出真正开始变得有趣的地方。

最奇怪的发现似乎是setTimezone方法的位置。例如,如果使用第三种时间戳增量方法,则启用内部foreach中的最后一个可能会产生不同的结果。它对前两种方法没有影响。

我第一次尝试迭代时间戳时使用了以下代码:

$changeover->setTimestamp($changeover->getTimestamp()+1);

然后我改为:

$changeover->modify("+1 second");

基本上给出了相同的结果:

< < date and time > >  ZONE UTC+/- listIdentifier DST OFFSET   TimeZone object info
2018-04-01 02:59:57 AM AEDT +1100 Australia/Sydney 1 39600Australia/Sydney
2018-04-01 02:59:58 AM AEST +1000 Australia/Sydney 0 36000Australia/Sydney
2018-04-01 02:59:59 AM AEST +1000 Australia/Sydney 0 36000Australia/Sydney
2018-04-01 03:00:00 AM AEST +1000 Australia/Sydney 0 36000Australia/Sydney
2018-04-01 03:00:01 AM AEST +1000 Australia/Sydney 0 36000Australia/Sydney
2018-04-01 03:00:02 AM AEST +1000 Australia/Sydney 0 36000Australia/Sydney 

(只有第一行是正确的,前三个应该是一个时区,其余三个应该是新的时区)。

然后我设置了DateInterval并使用了:

$changeover->add(new DateInterval('PT1S'));

以上DateInterval会给前两次尝试带来明显不同的结果。如果你运行并查看输出,可以看到可能出现两行正确值(应该有3行)。

2018-04-01 02:59:57 AM AEDT +1100 Australia/Sydney 1 39600Australia/Sydney
2018-04-01 02:59:58 AM AEDT +1100 Australia/Sydney 1 39600Australia/Sydney
2018-04-01 02:59:59 AM AEST +1000 Australia/Sydney 0 36000Australia/Sydney
2018-04-01 03:00:00 AM AEST +1000 Australia/Sydney 0 36000Australia/Sydney
2018-04-01 03:00:01 AM AEST +1000 Australia/Sydney 0 36000Australia/Sydney
2018-04-01 03:00:02 AM AEST +1000 Australia/Sydney 0 36000Australia/Sydney

如果我在永久循环中重置时区,那么它会给出一种更好的结果:

2018-04-01 02:59:57 AM AEDT +1100 Australia/Sydney 1 39600Australia/Sydney
2018-04-01 02:59:58 AM AEDT +1100 Australia/Sydney 1 39600Australia/Sydney
2018-04-01 02:59:59 AM AEDT +1100 Australia/Sydney 1 39600Australia/Sydney
2018-04-01 02:00:00 AM AEST +1000 Australia/Sydney 0 36000Australia/Sydney
2018-04-01 02:00:01 AM AEST +1000 Australia/Sydney 0 36000Australia/Sydney
2018-04-01 02:00:02 AM AEST +1000 Australia/Sydney 0 36000Australia/Sydney

有三个正确的条目,但这确实使其他条目不正确,例如洛杉矶:

2017-11-05 01:59:57 AM PDT -0700 America/Los_angeles 1 -25200America/Los_angeles
2017-11-05 01:59:58 AM PDT -0700 America/Los_angeles 1 -25200America/Los_angeles
2017-11-05 01:59:59 AM PDT -0700 America/Los_angeles 1 -25200America/Los_angeles
2017-11-05 01:00:00 AM PST -0800 America/Los_angeles 0 -28800America/Los_angeles
2017-11-05 01:00:01 AM PDT -0700 America/Los_angeles 1 -25200America/Los_angeles
2017-11-05 01:00:02 AM PDT -0700 America/Los_angeles 1 -25200America/Los_angeles

如果您取消注释文件顶部的listIdentifiers,则可以打印出所有具有DST数据的时区。你会经常发现美国DST过渡是正确的,但其他国家都受到了影响。它在澳大利亚甚至受到欢迎。

我在纽约,美国/太平洋地区的在线PHP测试网站以及位于悉尼和墨尔本的服务器以及本地开发服务器本地和新加坡的数字海洋服务器上尝试了此操作。

我主要使用PHP7(虽然其中一个服务器允许测试一系列PHP7次要修订版一直到7.1.0),以及5.6和5.5

完整的代码如下所示,如果有人愿意将其选中,并且此处有一个已保存的在线版本:

http://sandbox.onlinephpfunctions.com/code/bc085c977f419f9db9f137ca81ad3a733e849e49

问题基本上是为什么我无法从PHP获得可靠的DST / TimeZone转换数据?我做错了什么?

感谢您阅读

<?php
echo '<pre>';

$f = "Y-m-d H:i:s A T O e I ";

$secondsBack  = 3;  // how many seconds to go back
$secondsWatch = 6;

$tzTests=Array('Australia/North','Australia/Sydney','Australia/Lord_Howe','America/Los_angeles','America/Chicago','Europe/Berlin','Asia/Jerusalem','Pacific/Auckland');
//$tzTests=DateTimeZone::listIdentifiers();

//$time = new DateTime('2017-09-20 00:00:01', new DateTimeZone('UTC'));
$time = new DateTime('now', new DateTimeZone('UTC'));

foreach ($tzTests as $tzTest){

  //// This is the TimeZone we are going to test.
  $timezone = new DateTimeZone($tzTest);
  date_default_timezone_set($tzTest);  // just in case

  //// using the time we want to look around ($time), load the transitions for that $timezone
  $transitions = $timezone->getTransitions($time->getTimestamp());
  $slice = array_slice($transitions, 0, 3);
  //print_r($slice);

  // if there is no DST info, we're not going to show it
  if (count($slice)>1){

      //// break out individual parts of the transition information
      $tzCurrent        = $slice[0]['abbr'] ;
      $tzCurrentDST     = $slice[0]['isdst'];
      $tzNew            = $slice[1]['abbr'] ;
      $tzNewTimestamp   = $slice[1]['ts']   ;
      $tzNewDST         = $slice[1]['isdst'];
      $tzNewTimeString  = $slice[1]['time'] ;



      echo $tzTest . PHP_EOL;
      echo "currently: " . $tzCurrent. (($tzCurrentDST==1)?" (DST)":" (NORMAL)") ;
      echo PHP_EOL;

      $changeover = new DateTime("@0");     // *** force DTZ to UTC (just to be safe)
      $changeover->setTimestamp($tzNewTimestamp);// - $secondsBack);
      $changeover->modify("-$secondsBack seconds");

      ///* The following line can change the result if moved up two lines *///
      $changeover->setTimezone($timezone);

      echo "Next Change: " . $tzNew . (($tzNewDST == 1)?" (DST)":" (NORMAL)") . PHP_EOL;
      echo " @ " . $tzNewTimeString . PHP_EOL;


      for( $i = 0 ; $i < $secondsWatch; $i++){
      // for each loop, print the current time format
            echo $changeover->format($f) . $changeover->getOffset() . $changeover->getTimezone()->getName() . PHP_EOL;

      ////*** First increment style  ***////
      //$changeover->setTimestamp($changeover->getTimestamp()+1);

      ////***  Second increment style  ***////
      $changeover->modify("+1 second");

      ////*** Third and only one that works! (as long as you update the timezone!!!!) ***////
      //$changeover->add(new DateInterval('PT1S'));

      ////*** This has varying effects, works best with method 3 ***////
      //$changeover->setTimezone($timezone);
    }
        echo PHP_EOL . PHP_EOL;
  } else {
  //echo "No DST Information" . PHP_EOL . PHP_EOL;
 }
} ?>

0 个答案:

没有答案