| 1 |
<?php |
| 2 |
|
| 3 |
if (!defined('ABSPATH')) { |
| 4 |
exit; |
| 5 |
} |
| 6 |
|
| 7 |
/** |
| 8 |
* Formats the current instant as a log-line timestamp in the site's configured |
| 9 |
* timezone. |
| 10 |
* |
| 11 |
* Both WordPress option sources this reads (`timezone_string` and `gmt_offset`) |
| 12 |
* can hold garbage on a corrupted site: a stray value written into the wrong |
| 13 |
* options row, an out-of-range offset, a non-IANA name. A bad value must never |
| 14 |
* fatal the logging path, so DateTimeZone construction for either source shares |
| 15 |
* one guarded fallback to the server default and emits a breadcrumb through the |
| 16 |
* raw PHP fallback sink (never through the logger itself, which would risk |
| 17 |
* recursion if the timezone failure also breaks the logger's own DateTime use). |
| 18 |
* |
| 19 |
* Pure formatting policy: no business decisions, no data store of its own. |
| 20 |
*/ |
| 21 |
class ABJ_404_Solution_LogTimestampFormatter { |
| 22 |
|
| 23 |
/** |
| 24 |
* Current time formatted for the active site timezone. |
| 25 |
* |
| 26 |
* @return string A `Y-m-d H:i:s T` timestamp. |
| 27 |
*/ |
| 28 |
public function format(): string { |
| 29 |
$timezoneStringRaw = get_option('timezone_string'); |
| 30 |
$timezoneString = is_string($timezoneStringRaw) ? $timezoneStringRaw : ''; |
| 31 |
|
| 32 |
if (!empty($timezoneString)) { |
| 33 |
// WordPress stores an IANA name (e.g. "America/New_York") here. |
| 34 |
$tzString = $timezoneString; |
| 35 |
} else { |
| 36 |
$gmtOffsetRaw = get_option('gmt_offset'); |
| 37 |
// WordPress's gmt_offset is hours and may be fractional |
| 38 |
// (e.g. 5.5 India, 5.75 Nepal, -3.5 Newfoundland). |
| 39 |
$gmtOffsetHours = is_scalar($gmtOffsetRaw) ? (float)$gmtOffsetRaw : 0.0; |
| 40 |
$totalMinutes = (int) round($gmtOffsetHours * 60); |
| 41 |
$sign = $totalMinutes < 0 ? '-' : '+'; |
| 42 |
$absMinutes = abs($totalMinutes); |
| 43 |
$tzString = sprintf('%s%02d:%02d', $sign, intdiv($absMinutes, 60), $absMinutes % 60); |
| 44 |
} |
| 45 |
|
| 46 |
// Both option sources can hold garbage on a corrupted site: a stray |
| 47 |
// value written into the wrong options row, an out-of-range offset, |
| 48 |
// etc. A bad value must never fatal the logging path, so the |
| 49 |
// DateTimeZone construction for EITHER source shares one guarded |
| 50 |
// fallback to the server default. |
| 51 |
try { |
| 52 |
$date = new DateTime('@' . abj_clock()->now()); |
| 53 |
$date->setTimezone(new DateTimeZone($tzString)); |
| 54 |
} catch (Exception $e) { |
| 55 |
// Use the raw fallback sink because this method is part of |
| 56 |
// the logging path; calling warn here would risk recursion if |
| 57 |
// timezone failure also breaks warn's own DateTime use. |
| 58 |
abj404_logPhpFallback( |
| 59 |
'logger-internal', |
| 60 |
'timezone constructor failed (' . $e->getMessage() . '); using server default' |
| 61 |
); |
| 62 |
$date = new DateTime('@' . abj_clock()->now()); |
| 63 |
$date->setTimezone(new DateTimeZone(date_default_timezone_get())); |
| 64 |
} |
| 65 |
|
| 66 |
return $date->format('Y-m-d H:i:s T'); |
| 67 |
} |
| 68 |
} |
| 69 |
|