Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
76.56% covered (warning)
76.56%
49 / 64
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
TimeUnits
76.56% covered (warning)
76.56%
49 / 64
0.00% covered (danger)
0.00%
0 / 3
25.15
0.00% covered (danger)
0.00%
0 / 1
 getUnits
33.33% covered (danger)
33.33%
3 / 9
0.00% covered (danger)
0.00%
0 / 1
8.74
 loadLanguage
85.71% covered (warning)
85.71%
18 / 21
0.00% covered (danger)
0.00%
0 / 1
7.14
 onGetHumanTimestamp
82.35% covered (warning)
82.35%
28 / 34
0.00% covered (danger)
0.00%
0 / 1
9.45
1<?php
2
3namespace MediaWiki\Extension\CLDR;
4
5use Language;
6use MediaWiki\MediaWikiServices;
7use MWTimestamp;
8use User;
9
10/**
11 * A class for querying translated time units from CLDR data.
12 *
13 * @author Niklas Laxström
14 * @author Ryan Kaldari
15 * @copyright Copyright © 2007-2013
16 * @license GPL-2.0-or-later
17 */
18class TimeUnits {
19
20    private static $cache = [];
21
22    /**
23     * Get localized time units for a particular language, using fallback languages for missing
24     * items. The time units are returned as an associative array. The keys are of the form:
25     * <unit>-<tense>-<ordinality> (for example, 'hour-future-two'). The values include a placeholder
26     * for the number (for example, '{0} months ago').
27     *
28     * @param string $code The language to return the list in
29     * @return array an associative array of time unit codes and localized time units
30     */
31    public static function getUnits( $code ) {
32        // Load time units localized for the requested language
33        $units = self::loadLanguage( $code );
34
35        if ( $units ) {
36            return $units;
37        }
38        // Load missing time units from fallback languages
39        $fallbacks = MediaWikiServices::getInstance()->getLanguageFallback()->getAll( $code );
40        foreach ( $fallbacks as $fallback ) {
41            if ( $units ) {
42                break;
43            }
44            // Get time units from a fallback language
45            $units = self::loadLanguage( $fallback );
46        }
47
48        return $units;
49    }
50
51    /**
52     * Load time units localized for a particular language. Helper function for getUnits.
53     *
54     * @param string $code The language to return the list in
55     * @return array an associative array of time unit codes and localized time units
56     */
57    private static function loadLanguage( $code ) {
58        if ( !isset( self::$cache[$code] ) ) {
59            self::$cache[$code] = [];
60
61            $langNameUtils = MediaWikiServices::getInstance()->getLanguageNameUtils();
62
63            if ( !$langNameUtils->isValidBuiltInCode( $code ) ) {
64                return [];
65            }
66
67            /* Load override for wrong or missing entries in cldr */
68            $override = __DIR__ . '/../LocalNames/' .
69                $langNameUtils->getFileName( 'LocalNames', $code, '.php' );
70            if ( file_exists( $override ) ) {
71                $timeUnits = false;
72
73                require $override;
74
75                // @phan-suppress-next-line PhanImpossibleCondition
76                if ( is_array( $timeUnits ) ) {
77                    self::$cache[$code] = $timeUnits;
78                }
79            }
80
81            $filename = __DIR__ . '/../CldrNames/' .
82                $langNameUtils->getFileName( 'CldrNames', $code, '.php' );
83            if ( file_exists( $filename ) ) {
84                $timeUnits = false;
85                require $filename;
86                // @phan-suppress-next-line PhanImpossibleCondition
87                if ( is_array( $timeUnits ) ) {
88                    self::$cache[$code] += $timeUnits;
89                }
90            } else {
91                wfDebug( __METHOD__ . ": Unable to load time units for $filename\n" );
92            }
93        }
94
95        return self::$cache[$code];
96    }
97
98    /**
99     * Handler for GetHumanTimestamp hook.
100     * Converts the given time into a human-friendly relative format, for
101     * example, '6 days ago', 'In 10 months'.
102     *
103     * @param string &$output The output timestamp
104     * @param MWTimestamp $timestamp The current (user-adjusted) timestamp
105     * @param MWTimestamp $relativeTo The relative (user-adjusted) timestamp
106     * @param User $user User whose preferences are being used to make timestamp
107     * @param Language $lang Language that will be used to render the timestamp
108     * @return bool False means the timestamp was overridden so stop further
109     *     processing. True means the timestamp was not overridden.
110     */
111    public static function onGetHumanTimestamp( &$output, $timestamp, $relativeTo, $user, $lang ) {
112        // Map PHP's DateInterval property codes to CLDR unit names.
113        $units = [
114            's' => 'second',
115            'i' => 'minute',
116            'h' => 'hour',
117            'd' => 'day',
118            'm' => 'month',
119            'y' => 'year',
120        ];
121
122        // Get the difference between the two timestamps (as a DateInterval object).
123        $timeDifference = $timestamp->diff( $relativeTo );
124
125        // Figure out if the timestamp is in the future or the past.
126        if ( $timeDifference->invert ) {
127            $tense = 'future';
128        } else {
129            $tense = 'past';
130        }
131
132        // Figure out which unit (days, months, etc.) it makes sense to display
133        // the timestamp in, and get the number of that unit to use.
134        $unit = null;
135        $number = 0;
136        foreach ( $units as $code => $testUnit ) {
137            $testNumber = (int)$timeDifference->format( '%' . $code );
138            if ( $testNumber > 0 ) {
139                $unit = $testUnit;
140                $number = $testNumber;
141            }
142        }
143
144        // If it occurred less than 1 second ago, output 'just now' message.
145        if ( !$unit || !$number ) {
146            $output = wfMessage( 'just-now' )->inLanguage( $lang )->text();
147            return false;
148        }
149
150        // Get the CLDR time unit strings for the user's language.
151        // If no strings are returned, abandon the timestamp override.
152        $timeUnits = self::getUnits( $lang->getCode() );
153        if ( !$timeUnits ) {
154            return true;
155        }
156
157        // Figure out which grammatical number to use.
158        // If the template doesn't exist, fall back to 'other' as the default.
159        $grammaticalNumber = $lang->getPluralRuleType( $number );
160        $timeUnitKey = "{$unit}-{$tense}-{$grammaticalNumber}";
161        if ( !isset( $timeUnits[$timeUnitKey] ) ) {
162            $timeUnitKey = "{$unit}-{$tense}-other";
163        }
164
165        // Not all languages have translations for everything
166        if ( !isset( $timeUnits[$timeUnitKey] ) ) {
167            return true;
168        }
169
170        // Select the appropriate template for the timestamp.
171        $timeUnit = $timeUnits[$timeUnitKey];
172        // Replace the placeholder with the number.
173        $output = str_replace( '{0}', $lang->formatNum( $number ), $timeUnit );
174
175        return false;
176    }
177}