6

I am having difficulty using PHP DateTime to convert a date received with a GMT -8 timezone (PST) to a human readable format with timezone GMT -7 (PDT).

Here's an example:

$tz = new DateTimeZone('America/Los_Angeles');  
$saleEndDate = new DateTime("2016-11-07T17:30:00-08:00");
$saleEndDate->setTimezone($tz);
echo $saleEndDate->format('Y-m-d H:i:s');

The output of the above code is: 2016-11-07 17:30:00. However, it should display 2016-11-07 18:30:00 because America/Los_Angeles is now in daylight savings (GMT -7, PDT).

From what I've read in the DateTime docs, the new DateTime command should be able to interpret that the string 2016-11-07T17:30:00-08:00 has a GMT -8 timezone:

The timezone parameter and the current timezone are ignored when the time parameter either contains a UNIX timestamp (e.g. 946684800) or specifies a timezone (e.g. 2010-01-28T15:00:00+02:00).

Even so, I do not think DateTime is recognizing GMT -8 correctly.

Does anyone know what approach is necessary to convert between timezones correctly?

Update:

I've also tried passing in a DateTimeZone as the second parameter to the DateTime constructor, but also to no avail:

$tz = new DateTimeZone('America/Los_Angeles');  
$saleEndDate = new DateTime("2016-11-07T17:30:00-08:00", new DateTimeZone("America/Los_Angeles"));
$saleEndDate->setTimezone($tz);
echo $saleEndDate->format('Y-m-d H:i:s');

Also does not work:

$tz = new DateTimeZone('America/Los_Angeles');  
$saleEndDate = new DateTime("2016-11-07T17:30:00", new DateTimeZone("PST"));
$saleEndDate->setTimezone($tz);
echo $saleEndDate->format('Y-m-d H:i:s');

Also does not work:

