Reputation: 21
I've got a database full of users that each have a timezone
column stored as a string (e.g. America/New_York
). (And I know that storing them as offsets in the database makes way more sense, but cannot be done for this project) I've got a form where the user is able to change their timezone. Here was my first approach :
I have this populating a drop down (value
=> display
):
protected $timezones = [
'+00:00' => '(UTC +00:00) Western European Time',
'+01:00' => '(UTC +01:00) Central European Time',
'+02:00' => '(UTC +02:00) Eastern European Time',
'+03:00' => '(UTC +03:00) Further-Eastern European Time',
'+04:00' => '(UTC +04:00) Gulf Standard Time',
'+05:00' => '(UTC +05:00) Pakistan Standard Time',
'+05:30' => '(UTC +05:30) Indian Standard Time',
'+05:45' => '(UTC +05:45) Nepal Time',
'+06:00' => '(UTC +06:00) British Indian Ocean Time',
'+07:00' => '(UTC +07:00) Thailand Standard Time',
'+08:00' => '(UTC +08:00) Australian Western Standard Time',
'+09:00' => '(UTC +09:00) Japan Standard Time',
'+10:00' => '(UTC +10:00) Australian Eastern Standard Time',
'+10:30' => '(UTC +10:30) Australian Central Daylight Savings Time',
'+11:00' => '(UTC +11:00) Australian Eastern Daylight Savings Time',
'+12:00' => '(UTC +12:00) New Zealand Standard Time',
'+13:00' => '(UTC +13:00) New Zealand Daylight Time',
'-01:00' => '(UTC -01:00) Eastern Greenland Time',
'-02:00' => '(UTC -02:00) Brasilia Summer Time',
'-03:00' => '(UTC -03:00) Atlantic Daylight Time',
'-04:00' => '(UTC -04:00) Atlantic Standard Time',
'-05:00' => '(UTC -05:00) Central Daylight Time',
'-06:00' => '(UTC -06:00) Central Standard Time',
'-07:00' => '(UTC -07:00) Pacific Daylight Time',
'-08:00' => '(UTC -08:00) Pacific Standard Time',
'-09:00' => '(UTC -09:00) Alaska Standard Time',
'-10:00' => '(UTC -10:00) Hawaii-Aleutian Standard Time',
'-11:00' => '(UTC -11:00) Niue Time',
];
This function to convert timezone string to offset :
/**
* convert timezone string to offset
* e.g. "America/New_York" to "-04:00"
*
* @param string $timezone time zone represented as a string
*
* @return mixed string or null
*/
protected function convertStringToOffset($timezone)
{
$time = new \DateTime('now', new DateTimeZone($timezone));
if ($time) {
return $time->format('P');
}
}
And this function to convert an offset to a string :
/**
* convert timezone offset to string
* e.g. "-04:00" to "America/New_York"
*
* @param string $offset time zone represented as a numerical offset
*
* @return string time zone represented as a string
*/
protected function convertOffsetToString($offset)
{
// Calculate seconds from offset
list($hours, $minutes) = explode(':', $offset);
$seconds = $hours * 60 * 60 + $minutes * 60;
// Get timezone name from seconds
$tz = timezone_name_from_abbr('', $seconds, 1);
// Workaround for bug #44780
if ($tz === false) {
$tz = timezone_name_from_abbr('', $seconds, 0);
}
return $tz;
}
So on page load, I would pull the timezone from the database and convert it to it's offset. For example America/New_York
would be converted to -04:00
and the (UTC -04:00) Atlantic Standard Time
would be selected.
Now the user would select a different timezone and submit the form. I would then convert the offset into a string and then store that into the database.
I ran into a problem :
$this->convertOffsetToString('+03:00');
returns Europe/Helsinki
$this->convertOffsetToString('+04:00');
returns Europe/Moscow
but!
$this->convertStringToOffset('Europe/Moscow');
returns +03:00
$this->convertStringToOffset('Europe/Helsinki');
returns +03:00
So if a user came to the form and had the timezone Europe/Moscow
in the database, we would convert the string to it's offset getting +03:00
and the '(UTC +03:00) Further-Eastern European Time'
would be selected.
Problem : two different offsets (e.g. +03:00
and +04:00
) will be converted into two different string time zones. Those two different string timezones would be converted into the same offset! (e.g. +03:00
). Can anyone come up with a safe scale-able solution to tackle this problem?
Upvotes: 2
Views: 4324
Reputation: 18440
First of all,
I know that storing them as offsets in the database makes way more sense
No, it doesn't. Offsets change. Many timezones apply daylight saving for part of the year and the dates/times at which they change vary.
You should store the users timezone as a string from this list and then use that to create an instance of \DateTimeZone. For example:-
$tzString = 'Europe/London';
$tz = new \DateTimeZone($tzString);
You are then able to get the correct offset for any time you wish using something like:-
$tz->getOffset(new \DateTime());
Example. Note that the offset produced in the example will vary between 0 and 3600 depending on what date and time you are viewing it.
As you are storing the users timezone you never have to face the futility of trying to convert an offset to the correct timezone.
I would suggest that this is a "safe scaleable solution" to your problem.
Upvotes: 8