PocketMine-MP 5.15.1 git-08c6e63aac45a450fe3b437616ee8aa2500e04e2
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 exec;
30use function file_get_contents;
31use function implode;
32use function ini_get;
33use function ini_set;
34use function is_array;
35use function is_string;
36use function json_decode;
37use function parse_ini_file;
38use function preg_match;
39use function readlink;
40use function str_contains;
41use function str_replace;
42use function str_starts_with;
43use function substr;
44use function timezone_abbreviations_list;
45use function timezone_name_from_abbr;
46use function trim;
47
48abstract class Timezone{
49
50 public static function get() : string{
51 $tz = ini_get('date.timezone');
52 if($tz === false){
53 throw new AssumptionFailedError('date.timezone INI entry should always exist');
54 }
55 return $tz;
56 }
57
58 public static function init() : void{
59 $timezone = Utils::assumeNotFalse(ini_get("date.timezone"), "date.timezone should always be set in ini");
60 if($timezone !== ""){
61 /*
62 * This is here so that people don't come to us complaining and fill up the issue tracker when they put
63 * an incorrect timezone abbreviation in php.ini apparently.
64 */
65 if(!str_contains($timezone, "/")){
66 $default_timezone = timezone_name_from_abbr($timezone);
67 if($default_timezone !== false){
68 ini_set("date.timezone", $default_timezone);
69 date_default_timezone_set($default_timezone);
70 return;
71 }
72
73 //Bad php.ini value, try another method to detect timezone
74 \GlobalLogger::get()->warning("Timezone \"$timezone\" could not be parsed as a valid timezone from php.ini, falling back to auto-detection");
75 }else{
76 date_default_timezone_set($timezone);
77 return;
78 }
79 }
80
81 if(($timezone = self::detectSystemTimezone()) !== false && date_default_timezone_set($timezone)){
82 //Success! Timezone has already been set and validated in the if statement.
83 //This here is just for redundancy just in case some program wants to read timezone data from the ini.
84 ini_set("date.timezone", $timezone);
85 return;
86 }
87
88 if(($response = Internet::getURL("http://ip-api.com/json")) !== null //If system timezone detection fails or timezone is an invalid value.
89 && is_array($ip_geolocation_data = json_decode($response->getBody(), true))
90 && isset($ip_geolocation_data['status'])
91 && $ip_geolocation_data['status'] !== 'fail'
92 && is_string($ip_geolocation_data['timezone'])
93 && date_default_timezone_set($ip_geolocation_data['timezone'])
94 ){
95 //Again, for redundancy.
96 ini_set("date.timezone", $ip_geolocation_data['timezone']);
97 return;
98 }
99
100 ini_set("date.timezone", "UTC");
101 date_default_timezone_set("UTC");
102 \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.");
103 }
104
105 public static function detectSystemTimezone() : string|false{
106 switch(Utils::getOS()){
107 case Utils::OS_WINDOWS:
108 $regex = '/(UTC)(\+*\-*\d*\d*\:*\d*\d*)/';
109
110 /*
111 * wmic timezone get Caption
112 * Get the timezone offset
113 *
114 * Sample Output var_dump
115 * array(3) {
116 * [0] =>
117 * string(7) "Caption"
118 * [1] =>
119 * string(20) "(UTC+09:30) Adelaide"
120 * [2] =>
121 * string(0) ""
122 * }
123 */
124 exec("wmic timezone get Caption", $output);
125
126 $string = trim(implode("\n", $output));
127
128 //Detect the Time Zone string
129 preg_match($regex, $string, $matches);
130
131 if(!isset($matches[2])){
132 return false;
133 }
134
135 $offset = $matches[2];
136
137 if($offset == ""){
138 return "UTC";
139 }
140
141 return self::parseOffset($offset);
142 case Utils::OS_LINUX:
143 // Ubuntu / Debian.
144 $data = @file_get_contents('/etc/timezone');
145 if($data !== false){
146 return trim($data);
147 }
148
149 // RHEL / CentOS
150 $data = @parse_ini_file('/etc/sysconfig/clock');
151 if($data !== false && isset($data['ZONE']) && is_string($data['ZONE'])){
152 return trim($data['ZONE']);
153 }
154
155 //Portable method for incompatible linux distributions.
156
157 $offset = trim(exec('date +%:z'));
158
159 if($offset == "+00:00"){
160 return "UTC";
161 }
162
163 return self::parseOffset($offset);
164 case Utils::OS_MACOS:
165 $filename = @readlink('/etc/localtime');
166 if($filename !== false && str_starts_with($filename, '/usr/share/zoneinfo/')){
167 $timezone = substr($filename, 20);
168 return trim($timezone);
169 }
170
171 return false;
172 default:
173 return false;
174 }
175 }
176
180 private static function parseOffset(string $offset) : string|false{
181 //Make signed offsets unsigned for date_parse
182 if(str_starts_with($offset, '-')){
183 $negative_offset = true;
184 $offset = str_replace('-', '', $offset);
185 }else{
186 if(str_starts_with($offset, '+')){
187 $negative_offset = false;
188 $offset = str_replace('+', '', $offset);
189 }else{
190 return false;
191 }
192 }
193
194 $parsed = date_parse($offset);
195 $offset = $parsed['hour'] * 3600 + $parsed['minute'] * 60 + $parsed['second'];
196
197 //After date_parse is done, put the sign back
198 if($negative_offset == true){
199 $offset = -abs($offset);
200 }
201
202 //And then, look the offset up.
203 //timezone_name_from_abbr is not used because it returns false on some(most) offsets because it's mapping function is weird.
204 //That's been a bug in PHP since 2008!
205 foreach(timezone_abbreviations_list() as $zones){
206 foreach($zones as $timezone){
207 if($timezone['timezone_id'] !== null && $timezone['offset'] == $offset){
208 return $timezone['timezone_id'];
209 }
210 }
211 }
212
213 return false;
214 }
215}
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:275