'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
);
+ /**
+ * @since 1.20
+ * @var array
+ */
+ static public $durationIntervals = array(
+ 'millennia' => 31557600000,
+ 'centuries' => 3155760000,
+ 'decades' => 315576000,
+ 'years' => 31557600, // 86400 * 365.25
+ 'weeks' => 604800,
+ 'days' => 86400,
+ 'hours' => 3600,
+ 'minutes' => 60,
+ 'seconds' => 1,
+ );
+
/**
* Get a cached language object for a given language code
* @param $code String
return $this->sprintfDate( $df, $ts );
}
+ /**
+ * Takes a number of seconds and turns it into a text using values such as hours and minutes.
+ *
+ * @since 1.20
+ *
+ * @param integer $seconds The amount of seconds.
+ * @param array $chosenIntervals The intervals to enable.
+ *
+ * @return string
+ */
+ public function formatDuration( $seconds, array $chosenIntervals = array() ) {
+ $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals );
+
+ $segments = array();
+
+ foreach ( $intervals as $intervalName => $intervalValue ) {
+ $message = new Message( 'duration-' . $intervalName, array( $intervalValue ) );
+ $segments[] = $message->inLanguage( $this )->escaped();
+ }
+
+ return $this->listToText( $segments );
+ }
+
+ /**
+ * Takes a number of seconds and returns an array with a set of corresponding intervals.
+ * For example 65 will be turned into array( minutes => 1, seconds => 5 ).
+ *
+ * @since 1.20
+ *
+ * @param integer $seconds The amount of seconds.
+ * @param array $chosenIntervals The intervals to enable.
+ *
+ * @return array
+ */
+ public function getDurationIntervals( $seconds, array $chosenIntervals = array() ) {
+ if ( empty( $chosenIntervals ) ) {
+ $chosenIntervals = array( 'millennia', 'centuries', 'decades', 'years', 'days', 'hours', 'minutes', 'seconds' );
+ }
+
+ $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) );
+ $sortedNames = array_keys( $intervals );
+ $smallestInterval = array_pop( $sortedNames );
+
+ $segments = array();
+
+ foreach ( $intervals as $name => $length ) {
+ $value = floor( $seconds / $length );
+
+ if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) {
+ $seconds -= $value * $length;
+ $segments[$name] = $value;
+ }
+ }
+
+ return $segments;
+ }
+
/**
* Internal helper function for userDate(), userTime() and userTimeAndDate()
*
'directorycreateerror' => 'Could not create directory "$1".',
'filenotfound' => 'Could not find file "$1".',
'fileexistserror' => 'Unable to write to file "$1": File exists.',
-'filereadonlyerror' => 'Unable to modify the file "$1" because the file repository "$2" is in read-only mode.
-
-The administrator who locked it offered this explanation: "$3".',
'unexpected' => 'Unexpected value: "$1"="$2".',
'formerror' => 'Error: Could not submit form.',
'badarticleerror' => 'This action cannot be performed on this page.',
'ns-specialprotected' => 'Special pages cannot be edited.',
'titleprotected' => 'This title has been protected from creation by [[User:$1|$1]].
The reason given is "\'\'$2\'\'".',
+'filereadonlyerror' => 'Unable to the modify the file "$1" because the file repository "$2" is in read-only mode.
+
+The administrator who locked it offered this explanation: "$3".',
# Virus scanner
'virus-badscanner' => "Bad configuration: Unknown virus scanner: ''$1''",
'backend-fail-contenttype' => 'Could not determine the content type of the file to store at "$1".',
'backend-fail-batchsize' => 'Storage backend given a batch of $1 file {{PLURAL:$1|operation|operations}}; the limit is $2 {{PLURAL:$2|operation|operations}}.',
-# File journal
'filejournal-fail-dbconnect' => 'Could not connect to the journal database for storage backend "$1".',
'filejournal-fail-dbquery' => 'Could not update the journal database for storage backend "$1".',
'log-title-wildcard' => 'Search titles starting with this text',
# Special:AllPages
-'allpages' => 'All pages',
-'allpages-summary' => '', # do not translate or duplicate this message to other languages
-'alphaindexline' => '$1 to $2',
-'nextpage' => 'Next page ($1)',
-'prevpage' => 'Previous page ($1)',
-'allpagesfrom' => 'Display pages starting at:',
-'allpagesto' => 'Display pages ending at:',
-'allarticles' => 'All pages',
-'allinnamespace' => 'All pages ($1 namespace)',
-'allnotinnamespace' => 'All pages (not in $1 namespace)',
-'allpagesprev' => 'Previous',
-'allpagesnext' => 'Next',
-'allpagessubmit' => 'Go',
-'allpagesprefix' => 'Display pages with prefix:',
-'allpagesbadtitle' => 'The given page title was invalid or had an inter-language or inter-wiki prefix.
+'allpages' => 'All pages',
+'allpages-summary' => '', # do not translate or duplicate this message to other languages
+'alphaindexline' => '$1 to $2',
+'nextpage' => 'Next page ($1)',
+'prevpage' => 'Previous page ($1)',
+'allpagesfrom' => 'Display pages starting at:',
+'allpagesto' => 'Display pages ending at:',
+'allarticles' => 'All pages',
+'allinnamespace' => 'All pages ($1 namespace)',
+'allnotinnamespace' => 'All pages (not in $1 namespace)',
+'allpagesprev' => 'Previous',
+'allpagesnext' => 'Next',
+'allpagessubmit' => 'Go',
+'allpagesprefix' => 'Display pages with prefix:',
+'allpagesbadtitle' => 'The given page title was invalid or had an inter-language or inter-wiki prefix.
It may contain one or more characters which cannot be used in titles.',
-'allpages-bad-ns' => '{{SITENAME}} does not have namespace "$1".',
+'allpages-bad-ns' => '{{SITENAME}} does not have namespace "$1".',
'allpages-hide-redirects' => 'Hide redirects',
# Special:Categories
'api-error-uploaddisabled' => 'Uploading is disabled on this wiki.',
'api-error-verification-error' => 'This file might be corrupt, or have the wrong extension.',
+# Durations
+'duration-seconds' => '$1 {{PLURAL:$1|second|seconds}}',
+'duration-minutes' => '$1 {{PLURAL:$1|minute|minutes}}',
+'duration-hours' => '$1 {{PLURAL:$1|hour|hours}}',
+'duration-days' => '$1 {{PLURAL:$1|day|days}}',
+'duration-weeks' => '$1 {{PLURAL:$1|week|weeks}}',
+'duration-years' => '$1 {{PLURAL:$1|year|years}}',
+'duration-decades' => '$1 {{PLURAL:$1|decade|decades}}',
+'duration-centuries' => '$1 {{PLURAL:$1|century|centuries}}',
+'duration-millennia' => '$1 {{PLURAL:$1|millennium|millennia}}',
+
);
),
);
}
+
+
+
+ /**
+ * @dataProvider provideFormatDuration
+ */
+ function testFormatDuration( $duration, $expected, $intervals = array() ) {
+ $this->assertEquals(
+ $expected,
+ $this->lang->formatDuration( $duration, $intervals ),
+ "formatDuration('$duration'): $expected"
+ );
+ }
+
+ function provideFormatDuration() {
+ return array(
+ array(
+ 0,
+ '0 seconds',
+ ),
+ array(
+ 1,
+ '1 second',
+ ),
+ array(
+ 2,
+ '2 seconds',
+ ),
+ array(
+ 60,
+ '1 minute',
+ ),
+ array(
+ 2 * 60,
+ '2 minutes',
+ ),
+ array(
+ 3600,
+ '1 hour',
+ ),
+ array(
+ 2 * 3600,
+ '2 hours',
+ ),
+ array(
+ 24 * 3600,
+ '1 day',
+ ),
+ array(
+ 2 * 86400,
+ '2 days',
+ ),
+ array(
+ 365.25 * 86400, // 365.25 * 86400 = 31557600
+ '1 year',
+ ),
+ array(
+ 2 * 31557600,
+ '2 years',
+ ),
+ array(
+ 10 * 31557600,
+ '1 decade',
+ ),
+ array(
+ 20 * 31557600,
+ '2 decades',
+ ),
+ array(
+ 100 * 31557600,
+ '1 century',
+ ),
+ array(
+ 200 * 31557600,
+ '2 centuries',
+ ),
+ array(
+ 1000 * 31557600,
+ '1 millennium',
+ ),
+ array(
+ 2000 * 31557600,
+ '2 millennia',
+ ),
+ array(
+ 9001,
+ '2 hours, 30 minutes and 1 second'
+ ),
+ array(
+ 3601,
+ '1 hour and 1 second'
+ ),
+ array(
+ 31557600 + 2 * 86400 + 9000,
+ '1 year, 2 days, 2 hours and 30 minutes'
+ ),
+ array(
+ 42 * 1000 * 31557600 + 42,
+ '42 millennia and 42 seconds'
+ ),
+ array(
+ 60,
+ '60 seconds',
+ array( 'seconds' ),
+ ),
+ array(
+ 61,
+ '61 seconds',
+ array( 'seconds' ),
+ ),
+ array(
+ 1,
+ '1 second',
+ array( 'seconds' ),
+ ),
+ array(
+ 31557600 + 2 * 86400 + 9000,
+ '1 year, 2 days and 150 minutes',
+ array( 'years', 'days', 'minutes' ),
+ ),
+ array(
+ 42,
+ '0 days',
+ array( 'years', 'days' ),
+ ),
+ array(
+ 31557600 + 2 * 86400 + 9000,
+ '1 year, 2 days and 150 minutes',
+ array( 'minutes', 'days', 'years' ),
+ ),
+ array(
+ 42,
+ '0 days',
+ array( 'days', 'years' ),
+ ),
+ );
+ }
}