[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Core date and time related code. 19 * 20 * @package core 21 * @copyright 2015 Totara Learning Solutions Ltd {@link http://www.totaralms.com/} 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 * @author Petr Skoda <petr.skoda@totaralms.com> 24 */ 25 26 /** 27 * Core date and time related code. 28 * 29 * @since Moodle 2.9 30 * @package core 31 * @copyright 2015 Totara Learning Solutions Ltd {@link http://www.totaralms.com/} 32 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 * @author Petr Skoda <petr.skoda@totaralms.com> 34 */ 35 class core_date { 36 /** @var array list of recommended zones */ 37 protected static $goodzones = null; 38 39 /** @var array list of BC zones supported by PHP */ 40 protected static $bczones = null; 41 42 /** @var array mapping of timezones not supported by PHP */ 43 protected static $badzones = null; 44 45 /** @var string the default PHP timezone right after config.php */ 46 protected static $defaultphptimezone = null; 47 48 /** 49 * Returns a localised list of timezones. 50 * @param string $currentvalue 51 * @param bool $include99 should the server timezone info be included? 52 * @return array 53 */ 54 public static function get_list_of_timezones($currentvalue = null, $include99 = false) { 55 self::init_zones(); 56 57 // Localise first. 58 $timezones = array(); 59 foreach (self::$goodzones as $tzkey => $ignored) { 60 $timezones[$tzkey] = self::get_localised_timezone($tzkey); 61 } 62 core_collator::asort($timezones); 63 64 // Add '99' if requested. 65 if ($include99 or $currentvalue == 99) { 66 $timezones['99'] = self::get_localised_timezone('99'); 67 } 68 69 if (!isset($currentvalue) or isset($timezones[$currentvalue])) { 70 return $timezones; 71 } 72 73 if (is_numeric($currentvalue)) { 74 // UTC offset. 75 if ($currentvalue == 0) { 76 $a = 'UTC'; 77 } else { 78 $modifier = ($currentvalue > 0) ? '+' : ''; 79 $a = 'UTC' . $modifier . number_format($currentvalue, 1); 80 } 81 $timezones[$currentvalue] = get_string('timezoneinvalid', 'core_admin', $a); 82 } else { 83 // Some string we don't recognise. 84 $timezones[$currentvalue] = get_string('timezoneinvalid', 'core_admin', $currentvalue); 85 } 86 87 return $timezones; 88 } 89 90 /** 91 * Returns localised timezone name. 92 * @param string $tz 93 * @return string 94 */ 95 public static function get_localised_timezone($tz) { 96 if ($tz == 99) { 97 $tz = self::get_server_timezone(); 98 $tz = self::get_localised_timezone($tz); 99 return get_string('timezoneserver', 'core_admin', $tz); 100 } 101 102 if (get_string_manager()->string_exists(strtolower($tz), 'core_timezones')) { 103 $tz = get_string(strtolower($tz), 'core_timezones'); 104 } else if ($tz === 'GMT' or $tz === 'Etc/GMT' or $tz === 'Etc/UTC') { 105 $tz = 'UTC'; 106 } else if (preg_match('|^Etc/GMT([+-])([0-9]+)$|', $tz, $matches)) { 107 $sign = $matches[1] === '+' ? '-' : '+'; 108 $tz = 'UTC' . $sign . $matches[2]; 109 } 110 111 return $tz; 112 } 113 114 /** 115 * Normalise the timezone name. If timezone not supported 116 * this method falls back to server timezone (if valid) 117 * or default PHP timezone. 118 * 119 * @param int|string|float|DateTimeZone $tz 120 * @return string timezone compatible with PHP 121 */ 122 public static function normalise_timezone($tz) { 123 global $CFG; 124 125 if ($tz instanceof DateTimeZone) { 126 return $tz->getName(); 127 } 128 129 self::init_zones(); 130 $tz = (string)$tz; 131 132 if (isset(self::$goodzones[$tz]) or isset(self::$bczones[$tz])) { 133 return $tz; 134 } 135 136 $fixed = false; 137 if (isset(self::$badzones[$tz])) { 138 // Convert to known zone. 139 $tz = self::$badzones[$tz]; 140 $fixed = true; 141 } else if (is_numeric($tz)) { 142 // Half hour numeric offsets were already tested, try rounding to integers here. 143 $roundedtz = (string)(int)$tz; 144 if (isset(self::$badzones[$roundedtz])) { 145 $tz = self::$badzones[$roundedtz]; 146 $fixed = true; 147 } 148 } 149 150 if ($fixed and isset(self::$goodzones[$tz]) or isset(self::$bczones[$tz])) { 151 return $tz; 152 } 153 154 // Is server timezone usable? 155 if (isset($CFG->timezone) and !is_numeric($CFG->timezone)) { 156 $result = @timezone_open($CFG->timezone); // Hide notices if invalid. 157 if ($result !== false) { 158 return $result->getName(); 159 } 160 } 161 162 // Bad luck, use the php.ini default or value set in config.php. 163 return self::get_default_php_timezone(); 164 } 165 166 /** 167 * Returns server timezone. 168 * @return string normalised timezone name compatible with PHP 169 **/ 170 public static function get_server_timezone() { 171 global $CFG; 172 173 if (!isset($CFG->timezone) or $CFG->timezone == 99 or $CFG->timezone === '') { 174 return self::get_default_php_timezone(); 175 } 176 177 return self::normalise_timezone($CFG->timezone); 178 } 179 180 /** 181 * Returns server timezone. 182 * @return DateTimeZone 183 **/ 184 public static function get_server_timezone_object() { 185 $tz = self::get_server_timezone(); 186 return new DateTimeZone($tz); 187 } 188 189 /** 190 * Set PHP default timezone to $CFG->timezone. 191 */ 192 public static function set_default_server_timezone() { 193 global $CFG; 194 195 if (!isset($CFG->timezone) or $CFG->timezone == 99 or $CFG->timezone === '') { 196 date_default_timezone_set(self::get_default_php_timezone()); 197 return; 198 } 199 200 $current = date_default_timezone_get(); 201 if ($current === $CFG->timezone) { 202 // Nothing to do. 203 return; 204 } 205 206 if (!isset(self::$goodzones)) { 207 // For better performance try do do this without full tz init, 208 // because this is called from lib/setup.php file on each page. 209 $result = @timezone_open($CFG->timezone); // Ignore error if setting invalid. 210 if ($result !== false) { 211 date_default_timezone_set($result->getName()); 212 return; 213 } 214 } 215 216 // Slow way is the last option. 217 date_default_timezone_set(self::get_server_timezone()); 218 } 219 220 /** 221 * Returns user timezone. 222 * 223 * Ideally the parameter should be a real user record, 224 * unfortunately the legacy code is using 99 for both server 225 * and default value. 226 * 227 * Example of using legacy API: 228 * // Date for other user via legacy API. 229 * $datestr = userdate($time, core_date::get_user_timezone($user)); 230 * 231 * The coding style rules in Moodle are moronic, 232 * why cannot the parameter names have underscores in them? 233 * 234 * @param mixed $userorforcedtz user object or legacy forced timezone string or tz object 235 * @return string normalised timezone name compatible with PHP 236 */ 237 public static function get_user_timezone($userorforcedtz = null) { 238 global $USER, $CFG; 239 240 if ($userorforcedtz instanceof DateTimeZone) { 241 return $userorforcedtz->getName(); 242 } 243 244 if (isset($userorforcedtz) and !is_object($userorforcedtz) and $userorforcedtz != 99) { 245 // Legacy code is forcing timezone in legacy API. 246 return self::normalise_timezone($userorforcedtz); 247 } 248 249 if (isset($CFG->forcetimezone) and $CFG->forcetimezone != 99) { 250 // Override any user timezone. 251 return self::normalise_timezone($CFG->forcetimezone); 252 } 253 254 if ($userorforcedtz === null) { 255 $tz = isset($USER->timezone) ? $USER->timezone : 99; 256 257 } else if (is_object($userorforcedtz)) { 258 $tz = isset($userorforcedtz->timezone) ? $userorforcedtz->timezone : 99; 259 260 } else { 261 if ($userorforcedtz == 99) { 262 $tz = isset($USER->timezone) ? $USER->timezone : 99; 263 } else { 264 $tz = $userorforcedtz; 265 } 266 } 267 268 if ($tz == 99) { 269 return self::get_server_timezone(); 270 } 271 272 return self::normalise_timezone($tz); 273 } 274 275 /** 276 * Return user timezone object. 277 * 278 * @param mixed $userorforcedtz 279 * @return DateTimeZone 280 */ 281 public static function get_user_timezone_object($userorforcedtz = null) { 282 $tz = self::get_user_timezone($userorforcedtz); 283 return new DateTimeZone($tz); 284 } 285 286 /** 287 * Return default timezone set in php.ini or config.php. 288 * @return string normalised timezone compatible with PHP 289 */ 290 public static function get_default_php_timezone() { 291 if (!isset(self::$defaultphptimezone)) { 292 // This should not happen. 293 self::store_default_php_timezone(); 294 } 295 296 return self::$defaultphptimezone; 297 } 298 299 /** 300 * To be called from lib/setup.php only! 301 */ 302 public static function store_default_php_timezone() { 303 if ((defined('PHPUNIT_TEST') and PHPUNIT_TEST) 304 or defined('BEHAT_SITE_RUNNING') or defined('BEHAT_TEST') or defined('BEHAT_UTIL')) { 305 // We want all test sites to be consistent by default. 306 self::$defaultphptimezone = 'Australia/Perth'; 307 return; 308 } 309 if (!isset(self::$defaultphptimezone)) { 310 self::$defaultphptimezone = date_default_timezone_get(); 311 } 312 } 313 314 /** 315 * Do not use directly - use $this->setTimezone('xx', $tz) instead in your test case. 316 * @param string $tz valid timezone name 317 */ 318 public static function phpunit_override_default_php_timezone($tz) { 319 if (!defined('PHPUNIT_TEST')) { 320 throw new coding_exception('core_date::phpunit_override_default_php_timezone() must be used only from unit tests'); 321 } 322 $result = timezone_open($tz); // This triggers error if $tz invalid. 323 if ($result !== false) { 324 self::$defaultphptimezone = $tz; 325 } else { 326 self::$defaultphptimezone = 'Australia/Perth'; 327 } 328 } 329 330 /** 331 * To be called from phpunit reset only, after restoring $CFG. 332 */ 333 public static function phpunit_reset() { 334 global $CFG; 335 if (!defined('PHPUNIT_TEST')) { 336 throw new coding_exception('core_date::phpunit_reset() must be used only from unit tests'); 337 } 338 self::store_default_php_timezone(); 339 date_default_timezone_set($CFG->timezone); 340 } 341 342 /** 343 * Initialise timezone arrays, call before use. 344 */ 345 protected static function init_zones() { 346 if (isset(self::$goodzones)) { 347 return; 348 } 349 350 $zones = DateTimeZone::listIdentifiers(); 351 self::$goodzones = array_fill_keys($zones, true); 352 353 $zones = DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC); 354 self::$bczones = array(); 355 foreach ($zones as $zone) { 356 if (isset(self::$goodzones[$zone])) { 357 continue; 358 } 359 self::$bczones[$zone] = true; 360 } 361 362 self::$badzones = array( 363 // Windows time zones. 364 'Dateline Standard Time' => 'Etc/GMT+12', 365 'Hawaiian Standard Time' => 'Pacific/Honolulu', 366 'Alaskan Standard Time' => 'America/Anchorage', 367 'Pacific Standard Time (Mexico)' => 'America/Santa_Isabel', 368 'Pacific Standard Time' => 'America/Los_Angeles', 369 'US Mountain Standard Time' => 'America/Phoenix', 370 'Mountain Standard Time (Mexico)' => 'America/Chihuahua', 371 'Mountain Standard Time' => 'America/Denver', 372 'Central America Standard Time' => 'America/Guatemala', 373 'Central Standard Time' => 'America/Chicago', 374 'Central Standard Time (Mexico)' => 'America/Mexico_City', 375 'Canada Central Standard Time' => 'America/Regina', 376 'SA Pacific Standard Time' => 'America/Bogota', 377 'Eastern Standard Time' => 'America/New_York', 378 'US Eastern Standard Time' => 'America/Indianapolis', 379 'Venezuela Standard Time' => 'America/Caracas', 380 'Paraguay Standard Time' => 'America/Asuncion', 381 'Atlantic Standard Time' => 'America/Halifax', 382 'Central Brazilian Standard Time' => 'America/Cuiaba', 383 'SA Western Standard Time' => 'America/La_Paz', 384 'Pacific SA Standard Time' => 'America/Santiago', 385 'Newfoundland Standard Time' => 'America/St_Johns', 386 'E. South America Standard Time' => 'America/Sao_Paulo', 387 'Argentina Standard Time' => 'America/Buenos_Aires', 388 'SA Eastern Standard Time' => 'America/Cayenne', 389 'Greenland Standard Time' => 'America/Godthab', 390 'Montevideo Standard Time' => 'America/Montevideo', 391 'Bahia Standard Time' => 'America/Bahia', 392 'Azores Standard Time' => 'Atlantic/Azores', 393 'Cape Verde Standard Time' => 'Atlantic/Cape_Verde', 394 'Morocco Standard Time' => 'Africa/Casablanca', 395 'GMT Standard Time' => 'Europe/London', 396 'Greenwich Standard Time' => 'Atlantic/Reykjavik', 397 'W. Europe Standard Time' => 'Europe/Berlin', 398 'Central Europe Standard Time' => 'Europe/Budapest', 399 'Romance Standard Time' => 'Europe/Paris', 400 'Central European Standard Time' => 'Europe/Warsaw', 401 'W. Central Africa Standard Time' => 'Africa/Lagos', 402 'Namibia Standard Time' => 'Africa/Windhoek', 403 'Jordan Standard Time' => 'Asia/Amman', 404 'GTB Standard Time' => 'Europe/Bucharest', 405 'Middle East Standard Time' => 'Asia/Beirut', 406 'Egypt Standard Time' => 'Africa/Cairo', 407 'Syria Standard Time' => 'Asia/Damascus', 408 'South Africa Standard Time' => 'Africa/Johannesburg', 409 'FLE Standard Time' => 'Europe/Kiev', 410 'Turkey Standard Time' => 'Europe/Istanbul', 411 'Israel Standard Time' => 'Asia/Jerusalem', 412 'Kaliningrad Standard Time' => 'Europe/Kaliningrad', 413 'Libya Standard Time' => 'Africa/Tripoli', 414 'Arabic Standard Time' => 'Asia/Baghdad', 415 'Arab Standard Time' => 'Asia/Riyadh', 416 'Belarus Standard Time' => 'Europe/Minsk', 417 'Russian Standard Time' => 'Europe/Moscow', 418 'E. Africa Standard Time' => 'Africa/Nairobi', 419 'Iran Standard Time' => 'Asia/Tehran', 420 'Arabian Standard Time' => 'Asia/Dubai', 421 'Azerbaijan Standard Time' => 'Asia/Baku', 422 'Russia Time Zone 3' => 'Europe/Samara', 423 'Mauritius Standard Time' => 'Indian/Mauritius', 424 'Georgian Standard Time' => 'Asia/Tbilisi', 425 'Caucasus Standard Time' => 'Asia/Yerevan', 426 'Afghanistan Standard Time' => 'Asia/Kabul', 427 'West Asia Standard Time' => 'Asia/Tashkent', 428 'Ekaterinburg Standard Time' => 'Asia/Yekaterinburg', 429 'Pakistan Standard Time' => 'Asia/Karachi', 430 'India Standard Time' => 'Asia/Kolkata', // PHP and Windows differ in spelling. 431 'Sri Lanka Standard Time' => 'Asia/Colombo', 432 'Nepal Standard Time' => 'Asia/Katmandu', 433 'Central Asia Standard Time' => 'Asia/Almaty', 434 'Bangladesh Standard Time' => 'Asia/Dhaka', 435 'N. Central Asia Standard Time' => 'Asia/Novosibirsk', 436 'Myanmar Standard Time' => 'Asia/Rangoon', 437 'SE Asia Standard Time' => 'Asia/Bangkok', 438 'North Asia Standard Time' => 'Asia/Krasnoyarsk', 439 'China Standard Time' => 'Asia/Shanghai', 440 'North Asia East Standard Time' => 'Asia/Irkutsk', 441 'Singapore Standard Time' => 'Asia/Singapore', 442 'W. Australia Standard Time' => 'Australia/Perth', 443 'Taipei Standard Time' => 'Asia/Taipei', 444 'Ulaanbaatar Standard Time' => 'Asia/Ulaanbaatar', 445 'Tokyo Standard Time' => 'Asia/Tokyo', 446 'Korea Standard Time' => 'Asia/Seoul', 447 'Yakutsk Standard Time' => 'Asia/Yakutsk', 448 'Cen. Australia Standard Time' => 'Australia/Adelaide', 449 'AUS Central Standard Time' => 'Australia/Darwin', 450 'E. Australia Standard Time' => 'Australia/Brisbane', 451 'AUS Eastern Standard Time' => 'Australia/Sydney', 452 'West Pacific Standard Time' => 'Pacific/Port_Moresby', 453 'Tasmania Standard Time' => 'Australia/Hobart', 454 'Magadan Standard Time' => 'Asia/Magadan', 455 'Vladivostok Standard Time' => 'Asia/Vladivostok', 456 'Russia Time Zone 10' => 'Asia/Srednekolymsk', 457 'Central Pacific Standard Time' => 'Pacific/Guadalcanal', 458 'Russia Time Zone 11' => 'Asia/Kamchatka', 459 'New Zealand Standard Time' => 'Pacific/Auckland', 460 'Fiji Standard Time' => 'Pacific/Fiji', 461 'Tonga Standard Time' => 'Pacific/Tongatapu', 462 'Samoa Standard Time' => 'Pacific/Apia', 463 'Line Islands Standard Time' => 'Pacific/Kiritimati', 464 465 // A lot more bad legacy time zones. 466 'CET' => 'Europe/Berlin', 467 'Central European Time' => 'Europe/Berlin', 468 'CST' => 'America/Chicago', 469 'Central Time' => 'America/Chicago', 470 'CST6CDT' => 'America/Chicago', 471 'CDT' => 'America/Chicago', 472 'China Time' => 'Asia/Shanghai', 473 'EDT' => 'America/New_York', 474 'EST' => 'America/New_York', 475 'EST5EDT' => 'America/New_York', 476 'Eastern Time' => 'America/New_York', 477 'IST' => 'Asia/Kolkata', 478 'India Time' => 'Asia/Kolkata', 479 'JST' => 'Asia/Tokyo', 480 'Japan Time' => 'Asia/Tokyo', 481 'Japan Standard Time' => 'Asia/Tokyo', 482 'MDT' => 'America/Denver', 483 'MST' => 'America/Denver', 484 'MST7MDT' => 'America/Denver', 485 'PDT' => 'America/Los_Angeles', 486 'PST' => 'America/Los_Angeles', 487 'Pacific Time' => 'America/Los_Angeles', 488 'PST8PDT' => 'America/Los_Angeles', 489 'HST' => 'Pacific/Honolulu', 490 'WET' => 'Europe/London', 491 'EET' => 'Europe/Kiev', 492 'FET' => 'Europe/Minsk', 493 494 // Some UTC variations. 495 'UTC-01' => 'Etc/GMT+1', 496 'UTC-02' => 'Etc/GMT+2', 497 'UTC-03' => 'Etc/GMT+3', 498 'UTC-04' => 'Etc/GMT+4', 499 'UTC-05' => 'Etc/GMT+5', 500 'UTC-06' => 'Etc/GMT+6', 501 'UTC-07' => 'Etc/GMT+7', 502 'UTC-08' => 'Etc/GMT+8', 503 'UTC-09' => 'Etc/GMT+9', 504 505 // Some weird GMTs. 506 'Etc/GMT+0' => 'Etc/GMT', 507 'Etc/GMT-0' => 'Etc/GMT', 508 'Etc/GMT0' => 'Etc/GMT', 509 510 // And lastly some alternative city spelling. 511 'Asia/Calcutta' => 'Asia/Kolkata', 512 ); 513 514 // Legacy GMT fallback. 515 for ($i = -12; $i <= 14; $i++) { 516 $off = abs($i); 517 if ($i < 0) { 518 $mapto = 'Etc/GMT+' . $off; 519 $utc = 'UTC-' . $off; 520 $gmt = 'GMT-' . $off; 521 } else if ($i > 0) { 522 $mapto = 'Etc/GMT-' . $off; 523 $utc = 'UTC+' . $off; 524 $gmt = 'GMT+' . $off; 525 } else { 526 $mapto = 'Etc/GMT'; 527 $utc = 'UTC'; 528 $gmt = 'GMT'; 529 } 530 if (isset(self::$bczones[$mapto])) { 531 self::$badzones[$i . ''] = $mapto; 532 self::$badzones[$i . '.0'] = $mapto; 533 self::$badzones[$utc] = $mapto; 534 self::$badzones[$gmt] = $mapto; 535 } 536 } 537 538 // Legacy Moodle half an hour offsets - pick any city nearby, ideally without DST. 539 self::$badzones['4.5'] = 'Asia/Kabul'; 540 self::$badzones['5.5'] = 'Asia/Kolkata'; 541 self::$badzones['6.5'] = 'Asia/Rangoon'; 542 self::$badzones['9.5'] = 'Australia/Darwin'; 543 544 // Remove bad zones that are elsewhere. 545 foreach (self::$bczones as $zone => $unused) { 546 if (isset(self::$badzones[$zone])) { 547 unset(self::$badzones[$zone]); 548 } 549 } 550 foreach (self::$goodzones as $zone => $unused) { 551 if (isset(self::$badzones[$zone])) { 552 unset(self::$badzones[$zone]); 553 } 554 } 555 } 556 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |