diff --git a/src/wp-includes/general-template.php b/src/wp-includes/general-template.php index d551ed0cd1..eab85c6b7a 100644 --- a/src/wp-includes/general-template.php +++ b/src/wp-includes/general-template.php @@ -2515,7 +2515,7 @@ function the_time( $d = '' ) { * @param string $d Optional. Format to use for retrieving the time the post * was written. Either 'G', 'U', or php date format defaults * to the value specified in the time_format option. Default empty. - * @param int|WP_Post $post WP_Post object or ID. Default is global $post object. + * @param int|WP_Post $post WP_Post object or ID. Default is global `$post` object. * @return string|int|false Formatted date string or Unix timestamp if `$d` is 'U' or 'G'. False on failure. */ function get_the_time( $d = '', $post = null ) { @@ -2553,7 +2553,7 @@ function get_the_time( $d = '', $post = null ) { * @param string $d Optional. Format to use for retrieving the time the post * was written. Either 'G', 'U', or php date format. Default 'U'. * @param bool $gmt Optional. Whether to retrieve the GMT time. Default false. - * @param int|WP_Post $post WP_Post object or ID. Default is global $post object. + * @param int|WP_Post $post WP_Post object or ID. Default is global `$post` object. * @param bool $translate Whether to translate the time string. Default false. * @return string|int|false Formatted date string or Unix timestamp if `$d` is 'U' or 'G'. False on failure. */ @@ -2564,13 +2564,28 @@ function get_post_time( $d = 'U', $gmt = false, $post = null, $translate = false return false; } - if ( $gmt ) { - $time = $post->post_date_gmt; - } else { - $time = $post->post_date; + $datetime = get_post_datetime( $post ); + + if ( false === $datetime ) { + return false; } - $time = mysql2date( $d, $time, $translate ); + if ( 'U' === $d || 'G' === $d ) { + $time = $datetime->getTimestamp(); + + // Returns a sum of timestamp with timezone offset. Ideally should never be used. + if ( ! $gmt ) { + $time += $datetime->getOffset(); + } + } elseif ( $translate ) { + $time = wp_date( $d, $datetime->getTimestamp(), $gmt ? new DateTimeZone( 'UTC' ) : null ); + } else { + if ( $gmt ) { + $datetime = $datetime->setTimezone( new DateTimeZone( 'UTC' ) ); + } + + $time = $datetime->format( $d ); + } /** * Filters the localized time a post was written. @@ -2585,6 +2600,55 @@ function get_post_time( $d = 'U', $gmt = false, $post = null, $translate = false return apply_filters( 'get_post_time', $time, $d, $gmt ); } +/** + * Retrieve post published or modified time as a `DateTimeImmutable` object instance. + * + * The object will be set to the timezone from WordPress settings. + * + * @since 5.3.0 + * + * @param int|WP_Post $post Optional. WP_Post object or ID. Default is global `$post` object. + * @param string $field Optional. Post field to use. Accepts 'date' or 'modified'. + * @return DateTimeImmutable|false Time object on success, false on failure. + */ +function get_post_datetime( $post = null, $field = 'date' ) { + $post = get_post( $post ); + + if ( ! $post ) { + return false; + } + + $time = ( 'modified' === $field ) ? $post->post_modified : $post->post_date; + + if ( empty( $time ) || '0000-00-00 00:00:00' === $time ) { + return false; + } + + return date_create_immutable_from_format( 'Y-m-d H:i:s', $time, wp_timezone() ); +} + +/** + * Retrieve post published or modified time as a Unix timestamp. + * + * Note that this function returns a true Unix timestamp, not summed with timezone offset + * like older WP functions. + * + * @since 5.3.0 + * + * @param int|WP_Post $post Optional. WP_Post object or ID. Default is global `$post` object. + * @param string $field Optional. Post field to use. Accepts 'date' or 'modified'. + * @return int|false Unix timestamp on success, false on failure. + */ +function get_post_timestamp( $post = null, $field = 'date' ) { + $datetime = get_post_datetime( $post, $field ); + + if ( false === $datetime ) { + return false; + } + + return $datetime->getTimestamp(); +} + /** * Display the time at which the post was last modified. * @@ -2653,7 +2717,7 @@ function get_the_modified_time( $d = '', $post = null ) { * @param string $d Optional. Format to use for retrieving the time the post * was modified. Either 'G', 'U', or php date format. Default 'U'. * @param bool $gmt Optional. Whether to retrieve the GMT time. Default false. - * @param int|WP_Post $post WP_Post object or ID. Default is global $post object. + * @param int|WP_Post $post WP_Post object or ID. Default is global `$post` object. * @param bool $translate Whether to translate the time string. Default false. * @return string|int|false Formatted date string or Unix timestamp if `$d` is 'U' or 'G'. False on failure. */ @@ -2664,13 +2728,28 @@ function get_post_modified_time( $d = 'U', $gmt = false, $post = null, $translat return false; } - if ( $gmt ) { - $time = $post->post_modified_gmt; - } else { - $time = $post->post_modified; + $datetime = get_post_datetime( $post, 'modified' ); + + if ( false === $datetime ) { + return false; } - $time = mysql2date( $d, $time, $translate ); + if ( 'U' === $d || 'G' === $d ) { + $time = $datetime->getTimestamp(); + + // Returns a sum of timestamp with timezone offset. Ideally should never be used. + if ( ! $gmt ) { + $time += $datetime->getOffset(); + } + } elseif ( $translate ) { + $time = wp_date( $d, $datetime->getTimestamp(), $gmt ? new DateTimeZone( 'UTC' ) : null ); + } else { + if ( $gmt ) { + $datetime = $datetime->setTimezone( new DateTimeZone( 'UTC' ) ); + } + + $time = $datetime->format( $d ); + } /** * Filters the localized time a post was last modified. diff --git a/tests/phpunit/tests/date/postTime.php b/tests/phpunit/tests/date/postTime.php new file mode 100644 index 0000000000..02da7b5487 --- /dev/null +++ b/tests/phpunit/tests/date/postTime.php @@ -0,0 +1,65 @@ +format( 'Y-m-d H:i:s' ); + $timestamp = $datetime->getTimestamp(); + $wp_timestamp = $datetime->getTimestamp() + $datetime->getOffset(); + + $post_id = self::factory()->post->create( + array( + 'post_date' => $mysql, + 'post_modified' => $mysql, + ) + ); + + $this->assertEquals( $wp_timestamp, get_post_time( 'U', false, $post_id ) ); + $this->assertEquals( $wp_timestamp, get_post_time( 'G', false, $post_id ) ); + $this->assertEquals( $timestamp, get_post_time( 'U', true, $post_id ) ); + $this->assertEquals( $timestamp, get_post_time( 'G', true, $post_id ) ); + $this->assertEquals( $wp_timestamp, get_post_modified_time( 'U', false, $post_id ) ); + $this->assertEquals( $wp_timestamp, get_post_modified_time( 'G', false, $post_id ) ); + $this->assertEquals( $timestamp, get_post_modified_time( 'U', true, $post_id ) ); + $this->assertEquals( $timestamp, get_post_modified_time( 'G', true, $post_id ) ); + } + + /** + * @ticket 25002 + */ + public function test_should_return_time() { + $timezone = 'Europe/Kiev'; + update_option( 'timezone_string', $timezone ); + + $datetime = new DateTimeImmutable( 'now', new DateTimeZone( $timezone ) ); + $mysql = $datetime->format( 'Y-m-d H:i:s' ); + $rfc3339 = $datetime->format( DATE_RFC3339 ); + $rfc3339_utc = $datetime->setTimezone( new DateTimeZone( 'UTC' ) )->format( DATE_RFC3339 ); + $post_id = self::factory()->post->create( + array( + 'post_date' => $mysql, + 'post_modified' => $mysql, + ) + ); + + $this->assertEquals( $rfc3339, get_post_time( DATE_RFC3339, false, $post_id ) ); + $this->assertEquals( $rfc3339_utc, get_post_time( DATE_RFC3339, true, $post_id ) ); + $this->assertEquals( $rfc3339, get_post_time( DATE_RFC3339, false, $post_id, true ) ); + $this->assertEquals( $rfc3339_utc, get_post_time( DATE_RFC3339, true, $post_id, true ) ); + $this->assertEquals( $rfc3339, get_post_modified_time( DATE_RFC3339, false, $post_id ) ); + $this->assertEquals( $rfc3339_utc, get_post_modified_time( DATE_RFC3339, true, $post_id ) ); + $this->assertEquals( $rfc3339, get_post_modified_time( DATE_RFC3339, false, $post_id, true ) ); + $this->assertEquals( $rfc3339_utc, get_post_modified_time( DATE_RFC3339, true, $post_id, true ) ); + } +}