WordPress.org

Make WordPress Core

Ticket #25768: date-i18n.patch

File date-i18n.patch, 9.7 KB (added by Rarst, 5 months ago)

Needs wp_timezone() merged.

  • src/wp-includes/functions.php

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
     
    8181 * take over the format for the date. If it isn't, then the date format string
    8282 * will be used instead.
    8383 *
     84 * Note that due to the way WP typically generates a sum of timestamp and offset with `strtotime()`
     85 * it implies offset added at a _current_ time, not at the time which timestamp represents.
     86 * Storing such timestamps or calculating them differently will lead to invalid output.
     87 *
    8488 * @since 0.71
    8589 *
    8690 * @global WP_Locale $wp_locale
     
    106110         * See https://core.trac.wordpress.org/ticket/9396
    107111         */
    108112        $req_format = $dateformatstring;
     113        $new_format = '';
    109114
    110         $dateformatstring = preg_replace( '/(?<!\\\\)c/', DATE_W3C, $dateformatstring );
     115        // We need to unpack shorthand `r` format because it has parts that might be localized.
    111116        $dateformatstring = preg_replace( '/(?<!\\\\)r/', DATE_RFC2822, $dateformatstring );
    112117
    113         if ( ( ! empty( $wp_locale->month ) ) && ( ! empty( $wp_locale->weekday ) ) ) {
    114                 $datemonth            = $wp_locale->get_month( gmdate( 'm', $i ) );
    115                 $datemonth_abbrev     = $wp_locale->get_month_abbrev( $datemonth );
    116                 $dateweekday          = $wp_locale->get_weekday( gmdate( 'w', $i ) );
    117                 $dateweekday_abbrev   = $wp_locale->get_weekday_abbrev( $dateweekday );
    118                 $datemeridiem         = $wp_locale->get_meridiem( gmdate( 'a', $i ) );
    119                 $datemeridiem_capital = $wp_locale->get_meridiem( gmdate( 'A', $i ) );
    120                 $dateformatstring     = ' ' . $dateformatstring;
    121                 $dateformatstring     = preg_replace( '/([^\\\])D/', "\\1" . backslashit( $dateweekday_abbrev ), $dateformatstring );
    122                 $dateformatstring     = preg_replace( '/([^\\\])F/', "\\1" . backslashit( $datemonth ), $dateformatstring );
    123                 $dateformatstring     = preg_replace( '/([^\\\])l/', "\\1" . backslashit( $dateweekday ), $dateformatstring );
    124                 $dateformatstring     = preg_replace( '/([^\\\])M/', "\\1" . backslashit( $datemonth_abbrev ), $dateformatstring );
    125                 $dateformatstring     = preg_replace( '/([^\\\])a/', "\\1" . backslashit( $datemeridiem ), $dateformatstring );
    126                 $dateformatstring     = preg_replace( '/([^\\\])A/', "\\1" . backslashit( $datemeridiem_capital ), $dateformatstring );
     118        /**
     119         * Timestamp with offset is typically produced by a UTC `strtotime()` call on an input without time zone.
     120     *
     121     * This is the best attempt to reverse that operation into a local time to use.
     122         */
     123        $local_time = gmdate( 'Y-m-d H:i:s', $i );
     124        $gmt_mode   = $gmt && ( false === $timestamp_with_offset );
     125        $timezone   = $gmt_mode ? new DateTimeZone( 'UTC' ) : wp_timezone();
     126        $datetime   = date_create( $local_time, $timezone );
    127127
    128                 $dateformatstring = substr( $dateformatstring, 1, strlen( $dateformatstring ) - 1 );
     128        /**
     129         * This is a legacy implementation quirk that returned timestamp is also with offset.
     130         *
     131         * Ideally this function should never be used to produce a timestamp.
     132         */
     133        $timestamp_mode = 'U' === $dateformatstring;
     134
     135        if ( $timestamp_mode ) {
     136                $new_format = $i;
    129137        }
    130         $timezone_formats    = array( 'P', 'I', 'O', 'T', 'Z', 'e' );
    131         $timezone_formats_re = implode( '|', $timezone_formats );
    132         if ( preg_match( "/$timezone_formats_re/", $dateformatstring ) ) {
    133                 $timezone_string = get_option( 'timezone_string' );
    134                 if ( false === $timestamp_with_offset && $gmt ) {
    135                         $timezone_string = 'UTC';
    136                 }
    137                 if ( $timezone_string ) {
    138                         $timezone_object = timezone_open( $timezone_string );
    139                         $date_object     = date_create( null, $timezone_object );
    140                         foreach ( $timezone_formats as $timezone_format ) {
    141                                 if ( false !== strpos( $dateformatstring, $timezone_format ) ) {
    142                                         $formatted        = date_format( $date_object, $timezone_format );
    143                                         $dateformatstring = ' ' . $dateformatstring;
    144                                         $dateformatstring = preg_replace( "/([^\\\])$timezone_format/", "\\1" . backslashit( $formatted ), $dateformatstring );
    145                                         $dateformatstring = substr( $dateformatstring, 1, strlen( $dateformatstring ) - 1 );
    146                                 }
    147                         }
    148                 } else {
    149                         $offset = get_option( 'gmt_offset' );
    150                         foreach ( $timezone_formats as $timezone_format ) {
    151                                 if ( 'I' === $timezone_format ) {
    152                                         continue;
    153                                 }
    154138
    155                                 if ( false !== strpos( $dateformatstring, $timezone_format ) ) {
    156                                         if ( 'Z' === $timezone_format ) {
    157                                                 $formatted = (string) ( $offset * HOUR_IN_SECONDS );
    158                                         } else {
    159                                                 $prefix    = '';
    160                                                 $hours     = (int) $offset;
    161                                                 $separator = '';
    162                                                 $minutes   = abs( ( $offset - $hours ) * 60 );
     139        if ( ! $timestamp_mode && ( ! empty( $wp_locale->month ) ) && ( ! empty( $wp_locale->weekday ) ) ) {
     140                $month   = $wp_locale->get_month( $datetime->format( 'm' ) );
     141                $weekday = $wp_locale->get_weekday( $datetime->format( 'w' ) );
    163142
    164                                                 if ( 'T' === $timezone_format ) {
    165                                                         $prefix = 'GMT';
    166                                                 } elseif ( 'e' === $timezone_format || 'P' === $timezone_format ) {
    167                                                         $separator = ':';
    168                                                 }
     143                $format_length = strlen( $dateformatstring );
    169144
    170                                                 $formatted = sprintf( '%s%+03d%s%02d', $prefix, $hours, $separator, $minutes );
    171                                         }
     145                for ( $i = 0; $i < $format_length; $i ++ ) {
     146                        switch ( $dateformatstring[ $i ] ) {
     147                                case 'D':
     148                                        $new_format .= backslashit( $wp_locale->get_weekday_abbrev( $weekday ) );
     149                                        break;
     150                                case 'F':
     151                                        $new_format .= backslashit( $month );
     152                                        break;
     153                                case 'l':
     154                                        $new_format .= backslashit( $weekday );
     155                                        break;
     156                                case 'M':
     157                                        $new_format .= backslashit( $wp_locale->get_month_abbrev( $month ) );
     158                                        break;
     159                                case 'a':
     160                                        $new_format .= backslashit( $wp_locale->get_meridiem( $datetime->format( 'a' ) ) );
     161                                        break;
     162                                case 'A':
     163                                        $new_format .= backslashit( $wp_locale->get_meridiem( $datetime->format( 'A' ) ) );
     164                                        break;
     165                                case '\\':
     166                                        $new_format .= $dateformatstring[ $i ];
    172167
    173                                         $dateformatstring = ' ' . $dateformatstring;
    174                                         $dateformatstring = preg_replace( "/([^\\\])$timezone_format/", "\\1" . backslashit( $formatted ), $dateformatstring );
    175                                         $dateformatstring = substr( $dateformatstring, 1 );
    176                                 }
     168                                        // If character follows slash we add it without translating.
     169                                        if ( $i < $format_length ) {
     170                                                $new_format .= $dateformatstring[ ++ $i ];
     171                                        }
     172                                        break;
     173                                default:
     174                                        $new_format .= $dateformatstring[ $i ];
     175                                        break;
    177176                        }
    178177                }
    179178        }
    180         $j = @gmdate( $dateformatstring, $i );
     179
     180        $j = $datetime->format( $new_format );
    181181
    182182        /**
    183183         * Filters the date formatted based on the locale.
  • tests/phpunit/tests/date/dateI18n.php

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
     
    66 */
    77class Tests_Date_I18n extends WP_UnitTestCase {
    88        public function test_should_format_date() {
     9                update_option( 'timezone_string', 'UTC' );
     10
    911                $this->assertEquals( strtotime( gmdate( 'Y-m-d H:i:s' ) ), strtotime( date_i18n( 'Y-m-d H:i:s' ) ), 'The dates should be equal', 2 );
    1012        }
    1113
     
    1719                $this->assertEquals( strtotime( gmdate( DATE_RFC3339 ) ), strtotime( date_i18n( DATE_RFC3339, false, true ) ), 'The dates should be equal', 2 );
    1820        }
    1921
    20         public function test_custom_timestamp_ignores_gmt_setting() {
    21                 $this->assertEquals( '2012-12-01 00:00:00', date_i18n( 'Y-m-d H:i:s', strtotime( '2012-12-01 00:00:00' ) ) );
    22         }
    23 
    2422        public function test_custom_timezone_setting() {
    2523                update_option( 'timezone_string', 'America/Regina' );
    2624
     
    107105                        ),
    108106                );
    109107        }
     108
     109        public function test_should_return_wp_timestamp() {
     110
     111                update_option( 'timezone_string', 'Europe/Kiev' );
     112
     113                $datetime     = new DateTimeImmutable( 'now', wp_timezone() );
     114                $timestamp    = $datetime->getTimestamp();
     115                $wp_timestamp = $timestamp + $datetime->getOffset();
     116
     117                $this->assertEquals( $wp_timestamp, date_i18n( 'U' ), 2 );
     118                $this->assertEquals( $timestamp, date_i18n( 'U', false, true ), 2 );
     119                $this->assertEquals( $wp_timestamp, date_i18n( 'U', $wp_timestamp ) );
     120        }
     121
     122        /**
     123         * @link   https://core.trac.wordpress.org/ticket/43530
     124         * @ticket 43530
     125         */
     126        public function test_swatch_internet_time_with_wp_timestamp() {
     127                update_option( 'timezone_string', 'America/Regina' );
     128
     129                $this->assertEquals( gmdate( 'B' ), date_i18n( 'B' ) );
     130        }
     131
     132        public function test_should_handle_escaped_formats() {
     133
     134                $format = 'D | \D | \\D | \\\D | \\\\D | \\\\\D | \\\\\\D';
     135
     136                $this->assertEquals( gmdate( $format ), date_i18n( $format ) );
     137        }
     138
     139        /**
     140         * @dataProvider dst_times
     141         *
     142         * @param string $time    Time to test in Y-m-d H:i:s format.
     143         * @param string $tmezone PHP time zone string to use.
     144         */
     145        public function test_should_handle_dst( $time, $timezone ) {
     146
     147                update_option( 'timezone_string', $timezone );
     148
     149                $timezone     = new DateTimeZone( $timezone );
     150                $datetime     = new DateTime( $time, $timezone );
     151                $wp_timestamp = strtotime( $time );
     152                $format       = 'I ' . DATE_RFC3339;
     153
     154                $this->assertEquals( $datetime->format( $format ), date_i18n( $format, $wp_timestamp ) );
     155        }
     156
     157        public function dst_times() {
     158                return [
     159                        'Before DST start' => [ '2019-03-31 02:59:00', 'Europe/Kiev' ],
     160                        'After DST start'  => [ '2019-03-31 04:01:00', 'Europe/Kiev' ],
     161                        'Before DST end'   => [ '2019-10-27 02:59:00', 'Europe/Kiev' ],
     162                        'After DST end'    => [ '2019-10-27 04:01:00', 'Europe/Kiev' ],
     163                ];
     164        }
    110165}