Skip to content

Commit b34a01b

Browse files
authored
Merge pull request #188 from Automattic/fix/148-scheduled-posts-publish-immediately
2 parents ce03869 + 69417db commit b34a01b

File tree

2 files changed

+175
-2
lines changed

2 files changed

+175
-2
lines changed

includes/class-syndication-wp-rest-client.php

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public function new_post( $post_ID ) {
9292
'excerpt' => $post['post_excerpt'],
9393
'status' => $post['post_status'],
9494
'password' => $post['post_password'],
95-
'date' => $post['post_date_gmt'],
95+
'date' => $this->format_date_for_api( $post['post_date_gmt'] ),
9696
'categories' => $this->_prepare_terms( wp_get_object_terms( $post_ID, 'category', array('fields' => 'names') ) ),
9797
'tags' => $this->_prepare_terms( wp_get_object_terms( $post_ID, 'post_tag', array('fields' => 'names') ) )
9898
);
@@ -139,7 +139,7 @@ public function edit_post( $post_ID, $ext_ID ) {
139139
'excerpt' => $post['post_excerpt'],
140140
'status' => $post['post_status'],
141141
'password' => $post['post_password'],
142-
'date' => $post['post_date_gmt'],
142+
'date' => $this->format_date_for_api( $post['post_date_gmt'] ),
143143
'categories' => $this->_prepare_terms( wp_get_object_terms( $post_ID, 'category', array('fields' => 'names') ) ),
144144
'tags' => $this->_prepare_terms( wp_get_object_terms( $post_ID, 'post_tag', array('fields' => 'names') ) )
145145
);
@@ -184,6 +184,26 @@ function _prepare_terms( $terms ) {
184184

185185
}
186186

187+
/**
188+
* Format a MySQL date string for the WordPress.com REST API.
189+
*
190+
* The API expects dates in ISO 8601 format. This is especially important
191+
* for scheduled posts (status 'future') to ensure the scheduled date is
192+
* preserved on the target site.
193+
*
194+
* @since 2.2.0
195+
*
196+
* @param string $mysql_date Date in MySQL format (Y-m-d H:i:s).
197+
* @return string Date in ISO 8601 format, or empty string if invalid.
198+
*/
199+
private function format_date_for_api( $mysql_date ) {
200+
if ( empty( $mysql_date ) || '0000-00-00 00:00:00' === $mysql_date ) {
201+
return '';
202+
}
203+
204+
return mysql2date( 'c', $mysql_date, false );
205+
}
206+
187207
public function delete_post( $ext_ID ) {
188208

189209
$response = wp_remote_post( 'https://public-api.wordpress.com/rest/v1/sites/' . $this->blog_ID . '/posts/' . $ext_ID . '/delete', array(
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
<?php
2+
/**
3+
* Unit tests for REST client date formatting.
4+
*
5+
* @package Syndication
6+
*/
7+
8+
declare( strict_types=1 );
9+
10+
namespace Automattic\Syndication\Tests\Unit;
11+
12+
use Brain\Monkey\Functions;
13+
14+
/**
15+
* Tests for the format_date_for_api method.
16+
*
17+
* @group unit
18+
*/
19+
class RestClientDateFormattingTest extends TestCase {
20+
21+
/**
22+
* Test double instance that replicates the date formatting logic.
23+
*
24+
* @var object
25+
*/
26+
private $client;
27+
28+
/**
29+
* Set up test fixtures.
30+
*/
31+
protected function setUp(): void {
32+
parent::setUp();
33+
34+
// Create a test double that replicates the exact method.
35+
$this->client = new class() {
36+
/**
37+
* Format a MySQL date string for the WordPress.com REST API.
38+
*
39+
* @param string $mysql_date Date in MySQL format (Y-m-d H:i:s).
40+
* @return string Date in ISO 8601 format, or empty string if invalid.
41+
*/
42+
public function format_date_for_api( $mysql_date ) {
43+
if ( empty( $mysql_date ) || '0000-00-00 00:00:00' === $mysql_date ) {
44+
return '';
45+
}
46+
47+
return mysql2date( 'c', $mysql_date, false );
48+
}
49+
};
50+
}
51+
52+
/**
53+
* Test that empty date returns empty string.
54+
*/
55+
public function test_empty_date_returns_empty_string() {
56+
Functions\expect( 'mysql2date' )->never();
57+
58+
$result = $this->client->format_date_for_api( '' );
59+
60+
$this->assertSame( '', $result );
61+
}
62+
63+
/**
64+
* Test that null date returns empty string.
65+
*/
66+
public function test_null_date_returns_empty_string() {
67+
Functions\expect( 'mysql2date' )->never();
68+
69+
$result = $this->client->format_date_for_api( null );
70+
71+
$this->assertSame( '', $result );
72+
}
73+
74+
/**
75+
* Test that zero date returns empty string.
76+
*/
77+
public function test_zero_date_returns_empty_string() {
78+
Functions\expect( 'mysql2date' )->never();
79+
80+
$result = $this->client->format_date_for_api( '0000-00-00 00:00:00' );
81+
82+
$this->assertSame( '', $result );
83+
}
84+
85+
/**
86+
* Test that valid date is converted to ISO 8601 format.
87+
*/
88+
public function test_valid_date_returns_iso_8601_format() {
89+
$mysql_date = '2024-06-15 14:30:00';
90+
$expected_date = '2024-06-15T14:30:00+00:00';
91+
92+
Functions\expect( 'mysql2date' )
93+
->once()
94+
->with( 'c', $mysql_date, false )
95+
->andReturn( $expected_date );
96+
97+
$result = $this->client->format_date_for_api( $mysql_date );
98+
99+
$this->assertSame( $expected_date, $result );
100+
}
101+
102+
/**
103+
* Test that future date (scheduled post) is formatted correctly.
104+
*/
105+
public function test_future_date_is_formatted_correctly() {
106+
$mysql_date = '2025-12-25 10:00:00';
107+
$expected_date = '2025-12-25T10:00:00+00:00';
108+
109+
Functions\expect( 'mysql2date' )
110+
->once()
111+
->with( 'c', $mysql_date, false )
112+
->andReturn( $expected_date );
113+
114+
$result = $this->client->format_date_for_api( $mysql_date );
115+
116+
$this->assertSame( $expected_date, $result );
117+
}
118+
119+
/**
120+
* Test that mysql2date is called with correct parameters.
121+
*/
122+
public function test_mysql2date_called_with_iso_format_code() {
123+
$mysql_date = '2024-01-01 00:00:00';
124+
125+
Functions\expect( 'mysql2date' )
126+
->once()
127+
->with(
128+
'c', // ISO 8601 format code
129+
$mysql_date,
130+
false // Don't translate
131+
)
132+
->andReturn( '2024-01-01T00:00:00+00:00' );
133+
134+
$this->client->format_date_for_api( $mysql_date );
135+
}
136+
137+
/**
138+
* Test that date with timezone offset is handled.
139+
*/
140+
public function test_date_with_timezone_offset() {
141+
$mysql_date = '2024-03-15 08:45:30';
142+
$expected_date = '2024-03-15T08:45:30-05:00';
143+
144+
Functions\expect( 'mysql2date' )
145+
->once()
146+
->with( 'c', $mysql_date, false )
147+
->andReturn( $expected_date );
148+
149+
$result = $this->client->format_date_for_api( $mysql_date );
150+
151+
$this->assertSame( $expected_date, $result );
152+
}
153+
}

0 commit comments

Comments
 (0)