$tz = new DateTimeZone("PDT");  
$saleEndDate = new DateTime("2016-11-07T17:30:00", new DateTimeZone("PST"));
$saleEndDate->setTimezone($tz);
echo $saleEndDate->format('Y-m-d H:i:s');
Elliot B.
  • 17,060
  • 10
  • 80
  • 101
  • I didn't downvote, upvoted.. but maybe I don't understand here something... why do you think it should convert to 18:30 just because it's daylight saving right now? -8 is basically Los Angeles so maybe nothing to convert here. – Pawel Dubiel Sep 28 '16 at 01:46
  • 1
    http://stackoverflow.com/questions/17694894/different-timezone-types-on-datetime-object "Only DateTime objects with type 3 timezones attached will allow for DST correctly." Your `$saleEndDate` is being created as type 1. – ceejayoz Sep 28 '16 at 01:47
  • @PawelDubiel it should convert, because the time provided is `-8` but the timezone set is `-7` – zerkms Sep 28 '16 at 01:47
  • @ceejayoz Do you have a working example of this? If so, I'd happily accept your answer. I tried using `DateTime` passing in `DateTimeZone` as the second parameter -- still had the same result. I'll update my answer to reflect this. – Elliot B. Sep 28 '16 at 01:53
  • @ElliotB. it's unlikely you can do that with `America/Los_Angeles`. – zerkms Sep 28 '16 at 01:58
  • @zerkms I took `America/Los_Angeles` completely out of the code (using `PST` and `PDT` instead), but it still does not work correctly. There must be some way to accomplish this... – Elliot B. Sep 28 '16 at 02:01
  • @ElliotB. and as mentioned in the referred answer - the PST and PDT would create a datetime with timezone of type 2, which would still not work. (and it is annoying, but that's why we all like php) – zerkms Sep 28 '16 at 02:03
  • @zerkms No, I've tried the type 3 examples listed in your referred answer (see my updated answer). I include `new DateTimeZone` with the `DateTime` instantiation and the problem still remains -- with or without `America/Los_Angeles`. – Elliot B. Sep 28 '16 at 02:06
  • @ElliotB. "No, I'm using the type 3" --- please prove it with `var_dump` https://3v4l.org/4ECvc – zerkms Sep 28 '16 at 02:07
  • @zerkms Could you show me a code example that would switch it to type 3? I'd be happy to accept it as an answer. I have several examples in my updated answer which fit the pattern you've provided for type 3, but I'm still having no luck. – Elliot B. Sep 28 '16 at 02:09
  • https://3v4l.org/TQobj would be 3, but as I mentioned before, it's not helpful to you. And as I also mentioned, I doubt you can do that. "which fit the pattern" --- they don't: for type=3 the timezone should use the full name. – zerkms Sep 28 '16 at 02:11
  • @zerkms I get a `var_dump` similar to that: `(DateTime)#2 (3) { ["date"]=> string(26) "2016-11-07 17:30:00.000000" ["timezone_type"]=> int(2) ["timezone"]=> string(3) "PDT" }` but the issue persists. If you have a full working example, please let me know. I'm not trying to be difficult, just trying to get this unusual problem resolved. – Elliot B. Sep 28 '16 at 02:13
  • an idea can you use for PST something like America/Dawson_Creek ( whatever timezone would be withing PST range ) – Pawel Dubiel Sep 28 '16 at 02:13
  • `["timezone_type"]=> int(2)` --- "but the issue persists", because as you can see it is of a type 2 – zerkms Sep 28 '16 at 02:13
  • @PawelDubiel if the chosen timezone has or will have its own summer/winter time then the solution would work for just half a year, then it need to be fixed again. – zerkms Sep 28 '16 at 02:15
  • 1
    @zerkms Gotcha, I see the difference between the timezone type, but ultimately what I'm trying to do is convert a PST timestamp to PDT. Do you know how this can be done? When I re-introduce the `setTimeZone` method, it switches back to type 2. Before (type 3): https://3v4l.org/CKuFR After (type 2): https://3v4l.org/1kQuE – Elliot B. Sep 28 '16 at 02:19
  • 1
    Yes, I understand what you want, but I don't think I can suggest anything. – zerkms Sep 28 '16 at 02:24
  • @ElliotB. date you provided is november... so it has -8 hours echo date_offset_get(new DateTime('2016-11-07T17:30:00',new DateTimeZone("America/Los_Angeles")))/3600; it would be different during summer... -7 – Pawel Dubiel Sep 28 '16 at 03:10

2 Answers2

2

Not the greatest but it's the only way I can think of to do this

$tz = new DateTimeZone('America/Los_Angeles');  
$saleEndDate = new DateTime("2016-11-07T17:30:00-08:00");
$saleEndDate->setTimezone($tz);
$stamp = $saleEndDate->format('U');
$zone = $tz->getTransitions($stamp, $stamp);
if(!$zone[0]['isdst']) $saleEndDate->modify('+1 hour');
echo $saleEndDate->format('Y-m-d H:i:s');

What I'm doing here is using the DateTimeZone::getTransitions function to determine if the date you provided is DST or not. If it isn't, we add one hour. Note that this does not change the time zone, it just corrects for the DST shift

You can see it in action here

Machavity
  • 30,841
  • 27
  • 92
  • 100
2

The DateTime is functioning as it should. Unless you're in an area that observed different DST to the greater area, America/Los_Angeles left DST (PDT->PST) on the 6th of November (in 2016).

https://www.timeanddate.com/news/time/usa-canada-end-dst-2016.html

From the timezeonedb you can inspect the dates it uses by searching the array for your particular Date/Time (which Machavity did) and got the fact that it isn't in DST and then went on to modify it manually. Which is not an answer as it will eventually fail unless you manually add a cut-off time for the manual correction to stop.

Inspecting the transition dates around your date reveals:

date_default_timezone_set('America/Los_Angeles');
$theDate = new DateTime("2016-11-07T17:30:00",new DateTimeZone("America/Los_Angeles"));
$theDateBefore = new DateTime("2016-03-01");
$theDateAfter = new DateTime("2017-03-15");
echo "<pre>";
print_r( $theDate->getTimezone()->getTransitions(
     $theDateBefore->getTimestamp(),$theDateAfter->getTimestamp()));
echo "</pre>";

resulting in an array of 4:

Array
(
    [0] => Array
        (
            [ts] => 1456819200
            [time] => 2016-03-01T08:00:00+0000
            [offset] => -28800
            [isdst] => 
            [abbr] => PST
        )

    [1] => Array
        (
            [ts] => 1457863200
            [time] => 2016-03-13T10:00:00+0000
            [offset] => -25200
            [isdst] => 1
            [abbr] => PDT
        )

    [2] => Array
        (
            [ts] => 1478422800
            [time] => 2016-11-06T09:00:00+0000
            [offset] => -28800
            [isdst] => 
            [abbr] => PST
        )

    [3] => Array
        (
            [ts] => 1489312800
            [time] => 2017-03-12T10:00:00+0000
            [offset] => -25200
            [isdst] => 1
            [abbr] => PDT
        )

)

Array[0] is the timezone in effect as at theDateBefore and you can see the dates changes are in effect for your timezeone.

Your sale date falls after the change from PDT to PST.

To have the code return an adjusted date/time you would need to manually change it. Doing it the way that's been accepted would yield false results. As I mention, you would need to surround that with the dates you want the custom time zone to be enforced.

Madivad
  • 2,999
  • 7
  • 33
  • 60