| @@ -45,9 +45,9 @@ | ||
| 45 | 45 | $dt = clone $datetime; |
| 46 | 46 | |
| 47 | 47 | // If fromTz is specified and the datetime doesn't have timezone info, set it |
| 48 | 48 | if ($fromTz && $dt->getTimezone()->getName() === 'UTC' && $fromTz !== 'UTC') { |
| 49 | - $dt = $dt->setTimezone(new \DateTimeZone($fromTz)); | |
| 49 | + $dt = $dt->setTimezone(static::resolveTimezone($fromTz)); | |
| 50 | 50 | } |
| 51 | 51 | |
| 52 | 52 | return $dt->setTimezone(new \DateTimeZone('UTC')); |
| 53 | 53 | } |
| @@ -64,9 +64,9 @@ | ||
| 64 | 64 | |
| 65 | 65 | // If fromTz is provided and the parsed string doesn't contain timezone info |
| 66 | 66 | if ($fromTz && !preg_match('/[+-]\d{2}:?\d{2}$|Z$|[A-Z]{3,4}$/i', trim($datetime))) { |
| 67 | 67 | // Create a new datetime in the specified timezone |
| 68 | - $dt = static::createFromFormat('Y-m-d H:i:s', $dt->format('Y-m-d H:i:s'), new \DateTimeZone($fromTz)); | |
| 68 | + $dt = static::createFromFormat('Y-m-d H:i:s', $dt->format('Y-m-d H:i:s'), static::resolveTimezone($fromTz)); | |
| 69 | 69 | } |
| 70 | 70 | |
| 71 | 71 | return $dt->setTimezone(new \DateTimeZone('UTC')); |
| 72 | 72 | } |
| @@ -88,13 +88,10 @@ | ||
| 88 | 88 | * @return static |
| 89 | 89 | */ |
| 90 | 90 | public static function gmtToTimezone($datetime = null, $timezone = null): DateTime |
| 91 | 91 | { |
| 92 | - // If not a string or DateTimeZone, fallback to wp_timezone() | |
| 93 | 92 | if (!$timezone instanceof \DateTimeZone) { |
| 94 | - $timezone = is_string($timezone) | |
| 95 | - ? new \DateTimeZone($timezone) | |
| 96 | - : wp_timezone(); // returns a DateTimeZone object | |
| 93 | + $timezone = static::resolveTimezone($timezone); | |
| 97 | 94 | } |
| 98 | 95 | |
| 99 | 96 | |
| 100 | 97 | if (is_null($datetime)) { |
| @@ -118,8 +115,27 @@ | ||
| 118 | 115 | |
| 119 | 116 | } |
| 120 | 117 | |
| 121 | 118 | |
| 119 | + /** | |
| 120 | + * Safely resolve a timezone string to a DateTimeZone object. | |
| 121 | + * | |
| 122 | + * PHP 8.4 throws on deprecated aliases like Asia/Saigon, so we validate | |
| 123 | + * with timezone_open() before constructing. Old orders may carry such aliases | |
| 124 | + * from the browser-captured user_tz stored in order config at checkout time. | |
| 125 | + * Falls back to the supplied $fallback, or wp_timezone() if none given. | |
| 126 | + */ | |
| 127 | + private static function resolveTimezone($timezone, $fallback = null): \DateTimeZone | |
| 128 | + { | |
| 129 | + if ($timezone instanceof \DateTimeZone) { | |
| 130 | + return $timezone; | |
| 131 | + } | |
| 132 | + if (is_string($timezone) && $timezone !== '' && @timezone_open($timezone) !== false) { | |
| 133 | + return new \DateTimeZone($timezone); | |
| 134 | + } | |
| 135 | + return ($fallback instanceof \DateTimeZone) ? $fallback : wp_timezone(); | |
| 136 | + } | |
| 137 | + | |
| 122 | 138 | public function getDefaultTimezone() |
| 123 | 139 | { |
| 124 | 140 | return new DateTimeZone('UTC'); |
| 125 | 141 | } |
| @@ -135,9 +151,9 @@ | ||
| 135 | 151 | $dt = new \DateTimeImmutable($datetimeString); |
| 136 | 152 | } else { |
| 137 | 153 | $tz = $timezone instanceof \DateTimeZone |
| 138 | 154 | ? $timezone |
| 139 | - : ($timezone ? new \DateTimeZone($timezone) : new DateTimeZone('UTC')); | |
| 155 | + : ($timezone ? static::resolveTimezone($timezone, new \DateTimeZone('UTC')) : new DateTimeZone('UTC')); | |
| 140 | 156 | |
| 141 | 157 | $dt = new \DateTimeImmutable($datetimeString, $tz); |
| 142 | 158 | } |
| 143 | 159 | |