Apr/099
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:
- Collect the time zone from the user and store in Area/Location format
- Create a DateTimeZone object using the provided time zone
- Create a DateTime object, providing it the time in the local time zone
- Convert the DateTime object to the time zone created in step 1
- 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 & Canada)', 'America/Tijuana' => '(GMT-08:00) Tijuana, Baja California', 'America/Denver' => '(GMT-07:00) Mountain Time (US & 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 & Canada)', 'America/Mexico_City' => '(GMT-06:00) Mexico City', 'America/Monterrey' => '(GMT-06:00) Monterrey', 'America/New_York' => '(GMT-05:00) Eastern Time (US & 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 & Canada)</option> <option value="America/Phoenix">(GMT-07:00) Arizona</option> <option value="America/Denver">(GMT-07:00) Mountain Time (US & Canada)</option> <option value="America/Chicago">(GMT-06:00) Central Time (US & Canada)</option> <option value="America/New_York">(GMT-05:00) Eastern Time (US & Canada)</option> <option value="America/Indiana/Indianapolis">(GMT-05:00) Indiana (East)</option> <option disabled="disabled">——————–</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
Similar Posts:
- TempServers Status Update
- Snippet: Converting Seconds to Readable Time (PHP & JS)
- Fetch Twitter Updates in PHP

June 26th, 2009
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.
June 27th, 2009
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.
July 3rd, 2009
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.
July 9th, 2009
is it easier to just add or subtract the timezone offsets ?
July 10th, 2009
@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.
August 27th, 2009
saved me hours of work
October 26th, 2009
Brilliant how-to; great work!
November 5th, 2009
Thanks for this, I’ve been looking for it for a long time.
January 2nd, 2010
Awesome, this has solved a big headache for me. Thanks!