PluginProbe
404 Solution / trunk
404 Solution vtrunk
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / redirects / RedirectScheduleTimezone.php

RedirectScheduleTimezone.php in 404 Solution trunk, at includes/redirects/RedirectScheduleTimezone.php

69 lines 2.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * Converts between the WP-site-local calendar dates shown in the redirect
9 * schedule form ("Active From" / "Active Until") and the UTC epoch stored
10 * in `start_ts`/`end_ts`.
11 *
12 * The runtime activation check (getPermalinkFromURL.sql) compares
13 * `start_ts`/`end_ts` against MySQL's `UNIX_TIMESTAMP()` -- a true,
14 * timezone-independent UTC epoch. Both the write path (parsing the admin's
15 * "2026-07-15" input) and the read path (redisplaying that epoch in the
16 * edit form) must anchor to the same true-UTC convention using the site's
17 * configured timezone (ABJ_404_Solution_SiteTimezone), not PHP's implicit
18 * default timezone (frequently UTC regardless of what the WordPress site is
19 * configured to). Using PHP's default timezone instead of the site's
20 * silently shifts the activation moment by the site's UTC offset on every
21 * non-UTC site.
22 */
23 final class ABJ_404_Solution_RedirectScheduleTimezone {
24
25 /**
26 * Parse a "Y-m-d" (or any DateTime-parseable) date plus a literal time
27 * string in the WordPress site's configured timezone, returning a true
28 * UTC epoch. Returns null on empty or unparseable input (caller treats
29 * null as "no schedule bound", matching prior strtotime()===false
30 * handling).
31 *
32 * @param string $dateRaw
33 * @param string $time "H:i:s" literal, e.g. '00:00:00' or '23:59:59'
34 * @return int|null
35 */
36 public static function toEpoch(string $dateRaw, string $time): ?int {
37 if ($dateRaw === '') {
38 return null;
39 }
40 try {
41 $dt = new DateTimeImmutable($dateRaw . ' ' . $time, ABJ_404_Solution_SiteTimezone::resolve());
42 } catch (Exception $e) {
43 // $dateRaw is non-empty here (the empty-string case returns
44 // above) but failed to parse as a date -- a real, unexpected
45 // input shape (the admin's date-picker should only ever submit
46 // "Y-m-d"), not the routine "no schedule bound" case. Silently
47 // dropping the schedule with zero trace would leave an admin
48 // wondering why their Active From/Until didn't take effect.
49 if (function_exists('abj404_logRuntimeWarning')) {
50 abj404_logRuntimeWarning('RedirectScheduleTimezone: unparseable schedule date "' . $dateRaw . '"', $e);
51 }
52 return null;
53 }
54 return $dt->getTimestamp();
55 }
56
57 /**
58 * Format a UTC epoch (as stored in start_ts/end_ts) as a "Y-m-d" date
59 * in the WordPress site's configured timezone, for redisplay in the
60 * edit form.
61 *
62 * @param int $epoch
63 * @return string
64 */
65 public static function toDateString(int $epoch): string {
66 return (new DateTimeImmutable('@' . $epoch))->setTimezone(ABJ_404_Solution_SiteTimezone::resolve())->format('Y-m-d');
67 }
68 }
69