Make WordPress Core

Changeset 49146


Ignore:
Timestamp:
10/14/2020 06:19:43 PM (4 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.