From 42c7322273ac6db17d900afd8675561f24440e05 Mon Sep 17 00:00:00 2001 From: Michael Yoo Date: Sun, 5 Oct 2014 00:24:14 +0930 Subject: [PATCH 1/4] Reliable timezone detection using systeminfo for Windows and additional IP Geolocation-based detection. Added a warning message for when auto-detection fails. Fixes #2015. --- src/pocketmine/PocketMine.php | 129 ++++++++++++++++++++++++++-------- 1 file changed, 99 insertions(+), 30 deletions(-) diff --git a/src/pocketmine/PocketMine.php b/src/pocketmine/PocketMine.php index 4079bc41e..8ce8df20e 100644 --- a/src/pocketmine/PocketMine.php +++ b/src/pocketmine/PocketMine.php @@ -111,36 +111,6 @@ namespace pocketmine { set_time_limit(0); //Who set it to 30 seconds?!?! - if(ini_get("date.timezone") == ""){ //No Timezone set - date_default_timezone_set("GMT"); - if(strpos(" " . strtoupper(php_uname("s")), " WIN") !== false){ - $time = time(); - $time -= $time % 60; - //TODO: Parse different time & date formats by region. ¬¬ world - //Example: USA - @exec("time.exe /T", $hour); - $i = array_map("intval", explode(":", trim($hour[0]))); - @exec("date.exe /T", $date); - $j = array_map("intval", explode(substr($date[0], 2, 1), trim($date[0]))); - $offset = @round((mktime($i[0], $i[1], 0, $j[1], $j[0], $j[2]) - $time) / 60) * 60; - }else{ - @exec("date +%s", $t); - $offset = @round((intval(trim($t[0])) - time()) / 60) * 60; - } - - $daylight = (int) date("I"); - $d = timezone_name_from_abbr("", $offset, $daylight); - @ini_set("date.timezone", $d); - @date_default_timezone_set($d); - }else{ - $d = @date_default_timezone_get(); - if(strpos($d, "/") === false){ - $d = timezone_name_from_abbr($d); - @ini_set("date.timezone", $d); - @date_default_timezone_set($d); - } - } - gc_enable(); error_reporting(-1); ini_set("allow_url_fopen", 1); @@ -158,8 +128,107 @@ namespace pocketmine { define("pocketmine\\ANSI", ((strpos(strtoupper(php_uname("s")), "WIN") === false or isset($opts["enable-ansi"])) and !isset($opts["disable-ansi"]))); + //Logger has a dependency on timezone, so we'll set it to UTC until we can get the actual timezone. + date_default_timezone_set("UTC"); $logger = new MainLogger(\pocketmine\DATA . "server.log", \pocketmine\ANSI); + if(!ini_get("date.timezone")){ + if($timezone = detect_system_timezone() and date_default_timezone_set($timezone)){ + //Success! Timezone has already been set and validated in the if statement. + //This here is just for redundancy just in case some stupid program wants to read timezone data from the ini. + ini_set("date.timezone", $timezone); + }else{ + //If system timezone detection fails or timezone is an invalid value. + if($response = Utils::getURL("http://ip-api.com/json") + and $ip_geolocation_data = json_decode($response, true) + and $ip_geolocation_data['status'] != 'fail' + and date_default_timezone_set($ip_geolocation_data['timezone'])) + { + //Again, for redundancy. + ini_set("date.timezone", $ip_geolocation_data['timezone']); + }else{ + ini_set("date.timezone", "UTC"); + date_default_timezone_set("UTC"); + $logger->warning("Timezone could not be automatically determined. An incorrect timezone will result in incorrect timestamps on console logs. It has been set to \"UTC\" by default. You can change it on the php.ini file."); + } + } + }else{ + /* + * This is here so that stupid idiots don't come to us complaining and fill up the issue tracker when they put an incorrect timezone abbreviation in php.ini apparently. + */ + $default_timezone = date_default_timezone_get(); + if(strpos($default_timezone, "/") === false){ + $default_timezone = timezone_name_from_abbr($default_timezone); + ini_set("date.timezone", $default_timezone); + date_default_timezone_set($default_timezone); + } + } + + function detect_system_timezone() + { + switch(Utils::getOS()){ + case 'win': + $regex = '/(?:Time Zone:\s*\()(UTC)(\+*\-*\d*\d*\:*\d*\d*)(?:\))/'; + + exec("systeminfo", $output); + + $string = implode("\n", $output); + + //Detect the Time Zone string in systeminfo + preg_match($regex, $string, $matches); + + $offset = $matches[2]; + + if($offset == ""){ + return "UTC"; + }else{ + //Make signed offsets unsigned for date_parse + if(strpos($offset, '-') !== false){ + $negative_offset = true; + $offset = str_replace('-', '', $offset); + }else if(strpos($offset, '+') !== false){ + $negative_offset = false; + $offset = str_replace('+', '', $offset); + }else{ + return false; + } + + $parsed = date_parse($offset); + $offset = $parsed['hour'] * 3600 + $parsed['minute'] * 60 + $parsed['second']; + + //After date_parse is done, put the sign back + if($negative_offset == true){ + $offset = -abs($offset); + } + + //And then, look the offset up. + //timezone_name_from_abbr is not used because it returns false on some(most) offsets because it's mapping function is weird. + //That's been a bug in PHP since 2008! + foreach(timezone_abbreviations_list() as $zones){ + foreach($zones as $timezone){ + if($timezone['offset'] == $offset){ + return $timezone['timezone_id']; + } + } + } + } + break; + case 'linux': + exec("date +%s", $buffer); + $offset = round((intval(trim($buffer[0])) - time()) / 60) * 60; + + $daylight = (integer) date("I"); + $timezone = timezone_name_from_abbr("", $offset, $daylight); + + return $timezone; + break; + default: + return false; + } + + return false; + } + if(isset($opts["enable-profiler"])){ if(function_exists("profiler_enable")){ \profiler_enable(); From 769f1effb09210a8e3c4a9ae83a1dd9b9c9724d4 Mon Sep 17 00:00:00 2001 From: Michael Yoo Date: Sun, 5 Oct 2014 01:05:00 +0930 Subject: [PATCH 2/4] Cleaner timezone parsing for Linux and add support for Macs --- src/pocketmine/PocketMine.php | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/pocketmine/PocketMine.php b/src/pocketmine/PocketMine.php index 8ce8df20e..e897a5c9f 100644 --- a/src/pocketmine/PocketMine.php +++ b/src/pocketmine/PocketMine.php @@ -214,16 +214,38 @@ namespace pocketmine { } break; case 'linux': - exec("date +%s", $buffer); - $offset = round((intval(trim($buffer[0])) - time()) / 60) * 60; + // Ubuntu / Debian. + if(file_exists('/etc/timezone')){ + $data = file_get_contents('/etc/timezone'); + if($data){ + return $data; + } + } - $daylight = (integer) date("I"); - $timezone = timezone_name_from_abbr("", $offset, $daylight); + // RHEL / CentOS + if(file_exists('/etc/sysconfig/clock')){ + $data = parse_ini_file('/etc/sysconfig/clock'); + if(!empty($data['ZONE'])){ + return $data['ZONE']; + } + } - return $timezone; + return false; + break; + case 'mac': + if(is_link('/etc/localtime')){ + $filename = readlink('/etc/localtime'); + if (strpos($filename, '/usr/share/zoneinfo/') === 0){ + $timezone = substr($filename, 20); + return $timezone; + } + } + + return false; break; default: return false; + break; } return false; From eeda22d0badbcd187b02999138becae0e771fd2a Mon Sep 17 00:00:00 2001 From: Michael Yoo Date: Sun, 5 Oct 2014 01:29:07 +0930 Subject: [PATCH 3/4] Add portable linux timezone detection for incompatible linux distributions. --- src/pocketmine/PocketMine.php | 38 +++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/pocketmine/PocketMine.php b/src/pocketmine/PocketMine.php index e897a5c9f..3a6590469 100644 --- a/src/pocketmine/PocketMine.php +++ b/src/pocketmine/PocketMine.php @@ -230,6 +230,44 @@ namespace pocketmine { } } + //Portable method for incompatible linux distributions. + + $offset = exec('date +%:z'); + + if($offset == "+00:00"){ + return "UTC"; + }else{ + //Make signed offsets unsigned for date_parse + if(strpos($offset, '-') !== false){ + $negative_offset = true; + $offset = str_replace('-', '', $offset); + }else if(strpos($offset, '+') !== false){ + $negative_offset = false; + $offset = str_replace('+', '', $offset); + }else{ + return false; + } + + $parsed = date_parse($offset); + $offset = $parsed['hour'] * 3600 + $parsed['minute'] * 60 + $parsed['second']; + + //After date_parse is done, put the sign back + if($negative_offset == true){ + $offset = -abs($offset); + } + + //And then, look the offset up. + //timezone_name_from_abbr is not used because it returns false on some(most) offsets because it's mapping function is weird. + //That's been a bug in PHP since 2008! + foreach(timezone_abbreviations_list() as $zones){ + foreach($zones as $timezone){ + if($timezone['offset'] == $offset){ + return $timezone['timezone_id']; + } + } + } + } + return false; break; case 'mac': From e0a7944faaa3811ab62974379fe8a1d067f2d5e5 Mon Sep 17 00:00:00 2001 From: Michael Yoo Date: Sun, 5 Oct 2014 01:43:33 +0930 Subject: [PATCH 4/4] Re-use timezone offset parsing --- src/pocketmine/PocketMine.php | 107 ++++++++++++++-------------------- 1 file changed, 43 insertions(+), 64 deletions(-) diff --git a/src/pocketmine/PocketMine.php b/src/pocketmine/PocketMine.php index 3a6590469..905752eb0 100644 --- a/src/pocketmine/PocketMine.php +++ b/src/pocketmine/PocketMine.php @@ -164,8 +164,7 @@ namespace pocketmine { } } - function detect_system_timezone() - { + function detect_system_timezone(){ switch(Utils::getOS()){ case 'win': $regex = '/(?:Time Zone:\s*\()(UTC)(\+*\-*\d*\d*\:*\d*\d*)(?:\))/'; @@ -181,37 +180,9 @@ namespace pocketmine { if($offset == ""){ return "UTC"; - }else{ - //Make signed offsets unsigned for date_parse - if(strpos($offset, '-') !== false){ - $negative_offset = true; - $offset = str_replace('-', '', $offset); - }else if(strpos($offset, '+') !== false){ - $negative_offset = false; - $offset = str_replace('+', '', $offset); - }else{ - return false; - } - - $parsed = date_parse($offset); - $offset = $parsed['hour'] * 3600 + $parsed['minute'] * 60 + $parsed['second']; - - //After date_parse is done, put the sign back - if($negative_offset == true){ - $offset = -abs($offset); - } - - //And then, look the offset up. - //timezone_name_from_abbr is not used because it returns false on some(most) offsets because it's mapping function is weird. - //That's been a bug in PHP since 2008! - foreach(timezone_abbreviations_list() as $zones){ - foreach($zones as $timezone){ - if($timezone['offset'] == $offset){ - return $timezone['timezone_id']; - } - } - } } + + return parse_offset($offset); break; case 'linux': // Ubuntu / Debian. @@ -236,44 +207,14 @@ namespace pocketmine { if($offset == "+00:00"){ return "UTC"; - }else{ - //Make signed offsets unsigned for date_parse - if(strpos($offset, '-') !== false){ - $negative_offset = true; - $offset = str_replace('-', '', $offset); - }else if(strpos($offset, '+') !== false){ - $negative_offset = false; - $offset = str_replace('+', '', $offset); - }else{ - return false; - } - - $parsed = date_parse($offset); - $offset = $parsed['hour'] * 3600 + $parsed['minute'] * 60 + $parsed['second']; - - //After date_parse is done, put the sign back - if($negative_offset == true){ - $offset = -abs($offset); - } - - //And then, look the offset up. - //timezone_name_from_abbr is not used because it returns false on some(most) offsets because it's mapping function is weird. - //That's been a bug in PHP since 2008! - foreach(timezone_abbreviations_list() as $zones){ - foreach($zones as $timezone){ - if($timezone['offset'] == $offset){ - return $timezone['timezone_id']; - } - } - } } - return false; + return parse_offset($offset); break; case 'mac': if(is_link('/etc/localtime')){ $filename = readlink('/etc/localtime'); - if (strpos($filename, '/usr/share/zoneinfo/') === 0){ + if(strpos($filename, '/usr/share/zoneinfo/') === 0){ $timezone = substr($filename, 20); return $timezone; } @@ -285,6 +226,44 @@ namespace pocketmine { return false; break; } + } + + /** + * @param string $offset In the format of +09:00, +02:00, -04:00 etc. + * @return string + */ + function parse_offset($offset){ + //Make signed offsets unsigned for date_parse + if(strpos($offset, '-') !== false){ + $negative_offset = true; + $offset = str_replace('-', '', $offset); + }else{ + if(strpos($offset, '+') !== false){ + $negative_offset = false; + $offset = str_replace('+', '', $offset); + }else{ + return false; + } + } + + $parsed = date_parse($offset); + $offset = $parsed['hour'] * 3600 + $parsed['minute'] * 60 + $parsed['second']; + + //After date_parse is done, put the sign back + if($negative_offset == true){ + $offset = -abs($offset); + } + + //And then, look the offset up. + //timezone_name_from_abbr is not used because it returns false on some(most) offsets because it's mapping function is weird. + //That's been a bug in PHP since 2008! + foreach(timezone_abbreviations_list() as $zones){ + foreach($zones as $timezone){ + if($timezone['offset'] == $offset){ + return $timezone['timezone_id']; + } + } + } return false; }