See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]
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 'S.A. Pacific Standard Time' => 'America/Bogota', 378 'Eastern Standard Time' => 'America/New_York', 379 'US Eastern Standard Time' => 'America/Indianapolis', 380 'U.S. Eastern Standard Time' => 'America/Indianapolis', 381 'Venezuela Standard Time' => 'America/Caracas', 382 'Paraguay Standard Time' => 'America/Asuncion', 383 'Atlantic Standard Time' => 'America/Halifax', 384 'Central Brazilian Standard Time' => 'America/Cuiaba', 385 'SA Western Standard Time' => 'America/La_Paz', 386 'S.A. Western Standard Time' => 'America/La_Paz', 387 'Pacific SA Standard Time' => 'America/Santiago', 388 'Pacific S.A. Standard Time' => 'America/Santiago', 389 'Newfoundland Standard Time' => 'America/St_Johns', 390 'Newfoundland and Labrador Standard Time' => 'America/St_Johns', 391 'E. South America Standard Time' => 'America/Sao_Paulo', 392 'Argentina Standard Time' => 'America/Buenos_Aires', 393 'SA Eastern Standard Time' => 'America/Cayenne', 394 'S.A. Eastern Standard Time' => 'America/Cayenne', 395 'Greenland Standard Time' => 'America/Godthab', 396 'Montevideo Standard Time' => 'America/Montevideo', 397 'Bahia Standard Time' => 'America/Bahia', 398 'Azores Standard Time' => 'Atlantic/Azores', 399 'Cape Verde Standard Time' => 'Atlantic/Cape_Verde', 400 'Morocco Standard Time' => 'Africa/Casablanca', 401 'GMT Standard Time' => 'Europe/London', 402 'Greenwich Standard Time' => 'Atlantic/Reykjavik', 403 'W. Europe Standard Time' => 'Europe/Berlin', 404 'Central Europe Standard Time' => 'Europe/Budapest', 405 'Romance Standard Time' => 'Europe/Paris', 406 'Central European Standard Time' => 'Europe/Warsaw', 407 'W. Central Africa Standard Time' => 'Africa/Lagos', 408 'Namibia Standard Time' => 'Africa/Windhoek', 409 'Jordan Standard Time' => 'Asia/Amman', 410 'GTB Standard Time' => 'Europe/Bucharest', 411 'Middle East Standard Time' => 'Asia/Beirut', 412 'Egypt Standard Time' => 'Africa/Cairo', 413 'Syria Standard Time' => 'Asia/Damascus', 414 'South Africa Standard Time' => 'Africa/Johannesburg', 415 'FLE Standard Time' => 'Europe/Kiev', 416 'Turkey Standard Time' => 'Europe/Istanbul', 417 'Israel Standard Time' => 'Asia/Jerusalem', 418 'Kaliningrad Standard Time' => 'Europe/Kaliningrad', 419 'Libya Standard Time' => 'Africa/Tripoli', 420 'Arabic Standard Time' => 'Asia/Baghdad', 421 'Arab Standard Time' => 'Asia/Riyadh', 422 'Belarus Standard Time' => 'Europe/Minsk', 423 'Russian Standard Time' => 'Europe/Moscow', 424 'E. Africa Standard Time' => 'Africa/Nairobi', 425 'Iran Standard Time' => 'Asia/Tehran', 426 'Arabian Standard Time' => 'Asia/Dubai', 427 'Azerbaijan Standard Time' => 'Asia/Baku', 428 'Russia Time Zone 3' => 'Europe/Samara', 429 'Mauritius Standard Time' => 'Indian/Mauritius', 430 'Georgian Standard Time' => 'Asia/Tbilisi', 431 'Caucasus Standard Time' => 'Asia/Yerevan', 432 'Afghanistan Standard Time' => 'Asia/Kabul', 433 'West Asia Standard Time' => 'Asia/Tashkent', 434 'Ekaterinburg Standard Time' => 'Asia/Yekaterinburg', 435 'Pakistan Standard Time' => 'Asia/Karachi', 436 'India Standard Time' => 'Asia/Kolkata', // PHP and Windows differ in spelling. 437 'Sri Lanka Standard Time' => 'Asia/Colombo', 438 'Nepal Standard Time' => 'Asia/Katmandu', 439 'Central Asia Standard Time' => 'Asia/Almaty', 440 'Bangladesh Standard Time' => 'Asia/Dhaka', 441 'N. Central Asia Standard Time' => 'Asia/Novosibirsk', 442 'Myanmar Standard Time' => 'Asia/Rangoon', 443 'SE Asia Standard Time' => 'Asia/Bangkok', 444 'S.E. Asia Standard Time' => 'Asia/Bangkok', 445 'North Asia Standard Time' => 'Asia/Krasnoyarsk', 446 'China Standard Time' => 'Asia/Shanghai', 447 'North Asia East Standard Time' => 'Asia/Irkutsk', 448 'Singapore Standard Time' => 'Asia/Singapore', 449 'W. Australia Standard Time' => 'Australia/Perth', 450 'Taipei Standard Time' => 'Asia/Taipei', 451 'Ulaanbaatar Standard Time' => 'Asia/Ulaanbaatar', 452 'Tokyo Standard Time' => 'Asia/Tokyo', 453 'Korea Standard Time' => 'Asia/Seoul', 454 'Yakutsk Standard Time' => 'Asia/Yakutsk', 455 'Cen. Australia Standard Time' => 'Australia/Adelaide', 456 'AUS Central Standard Time' => 'Australia/Darwin', 457 'A.U.S. Central Standard Time' => 'Australia/Darwin', 458 'E. Australia Standard Time' => 'Australia/Brisbane', 459 'AUS Eastern Standard Time' => 'Australia/Sydney', 460 'A.U.S. Eastern Standard Time' => 'Australia/Sydney', 461 'West Pacific Standard Time' => 'Pacific/Port_Moresby', 462 'Tasmania Standard Time' => 'Australia/Hobart', 463 'Magadan Standard Time' => 'Asia/Magadan', 464 'Vladivostok Standard Time' => 'Asia/Vladivostok', 465 'Russia Time Zone 10' => 'Asia/Srednekolymsk', 466 'Central Pacific Standard Time' => 'Pacific/Guadalcanal', 467 'Russia Time Zone 11' => 'Asia/Kamchatka', 468 'New Zealand Standard Time' => 'Pacific/Auckland', 469 'Fiji Standard Time' => 'Pacific/Fiji', 470 'Fiji Islands Standard Time' => 'Pacific/Fiji', 471 'Tonga Standard Time' => 'Pacific/Tongatapu', 472 'Samoa Standard Time' => 'Pacific/Apia', 473 'Line Islands Standard Time' => 'Pacific/Kiritimati', 474 'Mexico Standard Time 2' => 'America/Chihuahua', 475 'Mexico Standard Time' => 'America/Mexico_City', 476 'U.S. Mountain Standard Time' => 'America/Phoenix', 477 'Mid-Atlantic Standard Time' => 'Atlantic/South_Georgia', 478 'E. Europe Standard Time' => 'Europe/Minsk', 479 'Transitional Islamic State of Afghanistan Standard Time' => 'Asia/Kabul', 480 'Armenian Standard Time' => 'Asia/Yerevan', 481 'Kamchatka Standard Time' => 'Asia/Kamchatka', 482 483 // A lot more bad legacy time zones. 484 'CET' => 'Europe/Berlin', 485 'Central European Time' => 'Europe/Berlin', 486 'CST' => 'America/Chicago', 487 'Central Time' => 'America/Chicago', 488 'CST6CDT' => 'America/Chicago', 489 'CDT' => 'America/Chicago', 490 'China Time' => 'Asia/Shanghai', 491 'EDT' => 'America/New_York', 492 'EST' => 'America/New_York', 493 'EST5EDT' => 'America/New_York', 494 'Eastern Time' => 'America/New_York', 495 'IST' => 'Asia/Kolkata', 496 'India Time' => 'Asia/Kolkata', 497 'JST' => 'Asia/Tokyo', 498 'Japan Time' => 'Asia/Tokyo', 499 'Japan Standard Time' => 'Asia/Tokyo', 500 'MDT' => 'America/Denver', 501 'MST' => 'America/Denver', 502 'MST7MDT' => 'America/Denver', 503 'PDT' => 'America/Los_Angeles', 504 'PST' => 'America/Los_Angeles', 505 'Pacific Time' => 'America/Los_Angeles', 506 'PST8PDT' => 'America/Los_Angeles', 507 'HST' => 'Pacific/Honolulu', 508 'WET' => 'Europe/London', 509 'EET' => 'Europe/Kiev', 510 'FET' => 'Europe/Minsk', 511 512 // Some UTC variations. 513 'UTC-01' => 'Etc/GMT+1', 514 'UTC-02' => 'Etc/GMT+2', 515 'UTC-03' => 'Etc/GMT+3', 516 'UTC-04' => 'Etc/GMT+4', 517 'UTC-05' => 'Etc/GMT+5', 518 'UTC-06' => 'Etc/GMT+6', 519 'UTC-07' => 'Etc/GMT+7', 520 'UTC-08' => 'Etc/GMT+8', 521 'UTC-09' => 'Etc/GMT+9', 522 523 // Some weird GMTs. 524 'Etc/GMT+0' => 'Etc/GMT', 525 'Etc/GMT-0' => 'Etc/GMT', 526 'Etc/GMT0' => 'Etc/GMT', 527 528 // And lastly some alternative city spelling. 529 'Asia/Calcutta' => 'Asia/Kolkata', 530 ); 531 532 // Legacy GMT fallback. 533 for ($i = -12; $i <= 14; $i++) { 534 $off = abs($i); 535 if ($i < 0) { 536 $mapto = 'Etc/GMT+' . $off; 537 $utc = 'UTC-' . $off; 538 $gmt = 'GMT-' . $off; 539 } else if ($i > 0) { 540 $mapto = 'Etc/GMT-' . $off; 541 $utc = 'UTC+' . $off; 542 $gmt = 'GMT+' . $off; 543 } else { 544 $mapto = 'Etc/GMT'; 545 $utc = 'UTC'; 546 $gmt = 'GMT'; 547 } 548 if (isset(self::$bczones[$mapto])) { 549 self::$badzones[$i . ''] = $mapto; 550 self::$badzones[$i . '.0'] = $mapto; 551 self::$badzones[$utc] = $mapto; 552 self::$badzones[$gmt] = $mapto; 553 } 554 } 555 556 // Legacy Moodle half an hour offsets - pick any city nearby, ideally without DST. 557 self::$badzones['4.5'] = 'Asia/Kabul'; 558 self::$badzones['5.5'] = 'Asia/Kolkata'; 559 self::$badzones['6.5'] = 'Asia/Rangoon'; 560 self::$badzones['9.5'] = 'Australia/Darwin'; 561 562 // Remove bad zones that are elsewhere. 563 foreach (self::$bczones as $zone => $unused) { 564 if (isset(self::$badzones[$zone])) { 565 unset(self::$badzones[$zone]); 566 } 567 } 568 foreach (self::$goodzones as $zone => $unused) { 569 if (isset(self::$badzones[$zone])) { 570 unset(self::$badzones[$zone]); 571 } 572 } 573 } 574 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body