UltraMega Tech.
21Apr/09Off

Working With Time Zones in PHP

Let's say you are developing an online application that involves users selecting or viewing times. One problem that you'll need to address is time zones. If your application is going to be used by users all over the world, you'll want to adjust the times to be in their local time zone to prevent confusion. Fortunately, PHP 5.2.0+ greatly simplifies this process with the DateTime and DateTimeZone classes.

The DateTime class provides all the date and time handling functionality, while the DateTimeZone provides DateTime objects with all the time zone information. We just need to provide DateTimeZone with the time zone in the Area/Location format (for example, the time zone in which this server is located is America/Los_Angeles), and it will return an object representing that time zone. We can pass this to a DateTime object to convert any time into this time zone.

Here is an overview of the steps required to accomplish this:

  1. Collect the time zone from the user and store in Area/Location format
  2. Create a DateTimeZone object using the provided time zone
  3. Create a DateTime object, providing it the time in the local time zone
  4. Convert the DateTime object to the time zone created in step 1
  5. Output the time from the DateTime object

One thing that we can do to avoid confusion is to store all times as a Unix timestamp. Unix timestamps are not affected by time zones since they always represent a time in UTC time. Also, PHP 5.3.0 (still a release candidate as of this tutorial) allows you to provide a timestamp directly to the DateTime class, so I will show the both code for PHP 5.2 and simplified code for PHP 5.3.

The first step is important, because we need to know the time zone of the user before we can think about converting times to their time zone. There are some methods to automatically predict this, but they tend to be unreliable and/or expensive (such as commercial GeoIP databases). So the best method is to simply ask the user where they are. (You can combine these methods by defaulting to the most likely setting when asking, but I won't go into that now).

Here is the code to create a select list of time zones and showing the user a more friendly name representing each zone:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
<?php
if(isset($_POST['tz'])) {
	// store the selected value for future use in DateTimeZone
	$_SESSION['tz'] = $_POST['tz'];
}
// create an array listing the time zones
$zonelist = array('Kwajalein' => '(GMT-12:00) International Date Line West',
		'Pacific/Midway' => '(GMT-11:00) Midway Island',
		'Pacific/Samoa' => '(GMT-11:00) Samoa',
		'Pacific/Honolulu' => '(GMT-10:00) Hawaii',
		'America/Anchorage' => '(GMT-09:00) Alaska',
		'America/Los_Angeles' => '(GMT-08:00) Pacific Time (US &amp; Canada)',
		'America/Tijuana' => '(GMT-08:00) Tijuana, Baja California',
		'America/Denver' => '(GMT-07:00) Mountain Time (US &amp; Canada)',
		'America/Chihuahua' => '(GMT-07:00) Chihuahua',
		'America/Mazatlan' => '(GMT-07:00) Mazatlan',
		'America/Phoenix' => '(GMT-07:00) Arizona',
		'America/Regina' => '(GMT-06:00) Saskatchewan',
		'America/Tegucigalpa' => '(GMT-06:00) Central America',
		'America/Chicago' => '(GMT-06:00) Central Time (US &amp; Canada)',
		'America/Mexico_City' => '(GMT-06:00) Mexico City',
		'America/Monterrey' => '(GMT-06:00) Monterrey',
		'America/New_York' => '(GMT-05:00) Eastern Time (US &amp; Canada)',
		'America/Bogota' => '(GMT-05:00) Bogota',
		'America/Lima' => '(GMT-05:00) Lima',
		'America/Rio_Branco' => '(GMT-05:00) Rio Branco',
		'America/Indiana/Indianapolis' => '(GMT-05:00) Indiana (East)',
		'America/Caracas' => '(GMT-04:30) Caracas',
		'America/Halifax' => '(GMT-04:00) Atlantic Time (Canada)',
		'America/Manaus' => '(GMT-04:00) Manaus',
		'America/Santiago' => '(GMT-04:00) Santiago',
		'America/La_Paz' => '(GMT-04:00) La Paz',
		'America/St_Johns' => '(GMT-03:30) Newfoundland',
		'America/Argentina/Buenos_Aires' => '(GMT-03:00) Georgetown',
		'America/Sao_Paulo' => '(GMT-03:00) Brasilia',
		'America/Godthab' => '(GMT-03:00) Greenland',
		'America/Montevideo' => '(GMT-03:00) Montevideo',
		'Atlantic/South_Georgia' => '(GMT-02:00) Mid-Atlantic',
		'Atlantic/Azores' => '(GMT-01:00) Azores',
		'Atlantic/Cape_Verde' => '(GMT-01:00) Cape Verde Is.',
		'Europe/Dublin' => '(GMT) Dublin',
		'Europe/Lisbon' => '(GMT) Lisbon',
		'Europe/London' => '(GMT) London',
		'Africa/Monrovia' => '(GMT) Monrovia',
		'Atlantic/Reykjavik' => '(GMT) Reykjavik',
		'Africa/Casablanca' => '(GMT) Casablanca',
		'Europe/Belgrade' => '(GMT+01:00) Belgrade',
		'Europe/Bratislava' => '(GMT+01:00) Bratislava',
		'Europe/Budapest' => '(GMT+01:00) Budapest',
		'Europe/Ljubljana' => '(GMT+01:00) Ljubljana',
		'Europe/Prague' => '(GMT+01:00) Prague',
		'Europe/Sarajevo' => '(GMT+01:00) Sarajevo',
		'Europe/Skopje' => '(GMT+01:00) Skopje',
		'Europe/Warsaw' => '(GMT+01:00) Warsaw',
		'Europe/Zagreb' => '(GMT+01:00) Zagreb',
		'Europe/Brussels' => '(GMT+01:00) Brussels',
		'Europe/Copenhagen' => '(GMT+01:00) Copenhagen',
		'Europe/Madrid' => '(GMT+01:00) Madrid',
		'Europe/Paris' => '(GMT+01:00) Paris',
		'Africa/Algiers' => '(GMT+01:00) West Central Africa',
		'Europe/Amsterdam' => '(GMT+01:00) Amsterdam',
		'Europe/Berlin' => '(GMT+01:00) Berlin',
		'Europe/Rome' => '(GMT+01:00) Rome',
		'Europe/Stockholm' => '(GMT+01:00) Stockholm',
		'Europe/Vienna' => '(GMT+01:00) Vienna',
		'Europe/Minsk' => '(GMT+02:00) Minsk',
		'Africa/Cairo' => '(GMT+02:00) Cairo',
		'Europe/Helsinki' => '(GMT+02:00) Helsinki',
		'Europe/Riga' => '(GMT+02:00) Riga',
		'Europe/Sofia' => '(GMT+02:00) Sofia',
		'Europe/Tallinn' => '(GMT+02:00) Tallinn',
		'Europe/Vilnius' => '(GMT+02:00) Vilnius',
		'Europe/Athens' => '(GMT+02:00) Athens',
		'Europe/Bucharest' => '(GMT+02:00) Bucharest',
		'Europe/Istanbul' => '(GMT+02:00) Istanbul',
		'Asia/Jerusalem' => '(GMT+02:00) Jerusalem',
		'Asia/Amman' => '(GMT+02:00) Amman',
		'Asia/Beirut' => '(GMT+02:00) Beirut',
		'Africa/Windhoek' => '(GMT+02:00) Windhoek',
		'Africa/Harare' => '(GMT+02:00) Harare',
		'Asia/Kuwait' => '(GMT+03:00) Kuwait',
		'Asia/Riyadh' => '(GMT+03:00) Riyadh',
		'Asia/Baghdad' => '(GMT+03:00) Baghdad',
		'Africa/Nairobi' => '(GMT+03:00) Nairobi',
		'Asia/Tbilisi' => '(GMT+03:00) Tbilisi',
		'Europe/Moscow' => '(GMT+03:00) Moscow',
		'Europe/Volgograd' => '(GMT+03:00) Volgograd',
		'Asia/Tehran' => '(GMT+03:30) Tehran',
		'Asia/Muscat' => '(GMT+04:00) Muscat',
		'Asia/Baku' => '(GMT+04:00) Baku',
		'Asia/Yerevan' => '(GMT+04:00) Yerevan',
		'Asia/Yekaterinburg' => '(GMT+05:00) Ekaterinburg',
		'Asia/Karachi' => '(GMT+05:00) Karachi',
		'Asia/Tashkent' => '(GMT+05:00) Tashkent',
		'Asia/Kolkata' => '(GMT+05:30) Calcutta',
		'Asia/Colombo' => '(GMT+05:30) Sri Jayawardenepura',
		'Asia/Katmandu' => '(GMT+05:45) Kathmandu',
		'Asia/Dhaka' => '(GMT+06:00) Dhaka',
		'Asia/Almaty' => '(GMT+06:00) Almaty',
		'Asia/Novosibirsk' => '(GMT+06:00) Novosibirsk',
		'Asia/Rangoon' => '(GMT+06:30) Yangon (Rangoon)',
		'Asia/Krasnoyarsk' => '(GMT+07:00) Krasnoyarsk',
		'Asia/Bangkok' => '(GMT+07:00) Bangkok',
		'Asia/Jakarta' => '(GMT+07:00) Jakarta',
		'Asia/Brunei' => '(GMT+08:00) Beijing',
		'Asia/Chongqing' => '(GMT+08:00) Chongqing',
		'Asia/Hong_Kong' => '(GMT+08:00) Hong Kong',
		'Asia/Urumqi' => '(GMT+08:00) Urumqi',
		'Asia/Irkutsk' => '(GMT+08:00) Irkutsk',
		'Asia/Ulaanbaatar' => '(GMT+08:00) Ulaan Bataar',
		'Asia/Kuala_Lumpur' => '(GMT+08:00) Kuala Lumpur',
		'Asia/Singapore' => '(GMT+08:00) Singapore',
		'Asia/Taipei' => '(GMT+08:00) Taipei',
		'Australia/Perth' => '(GMT+08:00) Perth',
		'Asia/Seoul' => '(GMT+09:00) Seoul',
		'Asia/Tokyo' => '(GMT+09:00) Tokyo',
		'Asia/Yakutsk' => '(GMT+09:00) Yakutsk',
		'Australia/Darwin' => '(GMT+09:30) Darwin',
		'Australia/Adelaide' => '(GMT+09:30) Adelaide',
		'Australia/Canberra' => '(GMT+10:00) Canberra',
		'Australia/Melbourne' => '(GMT+10:00) Melbourne',
		'Australia/Sydney' => '(GMT+10:00) Sydney',
		'Australia/Brisbane' => '(GMT+10:00) Brisbane',
		'Australia/Hobart' => '(GMT+10:00) Hobart',
		'Asia/Vladivostok' => '(GMT+10:00) Vladivostok',
		'Pacific/Guam' => '(GMT+10:00) Guam',
		'Pacific/Port_Moresby' => '(GMT+10:00) Port Moresby',
		'Asia/Magadan' => '(GMT+11:00) Magadan',
		'Pacific/Fiji' => '(GMT+12:00) Fiji',
		'Asia/Kamchatka' => '(GMT+12:00) Kamchatka',
		'Pacific/Auckland' => '(GMT+12:00) Auckland',
		'Pacific/Tongatapu' => '(GMT+13:00) Nukualofa');
?>
<form method="post">
	<select name="tz">
	<option value="Pacific/Honolulu">(GMT-10:00) Hawaii</option>
	<option value="America/Anchorage">(GMT-09:00) Alaska</option>
	<option value="America/Los_Angeles">(GMT-08:00) Pacific Time (US &amp; Canada)</option>
	<option value="America/Phoenix">(GMT-07:00) Arizona</option>
	<option value="America/Denver">(GMT-07:00) Mountain Time (US &amp; Canada)</option>
	<option value="America/Chicago">(GMT-06:00) Central Time (US &amp; Canada)</option>
	<option value="America/New_York">(GMT-05:00) Eastern Time (US &amp; Canada)</option>
	<option value="America/Indiana/Indianapolis">(GMT-05:00) Indiana (East)</option>
	<option disabled="disabled">&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;</option>
<?php
foreach($zonelist as $key => $value) {
	echo '		<option value="' . $key . '">' . $value . '</option>' . "\n";
}
?>
	</select>
	<input type="submit" name="submit" value="Set" />
</form>

This can be included as part of a user registration form, and the setting can be stored using your preferred method (database, cookie, etc). Here, I listed all the US time zones first for convenience if your application is based in the US.

So now that we have the needed information from the user, we can start with the conversion code. There are two different conversions we might need to do: Timestamp -> User's Time and User's Time -> Timestamp. This is for displaying time to the user and collecting time from the user, respectively.

Converting a timestamp to the user's time:
PHP 5.2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// timestamp to convert (just an example)
$timestamp = 1240349566;
 
// set this to the time zone provided by the user
$tz = $_SESSION['tz'];
 
// create the DateTimeZone object for later
$dtzone = new DateTimeZone($tz);
 
// first convert the timestamp into a string representing the local time
$time = date('r', $timestamp);
 
// now create the DateTime object for this time
$dtime = new DateTime($time);
 
// convert this to the user's timezone using the DateTimeZone object
$dtime->setTimeZone($dtzone);
 
// print the time using your preferred format
$time = $dtime->format('g:i A m/d/y');

PHP 5.3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// timestamp to convert (just an example)
$timestamp = 1240349566;
 
// set this to the time zone provided by the user
$tz = $_SESSION['tz'];
 
// create the DateTimeZone object for later
$dtzone = new DateTimeZone($tz);
 
// create a DateTime object
$dtime = new DateTime();
 
// set it to the timestamp (PHP >= 5.3.0)
$dtime->setTimestamp($timestamp);
 
// convert this to the user's timezone using the DateTimeZone object
$dtime->setTimeZone($dtzone);
 
// print the time using your preferred format
$time = $dtime->format('g:i A m/d/y');

Converting user's time to a timestamp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// time to convert (just an example)
$time = 'Tuesday, April 21, 2009 2:32:46 PM';
 
// set this to the time zone provided by the user
$tz = $_SESSION['tz'];
 
// create the DateTimeZone object for later
$dtzone = new DateTimeZone($tz);
 
// now create the DateTime object for this time and user time zone
$dtime = new DateTime($time, $dtzone);
 
// print the timestamp
$timestamp = $dtime->format('U');

So there you have it. Those few lines of code is all it takes to convert time zones in PHP.

Sources:
http://www.php.net/manual/book.datetime.php
http://www.threadaffinity.com/blog/2008/09/friendly-timezone-names-for-php

Posted by Steve

Comments (34) Trackbacks (5)
  1. Question: Do you have any simple ideas on how to deal with Daylight Saving Time? Since they are different everywhere it turns into a major hassle quickly. I don’t have any examples of how-to off-hand, as I hav so far been lucky enough to avoid it.

    • If you use DateTime with DateTimeZone, it will handle the DST conversion. So if you use the method in this tutorial, DST will be accounted for. Just make sure your timezone database is up to date in case the DST rules change, you can updated it using PECL’s timezonedb. Hope that helps.

      • What about the time zone offsets in the select box? Like right now, Eastern Time is GMT-4 but when DST ends it will go back to GMT-5. How do I properly display the time zone offsets in the select box during DST? Gosh, DST is annoying.

  2. Why are the first load seperated from the foreach loop? You have done this very differently to phpbb they seem to have a +14 as well as +10.30 and such.

  3. is it easier to just add or subtract the timezone offsets ?

  4. @liamdawe
    The US based time zones are listed first for convenience. You can remove those or replace them with your site’s local time zones. Also, all the time zones are from the article listed in the sources.

    @digitalpbk
    Yes, but you also have to account for daylight savings time, which it different in each time zone. This method automatically handles all the factors involved.

  5. saved me hours of work

  6. Brilliant how-to; great work!

  7. Thanks for this, I’ve been looking for it for a long time.

  8. Awesome, this has solved a big headache for me. Thanks!

  9. Great work! Thanks.

  10. Nice job, it works! :-) Simple, fast, and Daylight Savings Compatible!

  11. How would you get the current ID so when a user hits Submit… it stays selected on what they just picked.

    Here my code so far that DOESN’T work right:
    //'; if ($value == isset($_POST['tz'])) { echo ‘selected'; } echo ‘

    • Try this in your if statement:
      if (isset($_POST['tz']) && $value == $_POST['tz'])

      This asks if it is set then asks if it is equal to the current value.

      • Almost! ;-)

        It’s this:
        f (isset($_POST['tz']) && $key == $_POST['tz']) {
        I changed $value to $key because that’s how it is.

  12. Thanks for the post – I was hoping time zone management would be this easy :)

  13. I ended up doing this:

    //set up
    $time_zone = new DateTimeZone($time_zone_code);
    $time = new DateTime();
    $time->setTimeStamp($timestamp);
    $time->setTimeZone($time_zone);

    //server time zone’d timestamp to $time_zone’d timestamp
    … = $timestamp + $time->getOffset()

    //$time_zone’d timestamp to server time zone’d timestamp
    … = $timestamp – $time->getOffset()

    • Hey Tim,

      Would you mind posting the full code. I tried using the local timestamp but it doesn’t seem to be accurate with other time zones!

  14. Thank you for this! You’ve saved me a good chunk of time having to research this myself!

  15. That’s exactly the tools I was looking for! You’ve saved me a good chunk of time too!

  16. Thank you!!! Very useful infos!

  17. Thanks! saved me a lot of time

  18. top post, thank you!

  19. Fantastic post…thanks so much!

  20. Please send dubai gmt time zones as example

    (‘Pacific/Samoa’ => ‘(GMT-11:00) Samoa’,)

    Thanks,
    Ajay
    Front End Developer

  21. thank you for this. How to display the date on a specific language, Portuguese for example?

  22. Awesome Awesome Awesome thanks for making the timezone list really saved me a lot itme

  23. clock not keeping time, the timezones on the page does not keep up with time when you stay on the page for a few minutes it still shows the time when the page loaded….. please help me for this case

  24. Sometimes it may be required to detect timezone automatically on PHP side. I’ve found this way:

    Detecting the timezone on PHP side could be done by using Geo-IP services. Like this one – http://smart-ip.net/geoip

    An example in PHP how to detect the user’s timezone automatically could be as follows:

    [code]
    <?php
    $ip = $_SERVER['REMOTE_ADDR']; // means we got user's IP address
    $json = file_get_contents( 'http://smart-ip.net/geoip-json/&#039; . $ip); // this one service we gonna use to obtain timezone by IP
    // maybe it's good to add some checks (if/else you've got an answer and if json could be decoded, etc.)
    $ipData = json_decode( $json, true);

    if ($ipData['timezone']) {
    $tz = new DateTimeZone( $ipData['timezone']);
    $now = new DateTime( 'now', $tz); // DateTime object corellated to user's timezone
    } else {
    // we can't determine a timezone - do something else...
    }
    [/code]

  25. I am creating an application where the local time zone is set once by the administrator of the application during installation and all users are within the same time zone. In this scenario I just needed a function which allows me to create a local time string for use in the database (not a unix time stamp).

    The function below seems to be doing the trick for me quite nicely:
    [code]

    function getLocalTime($format) {
    global $Config_timezone; // the user's time zone as defined in a configuration file (e.g. 'America/Los_Angeles')
    $time = date("Y-m-d H:i:s"); // Get server time string
    $dtzone = new DateTimeZone($Config_timezone); // create new timezone object with stored user timezone
    $stz = new DateTimeZone(date_default_timezone_get()); // create new timezone object with recognized server timezone
    $dtime = new DateTime($time, $stz); // create new datetime object with server time and server timezone
    $dtime-> setTimeZone($dtzone); // convert server time into user time
    $userTime = $dtime->format($format); // format time object into string
    return $userTime;
    }

    getLocalTime("Y-m-d H:i:s");
    [/code]

  26. AWESOME!!!!

  27. Nice tutorial.