I\'ll try to explain what\'s the problem here.
According to list of supported timezones from PHP manual, I can see all valid TZ identifiers in PHP.
My first
See in PHP sources on php_date.c and timezonemap.h that`s why I can say this is always in 101.111111% static info (but per php build).
If you want to get it dynamically, use timezone_abbreviations_list as DateTimeZone::listAbbreviations is a map to it.
As you can see all these values are just one time filled list for current PHP version.
So much faster solution is simple -- prepare somehow static file with retrieved ids one time per server during install of your app and use it.
For example:
function isValidTZ($zone) {
static $zones = null;
if (null === $zones) {
include $YOUR_APP_STORAGE . '/tz_list.php';
}
// isset is muuuch faster than array_key_exists and also than in_array
// so you should work with structure like [key => 1]
return isset($zones[$zone]);
}
tz_list.php should be like this:
<?php
$zones = array(
'Africa/Abidjan' => 1,
'Africa/Accra' => 1,
'Africa/Addis_Ababa' => 1,
// ...
);
I would research what changes the perfect array and use a basic caching mechanism (like store the array in a file, that you include and update when needed). You're currently optimizing building an array that is static for 99.9999% of all the requests.
Edit: Ok, static/dynamic.
if( !function_exists(timezone_version_get) )
{
function timezone_version_get() { return '2009.6'; }
}
include 'tz_list_' . PHP_VERSION . '_' . timezone_version_get() . '.php';
Now each time the php version is updated, the file should be regenerated automatically by your code.
You solution works fine, so if it's speed you're looking for I would look more closely at what you're doing with your arrays. I've timed a few thousand trials to get reasonable average times, and these are the results:
createTZlist : 20,713 microseconds per run
createTZlist2 : 13,848 microseconds per run
Here's the faster function:
function createTZList2()
{
$out = array();
$tza = timezone_abbreviations_list();
foreach ($tza as $zone)
{
foreach ($zone as $item)
{
$out[$item['timezone_id']] = 1;
}
}
unset($out['']);
ksort($out);
return array_keys($out);
}
The if
test is faster if you reduce it to just if ($item['timezone_id'])
, but rather than running it 489 times to catch a single case, it's quicker to unset the empty key afterwards.
Setting hash keys allows us the skip the array_unique()
call which is more expensive. Sorting the keys and then extracting them is a tiny bit faster than extracting them and then sorting the extracted list.
If you drop the sorting (which is not needed unless you're comparing the list), it gets down to 12,339 microseconds.
But really, you don't need to return the keys anyway. Looking at the holistic isValidTimezoneId()
, you'd be better off doing this:
function isValidTimezoneId2($tzid)
{
$valid = array();
$tza = timezone_abbreviations_list();
foreach ($tza as $zone)
{
foreach ($zone as $item)
{
$valid[$item['timezone_id']] = true;
}
}
unset($valid['']);
return !!$valid[$tzid];
}
That is, assuming you only need to test once per execution, otherwise you'd want to save $valid
after the first run. This approach avoids having to do a sort or converting the keys to values, key lookups are faster than in_array() searches and there's no extra function call. Setting the array values to true
instead of 1
also removes a single cast when the result is true.
This brings it reliably down to under 12ms on my test machine, almost half the time of your example. A fun experiment in micro-optimizations!
Just an addendum to Cal's excellent answer. I think the following might be even faster...
function isValidTimezoneID($tzid) {
if (empty($tzid)) {
return false;
}
foreach (timezone_abbreviations_list() as $zone) {
foreach ($zone as $item) {
if ($item["timezone_id"] == $tzid) {
return true;
}
}
}
return false;
}
In case of php<5.3, how about this?
public static function is_valid_timezone($timezone)
{
$now_timezone = @date_default_timezone_get();
$result = @date_default_timezone_set($timezone);
if( $now_timezone ){
// set back to current timezone
date_default_timezone_set($now_timezone);
}
return $result;
}
If you're on Linux most if not all information on timezones there stored at /usr/share/zoneinfo/
. You can walk over them using is_file()
and related functions.
You can also parse the former files with zdump
for codes or fetch sources for these files and grep/cut out needed info. Again, you are not obliged to use built-in functions to accomplish the task. There isn't a rationale why would someone force you to use only the built-in date functions.