PocketMine-MP 5.35.1 git-e32e836dad793a3a3c8ddd8927c00e112b1e576a
Loading...
Searching...
No Matches
Timezone.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
24namespace pocketmine\utils;
25
26use function abs;
27use function date_default_timezone_set;
28use function date_parse;
29use function escapeshellarg;
30use function exec;
31use function file_get_contents;
32use function floor;
33use function hexdec;
34use function ini_get;
35use function ini_set;
36use function is_array;
37use function is_string;
38use function json_decode;
39use function parse_ini_file;
40use function preg_match;
41use function readlink;
42use function sprintf;
43use function str_contains;
44use function str_replace;
45use function str_starts_with;
46use function substr;
47use function timezone_abbreviations_list;
48use function timezone_name_from_abbr;
49use function trim;
50
51abstract class Timezone{
52
53 public static function get() : string{
54 $tz = ini_get('date.timezone');
55 if($tz === false){
56 throw new AssumptionFailedError('date.timezone INI entry should always exist');
57 }
58 return $tz;
59 }
60
61 public static function init() : void{
62 $timezone = Utils::assumeNotFalse(ini_get("date.timezone"), "date.timezone should always be set in ini");
63 if($timezone !== ""){
64 /*
65 * This is here so that people don't come to us complaining and fill up the issue tracker when they put
66 * an incorrect timezone abbreviation in php.ini apparently.
67 */
68 if(!str_contains($timezone, "/")){
69 $default_timezone = timezone_name_from_abbr($timezone);
70 if($default_timezone !== false){
71 ini_set("date.timezone", $default_timezone);
72 date_default_timezone_set($default_timezone);
73 return;
74 }
75
76 //Bad php.ini value, try another method to detect timezone
77 \GlobalLogger::get()->warning("Timezone \"$timezone\" could not be parsed as a valid timezone from php.ini, falling back to auto-detection");
78 }else{
79 date_default_timezone_set($timezone);
80 return;
81 }
82 }
83
84 if(($timezone = self::detectSystemTimezone()) !== false && date_default_timezone_set($timezone)){
85 //Success! Timezone has already been set and validated in the if statement.
86 //This here is just for redundancy just in case some program wants to read timezone data from the ini.
87 ini_set("date.timezone", $timezone);
88 return;
89 }
90
91 if(($response = Internet::getURL("http://ip-api.com/json")) !== null //If system timezone detection fails or timezone is an invalid value.
92 && is_array($ip_geolocation_data = json_decode($response->getBody(), true))
93 && isset($ip_geolocation_data['status'])
94 && $ip_geolocation_data['status'] !== 'fail'
95 && is_string($ip_geolocation_data['timezone'])
96 && date_default_timezone_set($ip_geolocation_data['timezone'])
97 ){
98 //Again, for redundancy.
99 ini_set("date.timezone", $ip_geolocation_data['timezone']);
100 return;
101 }
102
103 ini_set("date.timezone", "UTC");
104 date_default_timezone_set("UTC");
105 \GlobalLogger::get()->warning("Timezone could not be automatically determined or was set to an invalid value. 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.");
106 }
107
108 public static function detectSystemTimezone() : string|false{
109 switch(Utils::getOS()){
110 case Utils::OS_WINDOWS:
111 $keyPath = 'HKLM\\SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation';
112
113 /*
114 * Get the timezone offset through the registry
115 *
116 * Sample Output var_dump
117 * array(13) {
118 * [0]=>
119 * string(0) ""
120 * [1]=>
121 * string(71) "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
122 * [2]=>
123 * string(35) " Bias REG_DWORD 0xfffffe20"
124 * [3]=>
125 * string(43) " DaylightBias REG_DWORD 0xffffffc4"
126 * [4]=>
127 * string(45) " DaylightName REG_SZ @tzres.dll,-571"
128 * [5]=>
129 * string(67) " DaylightStart REG_BINARY 00000000000000000000000000000000"
130 * [6]=>
131 * string(36) " StandardBias REG_DWORD 0x0"
132 * [7]=>
133 * string(45) " StandardName REG_SZ @tzres.dll,-572"
134 * [8]=>
135 * string(67) " StandardStart REG_BINARY 00000000000000000000000000000000"
136 * [9]=>
137 * string(52) " TimeZoneKeyName REG_SZ China Standard Time"
138 * [10]=>
139 * string(51) " DynamicDaylightTimeDisabled REG_DWORD 0x0"
140 * [11]=>
141 * string(45) " ActiveTimeBias REG_DWORD 0xfffffe20"
142 * [12]=>
143 * string(0) ""
144 * }
145 */
146 exec("reg query " . escapeshellarg($keyPath), $output);
147
148 foreach($output as $line){
149 if(preg_match('/ActiveTimeBias\s+REG_DWORD\s+0x([0-9a-fA-F]+)/', $line, $matches) > 0){
150 $offsetMinutes = Binary::signInt((int) hexdec(trim($matches[1])));
151
152 if($offsetMinutes === 0){
153 return "UTC";
154 }
155
156 $sign = $offsetMinutes <= 0 ? '+' : '-'; //windows timezone + and - are opposite
157 $absMinutes = abs($offsetMinutes);
158 $hours = floor($absMinutes / 60);
159 $minutes = $absMinutes % 60;
160
161 $offset = sprintf(
162 "%s%02d:%02d",
163 $sign,
164 $hours,
165 $minutes
166 );
167
168 return self::parseOffset($offset);
169 }
170 }
171 return false;
172 case Utils::OS_LINUX:
173 // Ubuntu / Debian.
174 $data = @file_get_contents('/etc/timezone');
175 if($data !== false){
176 return trim($data);
177 }
178
179 // RHEL / CentOS
180 $data = @parse_ini_file('/etc/sysconfig/clock');
181 if($data !== false && isset($data['ZONE']) && is_string($data['ZONE'])){
182 return trim($data['ZONE']);
183 }
184
185 //Portable method for incompatible linux distributions.
186
187 $offset = trim(exec('date +%:z'));
188
189 if($offset === "+00:00"){
190 return "UTC";
191 }
192
193 return self::parseOffset($offset);
194 case Utils::OS_MACOS:
195 $filename = @readlink('/etc/localtime');
196 if($filename !== false && str_starts_with($filename, '/usr/share/zoneinfo/')){
197 $timezone = substr($filename, 20);
198 return trim($timezone);
199 }
200
201 return false;
202 default:
203 return false;
204 }
205 }
206
210 private static function parseOffset(string $offset) : string|false{
211 //Make signed offsets unsigned for date_parse
212 if(str_starts_with($offset, '-')){
213 $negative_offset = true;
214 $offset = str_replace('-', '', $offset);
215 }else{
216 if(str_starts_with($offset, '+')){
217 $negative_offset = false;
218 $offset = str_replace('+', '', $offset);
219 }else{
220 return false;
221 }
222 }
223
224 $parsed = date_parse($offset);
225 $offset = $parsed['hour'] * 3600 + $parsed['minute'] * 60 + $parsed['second'];
226
227 //After date_parse is done, put the sign back
228 if($negative_offset){
229 $offset = -abs($offset);
230 }
231
232 //And then, look the offset up.
233 //timezone_name_from_abbr is not used because it returns false on some(most) offsets because it's mapping function is weird.
234 //That's been a bug in PHP since 2008!
235 foreach(timezone_abbreviations_list() as $zones){
236 foreach($zones as $timezone){
237 if($timezone['timezone_id'] !== null && $timezone['offset'] === $offset){
238 return $timezone['timezone_id'];
239 }
240 }
241 }
242
243 return false;
244 }
245}
static getURL(string $page, int $timeout=10, array $extraHeaders=[], &$err=null)
Definition Internet.php:147
static assumeNotFalse(mixed $value, \Closure|string $context="This should never be false")
Definition Utils.php:623
static getOS(bool $recalculate=false)
Definition Utils.php:259