Skip to content

Commit

Permalink
Date/Time: Prevent type errors in current_time().
Browse files Browse the repository at this point in the history
Prevents a potential type error when calling `current_time( 'timestamp' )` by casting `get_option( 'gmt_offset' )` to a float prior to performing calculations with the value.

This mainly accounts for incorrect storage of values, such as an empty string or city name.

Follow up to [45856], [55054], [55598].

Props hellofromtonya, peterwilsoncc, rarst, costdev, Nick_theGeek, SergeyBiryukov, johnbillion, desrosj, reputeinfosystems, audrasjb, oglekler.
Fixes #57035.


git-svn-id: https://develop.svn.wordpress.org/trunk@58923 602fd350-edb4-49c9-b593-d223f7449a82
  • Loading branch information
peterwilsoncc committed Aug 22, 2024
1 parent 06f7c82 commit 6e7b124
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 11 deletions.
2 changes: 1 addition & 1 deletion src/wp-includes/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ function mysql2date( $format, $date, $translate = true ) {
function current_time( $type, $gmt = 0 ) {
// Don't use non-GMT timestamp, unless you know the difference and really need to.
if ( 'timestamp' === $type || 'U' === $type ) {
return $gmt ? time() : time() + (int) ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS );
return $gmt ? time() : time() + (int) ( (float) get_option( 'gmt_offset' ) * HOUR_IN_SECONDS );
}

if ( 'mysql' === $type ) {
Expand Down
127 changes: 117 additions & 10 deletions tests/phpunit/tests/date/currentTime.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,34 +91,44 @@ public function test_should_work_with_changed_timezone() {

/**
* @ticket 40653
* @ticket 57998
*
* @dataProvider data_timezones
*
* @param string $timezone The timezone to test.
*/
public function test_should_return_wp_timestamp() {
update_option( 'timezone_string', 'Europe/Helsinki' );
public function test_should_return_wp_timestamp( $timezone ) {
update_option( 'timezone_string', $timezone );

$timestamp = time();
$datetime = new DateTime( '@' . $timestamp );
$datetime->setTimezone( wp_timezone() );
$wp_timestamp = $timestamp + $datetime->getOffset();

// phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.RequestedUTC
$this->assertEqualsWithDelta( $timestamp, current_time( 'timestamp', true ), 2, 'The dates should be equal' );
$this->assertEqualsWithDelta( $timestamp, current_time( 'timestamp', true ), 2, 'When passing "timestamp", the date should be equal to time()' );
// phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.RequestedUTC
$this->assertEqualsWithDelta( $timestamp, current_time( 'U', true ), 2, 'The dates should be equal' );
$this->assertEqualsWithDelta( $timestamp, current_time( 'U', true ), 2, 'When passing "U", the date should be equal to time()' );

// phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
$this->assertEqualsWithDelta( $wp_timestamp, current_time( 'timestamp' ), 2, 'The dates should be equal' );
$this->assertEqualsWithDelta( $wp_timestamp, current_time( 'timestamp' ), 2, 'When passing "timestamp", the date should be equal to calculated timestamp' );
// phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
$this->assertEqualsWithDelta( $wp_timestamp, current_time( 'U' ), 2, 'The dates should be equal' );
$this->assertEqualsWithDelta( $wp_timestamp, current_time( 'U' ), 2, 'When passing "U", the date should be equal to calculated timestamp' );

// phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
$this->assertIsInt( current_time( 'timestamp' ) );
$this->assertIsInt( current_time( 'timestamp' ), 'The returned timestamp should be an integer' );
}

/**
* @ticket 40653
* @ticket 57998
*
* @dataProvider data_timezones
*
* @param string $timezone The timezone to test.
*/
public function test_should_return_correct_local_time() {
update_option( 'timezone_string', 'Europe/Helsinki' );
public function test_should_return_correct_local_time( $timezone ) {
update_option( 'timezone_string', $timezone );

$timestamp = time();
$datetime_local = new DateTime( '@' . $timestamp );
Expand All @@ -127,7 +137,20 @@ public function test_should_return_correct_local_time() {
$datetime_utc->setTimezone( new DateTimeZone( 'UTC' ) );

$this->assertEqualsWithDelta( strtotime( $datetime_local->format( DATE_W3C ) ), strtotime( current_time( DATE_W3C ) ), 2, 'The dates should be equal' );
$this->assertEqualsWithDelta( strtotime( $datetime_utc->format( DATE_W3C ) ), strtotime( current_time( DATE_W3C, true ) ), 2, 'The dates should be equal' );
$this->assertEqualsWithDelta( strtotime( $datetime_utc->format( DATE_W3C ) ), strtotime( current_time( DATE_W3C, true ) ), 2, 'When passing "timestamp", the dates should be equal' );
}

/**
* Data provider.
*
* @return array[]
*/
public function data_timezones() {
return array(
array( 'Europe/Helsinki' ),
array( 'Indian/Antananarivo' ),
array( 'Australia/Adelaide' ),
);
}

/**
Expand Down Expand Up @@ -158,4 +181,88 @@ public function test_should_work_with_deprecated_timezone() {
$this->assertSame( gmdate( $format ), $current_time_gmt, 'The dates should be equal [3]' );
$this->assertSame( $datetime->format( $format ), $current_time, 'The dates should be equal [4]' );
}

/**
* Ensures an empty offset does not cause a type error.
*
* @ticket 57998
*/
public function test_empty_offset_does_not_cause_a_type_error() {
// Ensure `wp_timezone_override_offset()` doesn't override offset.
update_option( 'timezone_string', '' );
update_option( 'gmt_offset', '' );

$expected = time();

// phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
$this->assertEqualsWithDelta( $expected, current_time( 'timestamp' ), 2, 'The timestamps should be equal' );
}

/**
* Ensures the offset applied in current_time() is correct.
*
* @ticket 57998
*
* @dataProvider data_partial_hour_timezones_with_timestamp
*
* @param float $partial_hour Partial hour GMT offset to test.
*/
public function test_partial_hour_timezones_with_timestamp( $partial_hour ) {
// Ensure `wp_timezone_override_offset()` doesn't override offset.
update_option( 'timezone_string', '' );
update_option( 'gmt_offset', $partial_hour );

$expected = time() + (int) ( $partial_hour * HOUR_IN_SECONDS );

// phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
$this->assertEqualsWithDelta( $expected, current_time( 'timestamp' ), 2, 'The timestamps should be equal' );
}

/**
* Tests the tests.
*
* Ensures the offsets match the stated timezones in the data provider.
*
* @ticket 57998
*
* @dataProvider data_partial_hour_timezones_with_timestamp
*
* @param float $partial_hour Partial hour GMT offset to test.
* @param string $timezone_string Timezone string to test.
*/
public function test_partial_hour_timezones_match_datetime_offset( $partial_hour, $timezone_string ) {
$timezone = new DateTimeZone( $timezone_string );
$datetime = new DateTime( 'now', $timezone );
$dst_offset = (int) $datetime->format( 'I' );

// Timezone offset in hours.
$offset = $timezone->getOffset( $datetime ) / HOUR_IN_SECONDS;

/*
* Adjust for daylight saving time.
*
* DST adds an hour to the offset, the partial hour offset
* is set the the standard time offset so this removes the
* DST offset to avoid false negatives.
*/
$offset -= $dst_offset;

$this->assertSame( $partial_hour, $offset, 'The offset should match to timezone.' );
}

/**
* Data provider.
*
* @return array[]
*/
public function data_partial_hour_timezones_with_timestamp() {
return array(
'+12:45' => array( 12.75, 'Pacific/Chatham' ), // New Zealand, Chatham Islands.
'+9:30' => array( 9.5, 'Australia/Darwin' ), // Australian Northern Territory.
'+05:30' => array( 5.5, 'Asia/Kolkata' ), // India and Sri Lanka.
'+05:45' => array( 5.75, 'Asia/Kathmandu' ), // Nepal.
'-03:30' => array( -3.50, 'Canada/Newfoundland' ), // Canada, Newfoundland.
'-09:30' => array( -9.50, 'Pacific/Marquesas' ), // French Polynesia, Marquesas Islands.
);
}
}

0 comments on commit 6e7b124

Please sign in to comment.