| @@ -77,10 +77,15 @@ | ||
| 77 | 77 | $this->config->update_schedule(null, $next); |
| 78 | 78 | return; |
| 79 | 79 | } |
| 80 | 80 | |
| 81 | - $next_ts = strtotime((string) $config['next_scheduled_at']); | |
| 82 | - if ($next_ts && $next_ts > time()) { | |
| 81 | + // next_scheduled_at is a site-local wall clock (written with wp_date()). | |
| 82 | + // strtotime() would resolve it against PHP's default timezone (UTC | |
| 83 | + // under WordPress) and shift the send by the site's offset — early on | |
| 84 | + // a negative offset, late on a positive one. Interpret it in the site | |
| 85 | + // timezone, the same way next_run_iso() reports it to the UI. | |
| 86 | + $next_ts = (int) get_gmt_from_date((string) $config['next_scheduled_at'], 'U'); | |
| 87 | + if ($next_ts > 0 && $next_ts > time()) { | |
| 83 | 88 | return; |
| 84 | 89 | } |
| 85 | 90 | |
| 86 | 91 | $this->generator->generate_for_due(); |
| @@ -88,8 +93,15 @@ | ||
| 88 | 93 | |
| 89 | 94 | /** |
| 90 | 95 | * Convenience used by REST + admin UI to surface the next-send timestamp |
| 91 | 96 | * to the user. Defends against an empty or stale next_scheduled_at. |
| 97 | + * | |
| 98 | + * next_scheduled_at is stored as a site-local `Y-m-d H:i:s` string with | |
| 99 | + * no zone information. Handing that to the browser meant `new Date()` | |
| 100 | + * read it in the *viewer's* timezone, so any admin whose browser zone | |
| 101 | + * differed from the site's saw the wrong time. Emit a real ISO 8601 | |
| 102 | + * string carrying the site's UTC offset instead, which parses to one | |
| 103 | + * unambiguous instant everywhere. | |
| 92 | 104 | */ |
| 93 | 105 | public function next_run_iso(): ?string { |
| 94 | 106 | $config = $this->config->get(); |
| 95 | 107 | if (empty($config['enabled']) || empty($config['next_scheduled_at'])) { |
| @@ -94,7 +106,15 @@ | ||
| 94 | 106 | $config = $this->config->get(); |
| 95 | 107 | if (empty($config['enabled']) || empty($config['next_scheduled_at'])) { |
| 96 | 108 | return null; |
| 97 | 109 | } |
| 98 | - return (string) $config['next_scheduled_at']; | |
| 110 | + | |
| 111 | + // Interpret the stored wall clock in the site timezone, then render | |
| 112 | + // that same instant with its offset attached. | |
| 113 | + $timestamp = (int) get_gmt_from_date((string) $config['next_scheduled_at'], 'U'); | |
| 114 | + if ($timestamp <= 0) { | |
| 115 | + return null; | |
| 116 | + } | |
| 117 | + | |
| 118 | + return wp_date('c', $timestamp); | |
| 99 | 119 | } |
| 100 | 120 | } |