Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
62.07% covered (warning)
62.07%
18 / 29
62.50% covered (warning)
62.50%
5 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
EventTimeFormatter
62.07% covered (warning)
62.07%
18 / 29
62.50% covered (warning)
62.50%
5 / 8
17.60
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 formatStart
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 formatEnd
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 formatTimeInternal
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 formatTimezone
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getTimeCorrection
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 wrapRangeForConversion
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 wrapTimeZoneForConversion
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3declare( strict_types=1 );
4
5namespace MediaWiki\Extension\CampaignEvents\Time;
6
7use MediaWiki\Extension\CampaignEvents\Event\EventRegistration;
8use MediaWiki\Extension\CampaignEvents\Utils;
9use MediaWiki\Language\Language;
10use MediaWiki\User\Options\UserOptionsLookup;
11use MediaWiki\User\UserIdentity;
12use MediaWiki\User\UserTimeCorrection;
13use OOUI\HtmlSnippet;
14use OOUI\Tag;
15use Wikimedia\Timestamp\ConvertibleTimestamp;
16
17/**
18 * This service formats the time of an event according to the event type, in a given language and for a given user.
19 * The timezone used for the return value can be obtained separately.
20 */
21class EventTimeFormatter {
22    public const SERVICE_NAME = 'CampaignEventsEventTimeFormatter';
23
24    private UserOptionsLookup $userOptionsLookup;
25
26    private const FORMAT_START = 'start';
27    private const FORMAT_END = 'end';
28
29    /**
30     * @param UserOptionsLookup $userOptionsLookup
31     */
32    public function __construct( UserOptionsLookup $userOptionsLookup ) {
33        $this->userOptionsLookup = $userOptionsLookup;
34    }
35
36    /**
37     * @param EventRegistration $event
38     * @param Language $language
39     * @param UserIdentity $user
40     * @return FormattedTime
41     */
42    public function formatStart(
43        EventRegistration $event,
44        Language $language,
45        UserIdentity $user
46    ): FormattedTime {
47        return $this->formatTimeInternal( self::FORMAT_START, $event, $language, $user );
48    }
49
50    /**
51     * @param EventRegistration $event
52     * @param Language $language
53     * @param UserIdentity $user
54     * @return FormattedTime
55     */
56    public function formatEnd(
57        EventRegistration $event,
58        Language $language,
59        UserIdentity $user
60    ): FormattedTime {
61        return $this->formatTimeInternal( self::FORMAT_END, $event, $language, $user );
62    }
63
64    /**
65     * @param string $type self::FORMAT_START or self::FORMAT_END
66     * @param EventRegistration $event
67     * @param Language $language
68     * @param UserIdentity $user
69     * @return FormattedTime
70     */
71    private function formatTimeInternal(
72        string $type,
73        EventRegistration $event,
74        Language $language,
75        UserIdentity $user
76    ): FormattedTime {
77        $formatOptions = [ 'timecorrection' => $this->getTimeCorrection( $event, $user )->toString() ];
78        $utcTimestamp = $type === self::FORMAT_START ? $event->getStartUTCTimestamp() : $event->getEndUTCTimestamp();
79        return new FormattedTime(
80            $language->userTime( $utcTimestamp, $user, $formatOptions ),
81            $language->userDate( $utcTimestamp, $user, $formatOptions ),
82            $language->userTimeAndDate( $utcTimestamp, $user, $formatOptions )
83        );
84    }
85
86    /**
87     * @param EventRegistration $eventRegistration
88     * @param UserIdentity $user
89     * @return string
90     */
91    public function formatTimezone( EventRegistration $eventRegistration, UserIdentity $user ): string {
92        $userTimeCorrection = $this->getTimeCorrection( $eventRegistration, $user );
93        $tzObj = $userTimeCorrection->getTimeZone();
94        if ( $tzObj ) {
95            return $tzObj->getName();
96        }
97        return UserTimeCorrection::formatTimezoneOffset( $userTimeCorrection->getTimeOffset() );
98    }
99
100    /**
101     * Returns the time correction that should be used when formatting time, date, and timezone.
102     * This uses the event timezone for in-person events, and the user preference for online and hybrid events,
103     * see T316688.
104     *
105     * @param EventRegistration $event
106     * @param UserIdentity $user
107     * @return UserTimeCorrection
108     */
109    private function getTimeCorrection( EventRegistration $event, UserIdentity $user ): UserTimeCorrection {
110        if ( $event->getMeetingType() === EventRegistration::MEETING_TYPE_IN_PERSON ) {
111            return Utils::timezoneToUserTimeCorrection( $event->getTimezone() );
112        }
113        $timeCorrectionPref = $this->userOptionsLookup->getOption( $user, 'timecorrection' ) ?? '';
114        return new UserTimeCorrection( $timeCorrectionPref );
115    }
116
117    /**
118     * Wrap a time range in an HTML structure that can be read by the TimeZoneConverter JavaScript utility.
119     * The timezone must also be wrapped, using {@see self::wrapTimeZoneForConversion}.
120     */
121    public static function wrapRangeForConversion( EventRegistration $event, string $range ): Tag {
122        return ( new Tag( 'span' ) )
123            ->addClasses( [ 'ext-campaignevents-time-range' ] )
124            ->setAttributes( [
125                'data-mw-start' => ConvertibleTimestamp::convert( TS_ISO_8601, $event->getStartUTCTimestamp() ),
126                'data-mw-end' => ConvertibleTimestamp::convert( TS_ISO_8601, $event->getEndUTCTimestamp() ),
127            ] )
128            ->appendContent( $range );
129    }
130
131    /**
132     * Wrap a timezone name in an HTML structure that can be read by the TimeZoneConverter JavaScript utility.
133     * The time range must also be wrapped, using {@see self::wrapRangeForConversion}.
134     */
135    public static function wrapTimeZoneForConversion( string $timezone ): Tag {
136        return ( new Tag( 'span' ) )
137            ->addClasses( [ 'ext-campaignevents-timezone' ] )
138            ->appendContent( new HtmlSnippet( $timezone ) );
139    }
140}