i have found the solution mysqlf using:
foreach ($output as $value) {
if (strpos($value, "]:") > -1) {
$tal = substr($value, strpos($value, "]:") +3) . "<br>";
echo $tal;
}
}
this returns:
-210
-212
Thanks in advance.
I want to preg so i only get the line: [10] => [147]: -210
or both [10] => [147]: -210 and [21] => [148]: -212
how can i preg [147]: or is there a better way to get the specific information?
my array $output contains:
Array
(
[0] => modpoll 3.4 - FieldTalk(tm) Modbus(R) Master Simulator
[1] => Copyright (c) 2002-2013 proconX Pty Ltd
[2] => Visit http://www.modbusdriver.com for Modbus libraries and tools.
[3] =>
[4] => Protocol configuration: MODBUS/TCP
[5] => Slave configuration...: address = 1, start reference = 147, count = 1
[6] => Communication.........: 10.234.6.11, port 502, t/o 1.00 s, poll rate 1000 ms
[7] => Data type.............: 16-bit register, output (holding) register table
[8] =>
[9] => -- Polling slave...
[10] => [147]: -210
[11] => modpoll 3.4 - FieldTalk(tm) Modbus(R) Master Simulator
[12] => Copyright (c) 2002-2013 proconX Pty Ltd
[13] => Visit http://www.modbusdriver.com for Modbus libraries and tools.
[14] =>
[15] => Protocol configuration: MODBUS/TCP
[16] => Slave configuration...: address = 1, start reference = 148, count = 1
[17] => Communication.........: 10.234.6.11, port 502, t/o 1.00 s, poll rate 1000 ms
[18] => Data type.............: 16-bit register, output (holding) register table
[19] =>
[20] => -- Polling slave...
[21] => [148]: -212
)
$matches = preg_grep ('/^[147] (\w+)/i', $output);
print_r ($matches);
//only returns Array()
You need to escape the opening square bracket because [147] is seen as a character class that contains 1, 4 and 7
You can do this with:
$result=preg_grep('~^\[14(?:7|8)]:~',$rgData);
print_r($result);
You can find all that you want to know about escaping (or not) square brackets here
Related
I have a multidimensional array in PHP, and want to concatenate a string onto each string element using recursion. The array is as follows:
$array = Array
(
[p] => Array
(
[0] => This Porsche 993 Carrera Cabriolet represents a great opportunity to acquire an open-top variant of one of the most coveted 911 models.
[1] => First registered on 5 August 1994, M912 SGY displays 10,630 miles on the odometer with a clock change at 66,244 miles in 2014.
[2] => The car’s Aventura Green metallic paintwork is reported to be in good condition, presenting well for its age and mileage.
[3] => The Marble Grey leather interior is believed to be entirely original.
[4] => Serviced by Porsche specialist Portiacraft in July 2020 at 76,598 miles, this consisted of an annual oil and filter service.
[5] => The last MOT was undertaken on 6 July 2020 at 76,598 miles.
[6] => It is supplied with a Porsche Club Great Britain folder with records of main dealer and specialist service history.
[7] => This Porsche 911 Carrera Cabriolet presents in highly original and well-maintained condition.
[8] => Summary of maintenance history:
[9] => Array
(
[strong] => The description of this auction lot is, to the best of the seller's knowledge, accurate and not misleading.
)
[10] => Array
(
[strong] => All UK-registered cars and motorbikes on Collecting Cars are run through an online HPI check. This vehicle shows no insurance database markers for damage or theft, and has no finance owing.
)
)
[ul] => Array
(
[li] => Array
(
[0] => 04/11/1996 – 16,120 miles
[1] => 18/11/1998 – 25,086 miles
[2] => 09/09/1999 – 28,769 miles
[3] => 21/02/2000 – 31,469 miles
[4] => 22/06/2001 – 36,055 miles
[5] => 29/10/2002 – 40,781 miles
[6] => 02/03/2005 – 46,238 miles
[7] => 24/03/2006 – 49,459 miles
[8] => 03/07/2007 – 53,051 miles
[9] => 17/12/2008 – 56,582 miles
[10] => 20/05/2010 – 57,385 miles
[11] => 08/06/2011 – 61,653 miles
[12] => 15/05/2012 – 64,425 miles
[13] => 17/04/2013 – 66,026 miles
[14] => 07/06/2014 – 66,244 miles
[15] => 14/09/2015 – 68,411 miles
[16] => 27/02/2018 – 74,856 miles
[17] => 06/08/2019 – ~76,400 miles
[18] => 06/07/2020 – 76,598 miles
)
)
)
Ideally, the result should look like this:
$array = Array
(
[p] => Array
(
[0] => This Porsche 993 Carrera Cabriolet represents a great opportunity to acquire an open-top variant of one of the most coveted 911 models. checked
[1] => First registered on 5 August 1994, M912 SGY displays 10,630 miles on the odometer with a clock change at 66,244 miles in 2014. checked
[2] => The car’s Aventura Green metallic paintwork is reported to be in good condition, presenting well for its age and mileage. checked
[3] => The Marble Grey leather interior is believed to be entirely original. checked
[4] => Serviced by Porsche specialist Portiacraft in July 2020 at 76,598 miles, this consisted of an annual oil and filter service. checked
[5] => The last MOT was undertaken on 6 July 2020 at 76,598 miles. checked
[6] => It is supplied with a Porsche Club Great Britain folder with records of main dealer and specialist service history. checked
[7] => This Porsche 911 Carrera Cabriolet presents in highly original and well-maintained condition. checked
[8] => Summary of maintenance history: checked
[9] => Array
(
[strong] => The description of this auction lot is, to the best of the seller's knowledge, accurate and not misleading. checked
)
[10] => Array
(
[strong] => All UK-registered cars and motorbikes on Collecting Cars are run through an online HPI check. This vehicle shows no insurance database markers for damage or theft, and has no finance owing. checked
)
)
[ul] => Array
(
[li] => Array
(
[0] => 04/11/1996 – 16,120 miles checked
[1] => 18/11/1998 – 25,086 miles checked
[2] => 09/09/1999 – 28,769 miles checked
[3] => 21/02/2000 – 31,469 miles checked
[4] => 22/06/2001 – 36,055 miles checked
[5] => 29/10/2002 – 40,781 miles checked
[6] => 02/03/2005 – 46,238 miles checked
[7] => 24/03/2006 – 49,459 miles checked
[8] => 03/07/2007 – 53,051 miles checked
[9] => 17/12/2008 – 56,582 miles checked
[10] => 20/05/2010 – 57,385 miles checked
[11] => 08/06/2011 – 61,653 miles checked
[12] => 15/05/2012 – 64,425 miles checked
[13] => 17/04/2013 – 66,026 miles checked
[14] => 07/06/2014 – 66,244 miles checked
[15] => 14/09/2015 – 68,411 miles checked
[16] => 27/02/2018 – 74,856 miles checked
[17] => 06/08/2019 – ~76,400 miles checked
[18] => 06/07/2020 – 76,598 miles checked
)
)
)
I have tried the following:
$addedChecked = $this->addCheckedRecursive($array);
private function addCheckedRecursive($array)
{
if(!is_array($array)) {
return $array . ' checked';
}
foreach($array as $v) {
$this->addCheckedRecursive($v);
}
}
and also
$addedChecked = array_walk_recursive($array, function (&$value) {
$value .= ' checked';
});
The latter simply returned true.
For info, every element of each array will always be a string, and I would also like to preserve the current array structure. Any help is appreciated.
There is an in-built function that you can use to achieve what you want.
If you use array_walk_recursive as follows:
// Say you have your array $xmlArray
array_walk_recursive($xmlArray, function (&$value) {
$value .= ' checked';
});
// Since $xmlArray is now modified (in place)
echo '<pre>';
print_r($xmlArray);
echo '</pre>';
In case you would not want $xmlArray to be changed as a side effect, could assign a value copy of the array to a new variable.
$addedChecked = $xmlArray;
array_walk_recursive($addedChecked, function (&$value) {
$value .= ' checked';
});
// Since $addedChecked is now modified (in place)
echo '<pre>';
print_r($addedChecked);
echo '</pre>';
The function takes in the array by reference and will thus modify the array directly as a side effect of the function. This is one important thing to note, it does not return an array, but only whether the function was successfully executed on the array you gave it.
This will loop over each key => value pair and do so recursively if the value is of type array. You can simply concatenate to the value (where we pass value by reference) to update it with checked.
why dont you try array_walk_recursive or array_replace_recursive ?
the docs can be found here
Try it like this:
$addedChecked = $this->addCheckedRecursive($array);
private function addCheckedRecursive($array)
{
if (!is_array($array)) {
return $array . ' checked';
}
else
{
for ($i = 0; $i < count($array); $i++) {
$array[$i] = $this->addCheckedRecursive($array[$i]);
}
return $array;
}
}
I am writing a reporting app that needs to consume logs which have been stored in the DB as base 64 encoded strings. I am able to decode them no problem, however, I am having some trouble getting them to be fed into str_getcsv() properly.
Below is the data I am working with, the code and the outputs. It seems to me that once decoded the files are not recognizable as tab-delimited. However, if I decode it with this URL and and save as a text file, I can open it properly in excel.
https://www.base64decode.org/
In PHP however, it seems to be an issue with recognizing some of the tabs and the line breaks seem to completely go away. I think it has to do with the encoding, the DB table and column are both UTF-8. They are being recognized as ASCII - which is a subset of UTF-8, but I am not sure if they need to be explicitly UTF-8 for it to work (the site that works uses UTF-8).
The code: very simple (though at this point I may be going overboard with the encoding)
// get the stored result (laravel eloquent)
$media_result = MediaResult::where("video_id", "=", $media_benchmark->id)->firstOrFail();
# decode the access_log stored as b64 string
$tab_file = base64_decode(mb_convert_encoding($media_result->access_log, "UTF-8"));
$encoding = mb_detect_encoding($tab_file); // I was using iconv() so I grabbed this - it is always ASCII
$new_file = mb_convert_encoding($tab_file,'UTF-8');
$encoding_new = mb_detect_encoding($new_file);
#if I were to echo both encoding variables, it would be ASCII - no matter what I do.
# convert the supposed tab-delimited file into an array
$full_stats = str_getcsv($new_file, 0, "\t");
Here is a sample base64 encoded log:
VVJJCXNlcnZlckFkZHJlc3MJbnVtYmVyT2ZTZXJ2ZXJBZGRyZXNzQ2hhbmdlcwltZWRpYVJlcXVlc3RzV1dBTgl0cmFuc2ZlckR1cmF0aW9uCW51bWJlck9mQnl0ZXNUcmFuc2ZlcnJlZAludW1iZXJPZk1lZGlhUmVxdWVzdHMJcGxheWJhY2tTdGFydERhdGUJcGxheWJhY2tTZXNzaW9uSUQJcGxheWJhY2tTdGFydE9mZnNldAlwbGF5YmFja1R5cGUJc3RhcnR1cFRpbWUJZHVyYXRpb25XYXRjaGVkCW51bWJlck9mRHJvcHBlZFZpZGVvRnJhbWVzCW51bWJlck9mU3RhbGxzCW51bWJlck9mU2VnbWVudHNEb3dubG9hZGVkCXNlZ21lbnRzRG93bmxvYWRlZER1cmF0aW9uCWRvd25sb2FkT3ZlcmR1ZQlvYnNlcnZlZEJpdHJhdGVTdGFuZGFyZERldmlhdGlvbglvYnNlcnZlZE1heEJpdHJhdGUJb2JzZXJ2ZWRNaW5CaXRyYXRlCXN3aXRjaEJpdHJhdGUJaW5kaWNhdGVkQml0cmF0ZQlvYnNlcnZlZEJpdHJhdGUKaHR0cDovL3Zldm9wbGF5bGlzdC1saXZlLmhscy5hZGFwdGl2ZS5sZXZlbDMubmV0L3Zldm8vY2gxLzAxL3Byb2dfaW5kZXgubTN1OAk4LjI1NC4yMy4yNTQJMAkwCTAuNjc4MjgwNzA5CTEwOTk2MTIJMwkyMDE2LTA1LTEwIDE5OjIxOjE4ICswMDAwCTdBMTI5MERDLTE2MzAtNDlGQy1BQTY0LUNDNzZDMTgxQzcyQQk0MglMSVZFCTAuMjUzMjk3OTg0NjAwMDY3MQkxNi4wODMyNjU5NjAyMTY1MgkwCTAJMwkxOAkwCS0xCTI1NTcyOTAxLjM4MzMwNzg3CTE4MjA3OTg3LjMyODUyNTkJMTAxMTU1NDguNzgzODE4MjUJNDkyMDAwCTIxMDI1OTU1LjA1Mzg4OTI0Cmh0dHA6Ly92ZXZvcGxheWxpc3QtbGl2ZS5obHMuYWRhcHRpdmUubGV2ZWwzLm5ldC92ZXZvL2NoMS8wNi9wcm9nX2luZGV4Lm0zdTgJOC4yNTMuMzIuMTI2CTgJMAkzNS43NDAxNjM2MjIJMTIzNDgxOTcyCTQzCTIwMTYtMDUtMTAgMTk6MjE6MzQgKzAwMDAJN0ExMjkwREMtMTYzMC00OUZDLUFBNjQtQ0M3NkMxODFDNzJBCTU4LjAyODk5NDM1OAlMSVZFCTAJMjQxLjkyNjk3NTk2NTQ5OTkJMAkwCTQzCTI1OAkwCS0xCTQ2ODg1OTAzLjAzNTk4OTkzCTEwODA3NDU3LjM4MjQwNjY3CS0xCTQwMDAwMDAJMzE3ODIzNjAuNjE0NTI4NjM=
Here is the same string decoded:
URI serverAddress numberOfServerAddressChanges mediaRequestsWWAN transferDuration numberOfBytesTransferred numberOfMediaRequests playbackStartDate playbackSessionID playbackStartOffset playbackType startupTime durationWatched numberOfDroppedVideoFrames numberOfStalls numberOfSegmentsDownloaded segmentsDownloadedDuration downloadOverdue observedBitrateStandardDeviation observedMaxBitrate observedMinBitrate switchBitrate indicatedBitrate observedBitrate http://vevoplaylist-live.hls.adaptive.level3.net/vevo/ch1/01/prog_index.m3u8 8.254.23.254 0 0 0.678280709 1099612 3 2016-05-10 19:21:18 +0000 7A1290DC-1630-49FC-AA64-CC76C181C72A 42 LIVE 0.2532979846000671 16.08326596021652 0 0 3 18 0 -1 25572901.38330787 18207987.3285259 10115548.78381825 492000 21025955.05388924 http://vevoplaylist-live.hls.adaptive.level3.net/vevo/ch1/06/prog_index.m3u8 8.253.32.126 8 0 35.740163622 123481972 43 2016-05-10 19:21:34 +0000 7A1290DC-1630-49FC-AA64-CC76C181C72A 58.028994358 LIVE 0 241.9269759654999 0 0 43 258 0 -1 46885903.03598993 10807457.38240667 -1 4000000 31782360.61452863
Finally, here is the resulting array:
Array ( [0] => URI serverAddress numberOfServerAddressChanges mediaRequestsWWAN transferDuration numberOfBytesTransferred numberOfMediaRequests playbackStartDate playbackSessionID playbackStartOffset playbackType startupTime durationWatched numberOfDroppedVideoFrames numberOfStalls numberOfSegmentsDownloaded segmentsDownloadedDuration downloadOverdue observedBitrateStandardDeviation observedMaxBitrate observedMinBitrate switchBitrate indicatedBitrate observedBitrate http://vevoplaylist-live.hls.adaptive.level3.net/vevo/ch1/ [1] => 1/prog_index.m3u8 8.254.23.254 [2] => 0 [3] => .67828 [4] => 7 [5] => 9 1 [6] => 99612 3 2 [7] => 16- [8] => 5-1 [9] => 19:21:18 + [10] => [11] => [12] => [13] => 7A1290DC-1630-49FC-AA64-CC76C181C72A42 LIVE [14] => .2532979846 [15] => [16] => [17] => 671 16. [18] => 8326596 [19] => 21652 [20] => 03 18 [21] => -1255729 [22] => 1.3833 [23] => 787 182 [24] => 7987.3285259 1 [25] => 115548.78381825 492 [26] => [27] => [28] => 21025955.05388924 http://vevoplaylist-live.hls.adaptive.level3.net/vevo/ch1/06/prog_index.m3u88.253.32.126 8 [29] => 35.740163622123481972 43 2 [30] => 16- [31] => 5-1 [32] => 19:21:34 + [33] => [34] => [35] => [36] => 7A1290DC-1630-49FC-AA64-CC76C181C72A58. [37] => 28994358 LIVE [38] => 241.9269759654999 [39] => 043 258 [40] => -1468859 [41] => 3. [42] => 3598993 1 [43] => 8 [44] => 7457.3824 [45] => 667 -1 4 [46] => [47] => [48] => [49] => [50] => [51] => 31782360.61452863 )
Keep in mind that str_getcsv()
parses only one line of a csv file
expects the delimiter "\t" to be the second parameter, not the third
You probably want something like:
$full_stats = [];
foreach(explode("\n", $decoded) as $line) {
$full_stats[] = str_getcsv($line, "\t");
}
var_dump($full_stats);
This will output an array containing 3 arrays (aka rows) containing 24 items (aka columns) each.
See http://sandbox.onlinephpfunctions.com/code/1ccf5115df6f8c342ff7c7e451f3ea26e081197e for working example and generated output.
Regarding the import of data that contains line breaks you should switch to fget_csv() which handles line breaks correctly:
$csv = <<< eot
"first","my data
with line breaks"
"second", "simple data"
eot;
// We need to "convert" the string to a file handle
$fp = fopen('data://text/plain,' . $csv,'r');
while ($data = fgetcsv($fp)) {
var_dump($data);
}
I have simple csv file which is tab delimited which i have to use as it is because it is coming from somewhere and i hvae to read it and insert it into my db i have used a simple php code to read it
if(($handle = fopen("var/import/MMT29DEC.csv","r"))!==FALSE){
/*Skip the first row*/
fgetcsv($handle, 1000,chr(9));
while(($data = fgetcsv($handle,1000,chr(9)))!==FALSE){
print_r($data[0]);
}
}
When print_r the data it shows like
Array ( [0] => 01SATAPC [1] => 40ATAPC [2] => [3] => 21P [4] => SERIAL ATA POWER CABLE [5] => 0.00 [6] => 2.00 [7] => 0 [8] => Power Supplies [9] => SERIAL ATA POWER CABLE [10] =>
4 TO 15 PIN 160MM
[11] => [12] => [13] => [14] => MELBHO [15] => 0.000 [16] => [17] => Order to Order [18] => 4 [19] => 2013-01-18 )
Which is the desired result but when i go to access the particular column value using the $data['index'] e.g. $data[8] or $data[1] it weirdly giving me garbage values says for some iterations it give me right values but after 10-15 rows its starting giving me the some numbers and other column values..... i don't know whats is going on with this as far as i know it should be formatting issue i have tried open the file in excel and its coming fine....
#ravisoni are you sure that the second parameter to fgetcsv of 1000 is longer than the longest line in your file? Try setting it to 0 as the docs say [php.net/fgetcsv] and see if that makes a difference.
if(($handle = fopen("var/import/MMT29DEC.csv","r"))!==FALSE){
/*Skip the first row*/
fgetcsv($handle, 0,chr(9));
while(($data = fgetcsv($handle,0,chr(9)))!==FALSE){
print_r($data[0]);
}
}
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 question is how to get that list from code but that's not what I really need.
My final goal is to write function isValidTimezoneId() that returns TRUE if timezone is valid, otherwise it should return FALSE.
function isValidTimezoneId($timezoneId) {
# ...function body...
return ?; # TRUE or FALSE
}
So, when I pass TZ identifier using $timezoneId (string) in function I need boolean result.
Well, what I have so far...
1) Solution using # operator
First solution I've got is something like this:
function isValidTimezoneId($timezoneId) {
$savedZone = date_default_timezone_get(); # save current zone
$res = $savedZone == $timezoneId; # it's TRUE if param matches current zone
if (!$res) { # 0r...
#date_default_timezone_set($timezoneId); # try to set new timezone
$res = date_default_timezone_get() == $timezoneId; # it's true if new timezone set matches param string.
}
date_default_timezone_set($savedZone); # restore back old timezone
return $res; # set result
}
That works perfectly, but I want another solution (to avoid trying to set wrong timezone)
2) Solution using timezone_identifiers_list()
Then, I was trying to get list of valid timezone identifiers and check it against parameter using in_array() function. So I've tried to use timezone_identifiers_list(), but that was not so good because a lot of timezones was missing in array returned by this function (alias of DateTimeZone::listIdentifiers()). At first sight that was exactly what I was looking for.
function isValidTimezoneId($timezoneId) {
$zoneList = timezone_identifiers_list(); # list of (all) valid timezones
return in_array($timezoneId, $zoneList); # set result
}
This code looks nice and easy but than I've found that $zoneList array contains ~400 elements. According to my calculations it should return 550+ elements. 150+ elements are missing... So that's not good enough as solution for my problem.
3) Solution based on DateTimeZone::listAbbreviations()
This is last step on my way trying to find perfect solution. Using array returned by this method I can extract all timezone identifiers supported by PHP.
function createTZlist() {
$tza = DateTimeZone::listAbbreviations();
$tzlist = array();
foreach ($tza as $zone)
foreach ($zone as $item)
if (is_string($item['timezone_id']) && $item['timezone_id'] != '')
$tzlist[] = $item['timezone_id'];
$tzlist = array_unique($tzlist);
asort($tzlist);
return array_values($tzlist);
}
This function returns 563 elements (in Example #2 I've got just 407).
I've tried to find differences between those two arrays:
$a1 = timezone_identifiers_list();
$a2 = createTZlist();
print_r(array_values(array_diff($a2, $a1)));
Result is:
Array
(
[0] => Africa/Asmera
[1] => Africa/Timbuktu
[2] => America/Argentina/ComodRivadavia
[3] => America/Atka
[4] => America/Buenos_Aires
[5] => America/Catamarca
[6] => America/Coral_Harbour
[7] => America/Cordoba
[8] => America/Ensenada
[9] => America/Fort_Wayne
[10] => America/Indianapolis
[11] => America/Jujuy
[12] => America/Knox_IN
[13] => America/Louisville
[14] => America/Mendoza
[15] => America/Porto_Acre
[16] => America/Rosario
[17] => America/Virgin
[18] => Asia/Ashkhabad
[19] => Asia/Calcutta
[20] => Asia/Chungking
[21] => Asia/Dacca
[22] => Asia/Istanbul
[23] => Asia/Katmandu
[24] => Asia/Macao
[25] => Asia/Saigon
[26] => Asia/Tel_Aviv
[27] => Asia/Thimbu
[28] => Asia/Ujung_Pandang
[29] => Asia/Ulan_Bator
[30] => Atlantic/Faeroe
[31] => Atlantic/Jan_Mayen
[32] => Australia/ACT
[33] => Australia/Canberra
[34] => Australia/LHI
[35] => Australia/NSW
[36] => Australia/North
[37] => Australia/Queensland
[38] => Australia/South
[39] => Australia/Tasmania
[40] => Australia/Victoria
[41] => Australia/West
[42] => Australia/Yancowinna
[43] => Brazil/Acre
[44] => Brazil/DeNoronha
[45] => Brazil/East
[46] => Brazil/West
[47] => CET
[48] => CST6CDT
[49] => Canada/Atlantic
[50] => Canada/Central
[51] => Canada/East-Saskatchewan
[52] => Canada/Eastern
[53] => Canada/Mountain
[54] => Canada/Newfoundland
[55] => Canada/Pacific
[56] => Canada/Saskatchewan
[57] => Canada/Yukon
[58] => Chile/Continental
[59] => Chile/EasterIsland
[60] => Cuba
[61] => EET
[62] => EST
[63] => EST5EDT
[64] => Egypt
[65] => Eire
[66] => Etc/GMT
[67] => Etc/GMT+0
[68] => Etc/GMT+1
[69] => Etc/GMT+10
[70] => Etc/GMT+11
[71] => Etc/GMT+12
[72] => Etc/GMT+2
[73] => Etc/GMT+3
[74] => Etc/GMT+4
[75] => Etc/GMT+5
[76] => Etc/GMT+6
[77] => Etc/GMT+7
[78] => Etc/GMT+8
[79] => Etc/GMT+9
[80] => Etc/GMT-0
[81] => Etc/GMT-1
[82] => Etc/GMT-10
[83] => Etc/GMT-11
[84] => Etc/GMT-12
[85] => Etc/GMT-13
[86] => Etc/GMT-14
[87] => Etc/GMT-2
[88] => Etc/GMT-3
[89] => Etc/GMT-4
[90] => Etc/GMT-5
[91] => Etc/GMT-6
[92] => Etc/GMT-7
[93] => Etc/GMT-8
[94] => Etc/GMT-9
[95] => Etc/GMT0
[96] => Etc/Greenwich
[97] => Etc/UCT
[98] => Etc/UTC
[99] => Etc/Universal
[100] => Etc/Zulu
[101] => Europe/Belfast
[102] => Europe/Nicosia
[103] => Europe/Tiraspol
[104] => Factory
[105] => GB
[106] => GB-Eire
[107] => GMT
[108] => GMT+0
[109] => GMT-0
[110] => GMT0
[111] => Greenwich
[112] => HST
[113] => Hongkong
[114] => Iceland
[115] => Iran
[116] => Israel
[117] => Jamaica
[118] => Japan
[119] => Kwajalein
[120] => Libya
[121] => MET
[122] => MST
[123] => MST7MDT
[124] => Mexico/BajaNorte
[125] => Mexico/BajaSur
[126] => Mexico/General
[127] => NZ
[128] => NZ-CHAT
[129] => Navajo
[130] => PRC
[131] => PST8PDT
[132] => Pacific/Ponape
[133] => Pacific/Samoa
[134] => Pacific/Truk
[135] => Pacific/Yap
[136] => Poland
[137] => Portugal
[138] => ROC
[139] => ROK
[140] => Singapore
[141] => Turkey
[142] => UCT
[143] => US/Alaska
[144] => US/Aleutian
[145] => US/Arizona
[146] => US/Central
[147] => US/East-Indiana
[148] => US/Eastern
[149] => US/Hawaii
[150] => US/Indiana-Starke
[151] => US/Michigan
[152] => US/Mountain
[153] => US/Pacific
[154] => US/Pacific-New
[155] => US/Samoa
[156] => Universal
[157] => W-SU
[158] => WET
[159] => Zulu
)
This list contains all valid TZ identifiers that Example #2 failed to match.
There's four TZ identifiers more (part of $a1):
print_r(array_values(array_diff($a1, $a2)));
Output
Array
(
[0] => America/Bahia_Banderas
[1] => Antarctica/Macquarie
[2] => Pacific/Chuuk
[3] => Pacific/Pohnpei
)
So now, I have almost perfect solution...
function isValidTimezoneId($timezoneId) {
$zoneList = createTZlist(); # list of all valid timezones (last 4 are not included)
return in_array($timezoneId, $zoneList); # set result
}
That's my solution and I can use it. Of course, I use this function as part of class so I don't need to generate $zoneList on every methods call.
What I really need here?
I'm wondering, is there any easier (quicker) solution to get list of all valid timezone identifiers as array (I want to avoid extracting that list from DateTimeZone::listAbbreviations() if that's possible)? Or if you know another way how to check is timezone parameter valid, please let me know (I repeat, # operator can't be part of solution).
P.S. If you need more details and examples, let me know. I guess you don't.
I'm using PHP 5.3.5 (think that's not important).
Update
Any part of code that throws exception on invalid timezone string (hidden using # or caught using try..catch block) is not solution I'm looking for.
Another update
I've put small bounty on this question!
Now I'm looking for the easiest way how to extract list of all timezone identifiers in PHP array.
Why not use # operator?
This code works pretty well, and you don't change default timezone:
function isValidTimezoneId($timezoneId) {
#$tz=timezone_open($timezoneId);
return $tz!==FALSE;
}
If you don't want #, you can do:
function isValidTimezoneId($timezoneId) {
try{
new DateTimeZone($timezoneId);
}catch(Exception $e){
return FALSE;
}
return TRUE;
}
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!
When I tried this on a Linux system running 5.3.6, your Example #2 gave me 411 zones and Example #3 gave 496. The following slight modification to Example #2 gives me 591:
$zoneList = timezone_identifiers_list(DateTimeZone::ALL_WITH_BC);
There are no zones returned by Example #3 that are not returned with that modified Example #2.
On an OS X system running 5.3.3, Example #2 gives 407, Example #3 gives 564, and the modified Example #2 gives 565. Again, there are no zones returned by Example #3 that are not returned with that modified Example #2.
On a Linux system running 5.2.6 with the timezonedb PECL extension installed, Example #2 gives me 571 zones and Example #3 gives me only 488. There are no zones returned by Example #3 that are not by Example #2 on this system. The constant DateTimeZone::ALL_WITH_BC does not seem to exist in 5.2.6; it was probably added in 5.3.0.
So it seems the simplest way to get a list of all time zones in 5.3.x is
timezone_identifiers_list(DateTimeZone::ALL_WITH_BC), and in 5.2.x is timezone_identifiers_list(). The simplest (if not fastest) way to check if a particular string is a valid time zone is still probably #timezone_open($timezoneId) !== false.
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.
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.
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;
}
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;
}
Look at IBM's Unicode for the working PHP programmer, especially listings 3 and 4.
On Ubuntu Lucid I get the same output from the code as IBM does, viz:
Здравсствуйте
Array
(
[1] => 65279
[2] => 1047
[3] => 1076
[4] => 1088
[5] => 1072
[6] => 1074
[7] => 1089
[8] => 1089
[9] => 1090
[10] => 1074
[11] => 1091
[12] => 1081
[13] => 1090
[14] => 1077
)
Здравсствуйте
However, on Windows I get a completely different response.
ðùð┤ÐÇð░ð▓ÐüÐüÐéð▓Ðâð╣ÐéðÁ
Array
(
[1] => -131072
[2] => 386138112
[3] => 872677376
[4] => 1074003968
[5] => 805568512
[6] => 839122944
[7] => 1090781184
[8] => 1090781184
[9] => 1107558400
[10] => 839122944
[11] => 1124335616
[12] => 956563456
[13] => 1107558400
[14] => 889454592
)
ðùð┤ÐÇð░ð▓ÐüÐüÐéð▓Ðâð╣ÐéðÁ
Aside from the fact that the Russian characters (which are in UTF-32) don't render in a CMD.EXE shell (because they're in UTF-32 not Windows' own UTF-16), why do the character values differ so significantly?
function utf8_to_unicode_code($utf8_string)
{
$expanded = iconv("UTF-8", "UTF-32", $utf8_string);
return unpack("L*", $expanded);
}
This does two things wrong:
It uses “UTF-32”, which will drop an unwanted BOM at the start of the string, which is why you get 65279 (0xFEFF BOM). You don't want stray BOMs hanging around the place causing trouble.
It uses machine-specific byte endianness (capital L) which iconv may well not agree with. To be honest I wouldn't have expected it to clash on a Windows box (as i386 is little-endian regardless of OS), but clearly it has, as the values you've got are all what would result from a reversed byte order.
Better to state both byte orderings explicitly, and avoid the BOM. Use UCS-4LE as the encoding, and unpack with V*. The same goes for unicode_code_to_utf8.
Also ignore listing 6. The ellipsis character—like the fi-ligature and others—is a ‘compatibility character’ which we wouldn't use in the modern Unicode-and-OpenType world. It's up to the font to provide contextual alternatives for fi or ... if it wants to, instead of requiring us to mangle the text.