Make WordPress Core

Changeset 49146


Ignore:
Timestamp:
10/14/2020 06:19:43 PM (6 years ago)
Author:
iandunn
Message:

Community Events: Display dates and times in the user's time zone.

Fixes #51130
Props sippis, hlashbrooke, audrasjb, Rarst, iandunn

Location:
trunk
Files:
1 added
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/js/_enqueues/wp/dashboard.js

    r48168 r49146  
    267267
    268268        var communityEventsData = window.communityEventsData || {},
     269                dateI18n = wp.date.dateI18n,
     270                format = wp.date.format,
     271                sprintf = wp.i18n.sprintf,
     272                __ = wp.i18n.__,
     273                _x = wp.i18n._x,
    269274                app;
    270275
     
    442447                                        app.renderEventsTemplate({
    443448                                                'location' : false,
     449                                                'events'   : [],
    444450                                                'error'    : true
    445451                                        }, initiatedBy );
     
    465471                                $locationMessage = $( '#community-events-location-message' ),
    466472                                $results         = $( '.community-events-results' );
     473
     474                        templateParams.events = app.populateDynamicEventFields(
     475                                templateParams.events,
     476                                communityEventsData.time_format
     477                        );
    467478
    468479                        /*
     
    577588                                app.toggleLocationForm( 'show' );
    578589                        }
     590                },
     591
     592                /**
     593                 * Populate event fields that have to be calculated on the fly.
     594                 *
     595                 * These can't be stored in the database, because they're dependent on
     596                 * the user's current time zone, locale, etc.
     597                 *
     598                 * @since 5.6.0
     599                 *
     600                 * @param {Array}  rawEvents  The events that should have dynamic fields added to them.
     601                 * @param {string} timeFormat A time format acceptable by `wp.date.dateI18n()`.
     602                 *
     603                 * @returns {Array}
     604                 */
     605                populateDynamicEventFields: function( rawEvents, timeFormat ) {
     606                        // Clone the parameter to avoid mutating it, so that this can remain a pure function.
     607                        var populatedEvents = JSON.parse( JSON.stringify( rawEvents ) );
     608
     609                        $.each( populatedEvents, function( index, event ) {
     610                                var timeZone = app.getTimeZone( event.start_unix_timestamp * 1000 );
     611
     612                                event.user_formatted_date = app.getFormattedDate(
     613                                        event.start_unix_timestamp * 1000,
     614                                        event.end_unix_timestamp * 1000,
     615                                        timeZone
     616                                );
     617
     618                                event.user_formatted_time = dateI18n(
     619                                        timeFormat,
     620                                        event.start_unix_timestamp * 1000,
     621                                        timeZone
     622                                );
     623
     624                                event.timeZoneAbbreviation = app.getTimeZoneAbbreviation( event.start_unix_timestamp * 1000 );
     625                        } );
     626
     627                        return populatedEvents;
     628                },
     629
     630                /**
     631                 * Returns the user's local/browser time zone, in a form suitable for `wp.date.i18n()`.
     632                 *
     633                 * @since 5.6.0
     634                 *
     635                 * @param startTimestamp
     636                 *
     637                 * @returns {string|number}
     638                 */
     639                getTimeZone: function( startTimestamp ) {
     640                        /*
     641                         * Prefer a name like `Europe/Helsinki`, since that automatically tracks daylight savings. This
     642                         * doesn't need to take `startTimestamp` into account for that reason.
     643                         */
     644                        var timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
     645
     646                        /*
     647                         * Fall back to an offset for IE11, which declares the property but doesn't assign a value.
     648                         */
     649                        if ( 'undefined' === typeof timeZone ) {
     650                                /*
     651                                 * It's important to use the _event_ time, not the _current_
     652                                 * time, so that daylight savings time is accounted for.
     653                                 */
     654                                timeZone = app.getFlippedTimeZoneOffset( startTimestamp );
     655                        }
     656
     657                        return timeZone;
     658                },
     659
     660                /**
     661                 * Get intuitive time zone offset.
     662                 *
     663                 * `Data.prototype.getTimezoneOffset()` returns a positive value for time zones
     664                 * that are _behind_ UTC, and a _negative_ value for ones that are ahead.
     665                 *
     666                 * See https://stackoverflow.com/questions/21102435/why-does-javascript-date-gettimezoneoffset-consider-0500-as-a-positive-off.
     667                 *
     668                 * @since 5.6.0
     669                 *
     670                 * @param {number} startTimestamp
     671                 *
     672                 * @returns {number}
     673                 */
     674                getFlippedTimeZoneOffset: function( startTimestamp ) {
     675                        return new Date( startTimestamp ).getTimezoneOffset() * -1;
     676                },
     677
     678                /**
     679                 * Get a short time zone name, like `PST`.
     680                 *
     681                 * @since 5.6.0
     682                 *
     683                 * @param {number} startTimestamp
     684                 *
     685                 * @returns {string}
     686                 */
     687                getTimeZoneAbbreviation: function( startTimestamp ) {
     688                        var timeZoneAbbreviation,
     689                                eventDateTime = new Date( startTimestamp );
     690
     691                        /*
     692                         * Leaving the `locales` argument undefined is important, so that the browser
     693                         * displays the abbreviation that's most appropriate for the current locale. For
     694                         * some that will be `UTC{+|-}{n}`, and for others it will be a code like `PST`.
     695                         *
     696                         * This doesn't need to take `startTimestamp` into account, because a name like
     697                         * `America/Chicago` automatically tracks daylight savings.
     698                         */
     699                        var shortTimeStringParts = eventDateTime.toLocaleTimeString( undefined, { timeZoneName : 'short' } ).split( ' ' );
     700
     701                        if ( 3 === shortTimeStringParts.length ) {
     702                                timeZoneAbbreviation = shortTimeStringParts[2];
     703                        }
     704
     705                        if ( 'undefined' === typeof timeZoneAbbreviation ) {
     706                                /*
     707                                 * It's important to use the _event_ time, not the _current_
     708                                 * time, so that daylight savings time is accounted for.
     709                                 */
     710                                var timeZoneOffset = app.getFlippedTimeZoneOffset( startTimestamp ),
     711                                        sign = -1 === Math.sign( timeZoneOffset ) ? '' : '+';
     712
     713                                // translators: Used as part of a string like `GMT+5` in the Events Widget.
     714                                timeZoneAbbreviation = _x( 'GMT', 'Events widget offset prefix' ) + sign + ( timeZoneOffset / 60 );
     715                        }
     716
     717                        return timeZoneAbbreviation;
     718                },
     719
     720                /**
     721                 * Format a start/end date in the user's local time zone and locale.
     722                 *
     723                 * @since 5.6.0
     724                 *
     725                 * @param {int}    startDate   The Unix timestamp in milliseconds when the the event starts.
     726                 * @param {int}    endDate     The Unix timestamp in milliseconds when the the event ends.
     727                 * @param {string} timeZone    A time zone string or offset which is parsable by `wp.date.i18n()`.
     728                 *
     729                 * @returns {string}
     730                 */
     731                getFormattedDate: function( startDate, endDate, timeZone ) {
     732                        var formattedDate;
     733
     734                        /*
     735                         * The `date_format` option is not used because it's important
     736                         * in this context to keep the day of the week in the displayed date,
     737                         * so that users can tell at a glance if the event is on a day they
     738                         * are available, without having to open the link.
     739                         *
     740                         * The case of crossing a year boundary is intentionally not handled.
     741                         * It's so rare in practice that it's not worth the complexity
     742                         * tradeoff. The _ending_ year should be passed to
     743                         * `multiple_month_event`, though, just in case.
     744                         */
     745                        /* translators: Date format for upcoming events on the dashboard. Include the day of the week. See https://www.php.net/manual/datetime.format.php */
     746                        var singleDayEvent = __( 'l, M j, Y' ),
     747                                /* translators: Date string for upcoming events. 1: Month, 2: Starting day, 3: Ending day, 4: Year. */
     748                                multipleDayEvent = __( '%1$s %2$d–%3$d, %4$d' ),
     749                                /* translators: Date string for upcoming events. 1: Starting month, 2: Starting day, 3: Ending month, 4: Ending day, 5: Ending year. */
     750                                multipleMonthEvent = __( '%1$s %2$d – %3$s %4$d, %5$d' );
     751
     752                        // Detect single-day events.
     753                        if ( ! endDate || format( 'Y-m-d', startDate ) === format( 'Y-m-d', endDate ) ) {
     754                                formattedDate = dateI18n( singleDayEvent, startDate, timeZone );
     755
     756                        // Multiple day events.
     757                        } else if ( format( 'Y-m', startDate ) === format( 'Y-m', endDate ) ) {
     758                                formattedDate = sprintf(
     759                                        multipleDayEvent,
     760                                        dateI18n( _x( 'F', 'upcoming events month format' ), startDate, timeZone ),
     761                                        dateI18n( _x( 'j', 'upcoming events day format' ), startDate, timeZone ),
     762                                        dateI18n( _x( 'j', 'upcoming events day format' ), endDate, timeZone ),
     763                                        dateI18n( _x( 'Y', 'upcoming events year format' ), endDate, timeZone )
     764                                );
     765
     766                        // Multi-day events that cross a month boundary.
     767                        } else {
     768                                formattedDate = sprintf(
     769                                        multipleMonthEvent,
     770                                        dateI18n( _x( 'F', 'upcoming events month format' ), startDate, timeZone ),
     771                                        dateI18n( _x( 'j', 'upcoming events day format' ), startDate, timeZone ),
     772                                        dateI18n( _x( 'F', 'upcoming events month format' ), endDate, timeZone ),
     773                                        dateI18n( _x( 'j', 'upcoming events day format' ), endDate, timeZone ),
     774                                        dateI18n( _x( 'Y', 'upcoming events year format' ), endDate, timeZone )
     775                                );
     776                        }
     777
     778                        return formattedDate;
    579779                }
    580780        };
  • trunk/src/wp-admin/includes/class-wp-community-events.php

    r49145 r49146  
    7878         *
    7979         * @since 4.8.0
     80         * @since 5.6.0 Response no longer contains formatted date field. They're added
     81         *              in `wp.communityEvents.populateDynamicEventFields()` now.
    8082         *
    8183         * @param string $location_search Optional. City name to help determine the location.
     
    166168
    167169                        $response_body['events'] = $this->trim_events( $response_body['events'] );
    168                         $response_body = $this->format_event_data_time( $response_body );
    169170
    170171                        return $response_body;
     
    345346         *
    346347         * @since 4.8.0
     348         * @since 5.6.0 Response no longer contains formatted date field. They're added
     349         *              in `wp.communityEvents.populateDynamicEventFields()` now.
    347350         *
    348351         * @return array|false An array containing `location` and `events` items
     
    356359                }
    357360
    358                 return $this->format_event_data_time( $cached_response );
     361                return $cached_response;
    359362        }
    360363
     
    373376         */
    374377        protected function format_event_data_time( $response_body ) {
     378                _deprecated_function(
     379                        __METHOD__,
     380                        '5.6.0',
     381                        'This is no longer used by Core, and only kept for backwards-compatibility.'
     382                );
     383
    375384                if ( isset( $response_body['events'] ) ) {
    376385                        foreach ( $response_body['events'] as $key => $event ) {
  • trunk/src/wp-admin/includes/dashboard.php

    r49123 r49146  
    13901390
    13911391                                <div class="event-date-time">
    1392                                         <span class="event-date">{{ event.formatted_date }}</span>
     1392                                        <span class="event-date">{{ event.user_formatted_date }}</span>
    13931393                                        <# if ( 'meetup' === event.type ) { #>
    1394                                                 <span class="event-time">{{ event.formatted_time }}</span>
     1394                                                <span class="event-time">
     1395                                                        {{ event.user_formatted_time }} {{ event.timeZoneAbbreviation }}
     1396                                                </span>
    13951397                                        <# } #>
    13961398                                </div>
  • trunk/src/wp-includes/script-loader.php

    r49109 r49146  
    13091309                $scripts->set_translations( 'wp-color-picker' );
    13101310
    1311                 $scripts->add( 'dashboard', "/wp-admin/js/dashboard$suffix.js", array( 'jquery', 'admin-comments', 'postbox', 'wp-util', 'wp-a11y' ), false, 1 );
     1311                $scripts->add( 'dashboard', "/wp-admin/js/dashboard$suffix.js", array( 'jquery', 'admin-comments', 'postbox', 'wp-util', 'wp-a11y', 'wp-date' ), false, 1 );
     1312                $scripts->set_translations( 'dashboard' );
    13121313
    13131314                $scripts->add( 'list-revisions', "/wp-includes/js/wp-list-revisions$suffix.js" );
     
    17561757                        'nonce' => wp_create_nonce( 'community_events' ),
    17571758                        'cache' => $events_client->get_cached_events(),
     1759                        'time_format' => get_option( 'time_format' ),
    17581760
    17591761                        'l10n'  => array(
  • trunk/tests/qunit/index.html

    r49107 r49146  
    8686
    8787                <!-- Tested files -->
     88                <script src="../../build/wp-admin/js/dashboard.js"></script>
    8889                <script src="../../build/wp-admin/js/password-strength-meter.js"></script>
     90                <script src="../../build/wp-admin/js/postbox.js"></script>
     91                <script src="../../build/wp-includes/js/dist/vendor/moment.js"></script>
     92                <script src="../../build/wp-includes/js/dist/date.js"></script>
     93                <script src="../../build/wp-includes/js/dist/i18n.js"></script>
    8994                <script src="../../build/wp-includes/js/customize-base.js"></script>
    9095                <script src="../../build/wp-includes/js/customize-models.js"></script>
     
    143148                <script src="wp-admin/js/customize-base.js"></script>
    144149                <script src="wp-admin/js/customize-header.js"></script>
     150                <script src="wp-admin/js/dashboard.js"></script>
    145151                <script src="wp-includes/js/shortcode.js"></script>
    146152                <script src="wp-includes/js/api-request.js"></script>
Note: See TracChangeset for help on using the changeset viewer.