Make WordPress Core


Ignore:
Timestamp:
08/23/2019 12:56:21 AM (5 years ago)
Author:
SergeyBiryukov
Message:

Date/Time: Rewrite and simplify date_i18n() using wp_timezone() to address multiple issues with certain date formats and timezones, while preserving some extra handling for legacy use cases.

Improve unit test coverage.

Props Rarst, remcotolsma, raubvogel.
Fixes #25768.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/functions.php

    r45877 r45882  
    129129 * take over the format for the date. If it isn't, then the date format string
    130130 * will be used instead.
     131 *
     132 * Note that due to the way WP typically generates a sum of timestamp and offset
     133 * with `strtotime()`, it implies offset added at a _current_ time, not at the time
     134 * the timestamp represents. Storing such timestamps or calculating them differently
     135 * will lead to invalid output.
    131136 *
    132137 * @since 0.71
     
    144149function date_i18n( $dateformatstring, $timestamp_with_offset = false, $gmt = false ) {
    145150    global $wp_locale;
     151
    146152    $i = $timestamp_with_offset;
    147153
     
    155161     */
    156162    $req_format = $dateformatstring;
    157 
    158     $dateformatstring = preg_replace( '/(?<!\\\\)c/', DATE_W3C, $dateformatstring );
     163    $new_format = '';
     164
     165    // We need to unpack shorthand `r` format because it has parts that might be localized.
    159166    $dateformatstring = preg_replace( '/(?<!\\\\)r/', DATE_RFC2822, $dateformatstring );
    160167
    161     if ( ( ! empty( $wp_locale->month ) ) && ( ! empty( $wp_locale->weekday ) ) ) {
    162         $datemonth            = $wp_locale->get_month( gmdate( 'm', $i ) );
    163         $datemonth_abbrev     = $wp_locale->get_month_abbrev( $datemonth );
    164         $dateweekday          = $wp_locale->get_weekday( gmdate( 'w', $i ) );
    165         $dateweekday_abbrev   = $wp_locale->get_weekday_abbrev( $dateweekday );
    166         $datemeridiem         = $wp_locale->get_meridiem( gmdate( 'a', $i ) );
    167         $datemeridiem_capital = $wp_locale->get_meridiem( gmdate( 'A', $i ) );
    168         $dateformatstring     = ' ' . $dateformatstring;
    169         $dateformatstring     = preg_replace( '/([^\\\])D/', "\\1" . backslashit( $dateweekday_abbrev ), $dateformatstring );
    170         $dateformatstring     = preg_replace( '/([^\\\])F/', "\\1" . backslashit( $datemonth ), $dateformatstring );
    171         $dateformatstring     = preg_replace( '/([^\\\])l/', "\\1" . backslashit( $dateweekday ), $dateformatstring );
    172         $dateformatstring     = preg_replace( '/([^\\\])M/', "\\1" . backslashit( $datemonth_abbrev ), $dateformatstring );
    173         $dateformatstring     = preg_replace( '/([^\\\])a/', "\\1" . backslashit( $datemeridiem ), $dateformatstring );
    174         $dateformatstring     = preg_replace( '/([^\\\])A/', "\\1" . backslashit( $datemeridiem_capital ), $dateformatstring );
    175 
    176         $dateformatstring = substr( $dateformatstring, 1, strlen( $dateformatstring ) - 1 );
    177     }
    178     $timezone_formats    = array( 'P', 'I', 'O', 'T', 'Z', 'e' );
    179     $timezone_formats_re = implode( '|', $timezone_formats );
    180     if ( preg_match( "/$timezone_formats_re/", $dateformatstring ) ) {
    181         $timezone_string = get_option( 'timezone_string' );
    182         if ( false === $timestamp_with_offset && $gmt ) {
    183             $timezone_string = 'UTC';
    184         }
    185         if ( $timezone_string ) {
    186             $timezone_object = timezone_open( $timezone_string );
    187             $date_object     = date_create( null, $timezone_object );
    188             foreach ( $timezone_formats as $timezone_format ) {
    189                 if ( false !== strpos( $dateformatstring, $timezone_format ) ) {
    190                     $formatted        = date_format( $date_object, $timezone_format );
    191                     $dateformatstring = ' ' . $dateformatstring;
    192                     $dateformatstring = preg_replace( "/([^\\\])$timezone_format/", "\\1" . backslashit( $formatted ), $dateformatstring );
    193                     $dateformatstring = substr( $dateformatstring, 1, strlen( $dateformatstring ) - 1 );
    194                 }
     168    /*
     169     * Timestamp with offset is typically produced by a UTC `strtotime()` call on an input without timezone.
     170     * This is the best attempt to reverse that operation into a local time to use.
     171     */
     172    $local_time = gmdate( 'Y-m-d H:i:s', $i );
     173    $gmt_mode   = $gmt && ( false === $timestamp_with_offset );
     174    $timezone   = $gmt_mode ? new DateTimeZone( 'UTC' ) : wp_timezone();
     175    $datetime   = date_create( $local_time, $timezone );
     176
     177    /*
     178     * This is a legacy implementation quirk that the returned timestamp is also with offset.
     179     * Ideally this function should never be used to produce a timestamp.
     180     */
     181    $timestamp_mode = ( 'U' === $dateformatstring );
     182
     183    if ( $timestamp_mode ) {
     184        $new_format = $i;
     185    }
     186
     187    if ( ! $timestamp_mode && ! empty( $wp_locale->month ) && ! empty( $wp_locale->weekday ) ) {
     188        $month   = $wp_locale->get_month( $datetime->format( 'm' ) );
     189        $weekday = $wp_locale->get_weekday( $datetime->format( 'w' ) );
     190
     191        $format_length = strlen( $dateformatstring );
     192
     193        for ( $i = 0; $i < $format_length; $i ++ ) {
     194            switch ( $dateformatstring[ $i ] ) {
     195                case 'D':
     196                    $new_format .= backslashit( $wp_locale->get_weekday_abbrev( $weekday ) );
     197                    break;
     198                case 'F':
     199                    $new_format .= backslashit( $month );
     200                    break;
     201                case 'l':
     202                    $new_format .= backslashit( $weekday );
     203                    break;
     204                case 'M':
     205                    $new_format .= backslashit( $wp_locale->get_month_abbrev( $month ) );
     206                    break;
     207                case 'a':
     208                    $new_format .= backslashit( $wp_locale->get_meridiem( $datetime->format( 'a' ) ) );
     209                    break;
     210                case 'A':
     211                    $new_format .= backslashit( $wp_locale->get_meridiem( $datetime->format( 'A' ) ) );
     212                    break;
     213                case '\\':
     214                    $new_format .= $dateformatstring[ $i ];
     215
     216                    // If character follows a slash, we add it without translating.
     217                    if ( $i < $format_length ) {
     218                        $new_format .= $dateformatstring[ ++$i ];
     219                    }
     220                    break;
     221                default:
     222                    $new_format .= $dateformatstring[ $i ];
     223                    break;
    195224            }
    196         } else {
    197             $offset = get_option( 'gmt_offset' );
    198             foreach ( $timezone_formats as $timezone_format ) {
    199                 if ( 'I' === $timezone_format ) {
    200                     continue;
    201                 }
    202 
    203                 if ( false !== strpos( $dateformatstring, $timezone_format ) ) {
    204                     if ( 'Z' === $timezone_format ) {
    205                         $formatted = (string) ( $offset * HOUR_IN_SECONDS );
    206                     } else {
    207                         $prefix    = '';
    208                         $hours     = (int) $offset;
    209                         $separator = '';
    210                         $minutes   = abs( ( $offset - $hours ) * 60 );
    211 
    212                         if ( 'T' === $timezone_format ) {
    213                             $prefix = 'GMT';
    214                         } elseif ( 'e' === $timezone_format || 'P' === $timezone_format ) {
    215                             $separator = ':';
    216                         }
    217 
    218                         $formatted = sprintf( '%s%+03d%s%02d', $prefix, $hours, $separator, $minutes );
    219                     }
    220 
    221                     $dateformatstring = ' ' . $dateformatstring;
    222                     $dateformatstring = preg_replace( "/([^\\\])$timezone_format/", "\\1" . backslashit( $formatted ), $dateformatstring );
    223                     $dateformatstring = substr( $dateformatstring, 1 );
    224                 }
    225             }
    226         }
    227     }
    228     $j = gmdate( $dateformatstring, $i );
     225        }
     226    }
     227
     228    $j = $datetime->format( $new_format );
    229229
    230230    /**
     
    240240     */
    241241    $j = apply_filters( 'date_i18n', $j, $req_format, $i, $gmt );
     242
    242243    return $j;
    243244}
     
    15541555     */
    15551556    echo apply_filters( 'robots_txt', $output, $public );
     1557}
     1558
     1559/**
     1560 * Display the robots.txt file content.
     1561 *
     1562 * The echo content should be with usage of the permalinks or for creating the
     1563 * robots.txt file.
     1564 *
     1565 * @since 5.3.0
     1566 */
     1567function do_favicon() {
     1568    /**
     1569     * Fires when serving the favicon.ico file.
     1570     *
     1571     * @since 5.3.0
     1572     */
     1573    do_action( 'do_faviconico' );
     1574
     1575    wp_safe_redirect( esc_url( get_site_icon_url( 32, admin_url( 'images/w-logo-blue.png' ) ) ) );
     1576
     1577}
     1578
     1579/**
     1580 * Don't load all of WordPress when handling a favicon.ico request.
     1581 *
     1582 * Instead, send the headers for a zero-length favicon and bail.
     1583 *
     1584 * @since 3.0.0
     1585 */
     1586function wp_favicon_request() {
     1587    if ( '/favicon.ico' == $_SERVER['REQUEST_URI'] ) {
     1588        header( 'Content-Type: image/vnd.microsoft.icon' );
     1589        exit;
     1590    }
    15561591}
    15571592
Note: See TracChangeset for help on using the changeset viewer.