Current File : /home/jvzmxxx/wiki1/extensions/Wikibase/lib/includes/Formatters/MwTimeIsoFormatter.php
<?php

namespace Wikibase\Lib;

use DataValues\TimeValue;
use InvalidArgumentException;
use Language;
use ValueFormatters\FormatterOptions;
use ValueFormatters\ValueFormatter;
use ValueFormatters\ValueFormatterBase;

/**
 * @since 0.4
 *
 * @license GPL-2.0+
 * @author H. Snater < mediawiki@snater.com >
 * @author Addshore
 * @author Thiemo Mättig
 *
 * @todo move me to DataValues-time
 */
class MwTimeIsoFormatter extends ValueFormatterBase {

	/**
	 * @var Language
	 */
	private $language;

	/**
	 * @param FormatterOptions|null $options
	 */
	public function __construct( FormatterOptions $options = null ) {
		parent::__construct( $options );

		$languageCode = $this->getOption( ValueFormatter::OPT_LANG );
		$this->language = Language::factory( $languageCode );
	}

	/**
	 * @see ValueFormatter::format
	 *
	 * @param TimeValue $value
	 *
	 * @throws InvalidArgumentException
	 * @return string Text
	 */
	public function format( $value ) {
		if ( !( $value instanceof TimeValue ) ) {
			throw new InvalidArgumentException( 'Data value type mismatch. Expected a TimeValue.' );
		}

		return $this->formatTimeValue( $value );
	}

	/**
	 * @param TimeValue $timeValue
	 *
	 * @return string Text
	 */
	private function formatTimeValue( TimeValue $timeValue ) {
		$isoTimestamp = $timeValue->getTime();

		try {
			return $this->getLocalizedDate( $isoTimestamp, $timeValue->getPrecision() );
		} catch ( InvalidArgumentException $ex ) {
			return $isoTimestamp;
		}
	}

	/**
	 * @param string $isoTimestamp
	 * @param int $precision
	 *
	 * @throws InvalidArgumentException
	 * @return string Formatted date
	 */
	private function getLocalizedDate( $isoTimestamp, $precision ) {
		$localizedYear = $this->getLocalizedYear( $isoTimestamp, $precision );

		if ( $precision <= TimeValue::PRECISION_YEAR ) {
			return $localizedYear;
		}

		$dateFormat = $this->getDateFormat( $precision );
		$mwTimestamp = $this->getMwTimestamp( $isoTimestamp, $precision );
		$mwYear = $this->language->sprintfDate( 'Y', $mwTimestamp );
		$localizedDate = $this->language->sprintfDate( $dateFormat, $mwTimestamp );

		if ( $mwYear !== $localizedYear ) {
			// If we cannot reliably fix the year, return the full time stamp. This should
			// never happen as Language::sprintfDate should always return a 4 digit year.
			if ( substr_count( $localizedDate, $mwYear ) !== 1 ) {
				throw new InvalidArgumentException( 'Cannot identify year in formatted date.' );
			}

			$localizedDate = str_replace( $mwYear, $localizedYear, $localizedDate );
		}

		return $localizedDate;
	}

	/**
	 * @param int $precision
	 *
	 * @throws InvalidArgumentException
	 * @return string Date format string to be used by Language::sprintfDate
	 */
	private function getDateFormat( $precision ) {
		if ( $precision === TimeValue::PRECISION_MONTH ) {
			$format = $this->language->getDateFormatString( 'monthonly', 'dmy' );
			return sprintf( '%s Y', $this->getMonthFormat( $format ) );
		} elseif ( $precision === TimeValue::PRECISION_DAY ) {
			$format = $this->language->getDateFormatString( 'date', 'dmy' );
			return sprintf( '%s %s Y', $this->getDayFormat( $format ), $this->getMonthFormat( $format ) );
		} else {
			throw new InvalidArgumentException( 'Unsupported precision' );
		}
	}

	/**
	 * @see Language::sprintfDate
	 *
	 * @param string $dateFormat
	 *
	 * @return string A date format for the day that roundtrips the Wikibase TimeParsers.
	 */
	private function getDayFormat( $dateFormat ) {
		if ( preg_match( '/(?:d|(?<!x)j)[.,]?/', $dateFormat, $matches ) ) {
			return $matches[0];
		}

		return 'j';
	}

	/**
	 * @see Language::sprintfDate
	 *
	 * @param string $dateFormat
	 *
	 * @return string A date format for the month that roundtrips the Wikibase TimeParsers.
	 */
	private function getMonthFormat( $dateFormat ) {
		if ( preg_match( '/(?:[FMn]|(?<!x)m|xg)[.,]?/', $dateFormat, $matches ) ) {
			return $matches[0];
		}

		return 'F';
	}

	/**
	 * @param string $isoTimestamp
	 * @param int $precision
	 *
	 * @throws InvalidArgumentException
	 * @return string MediaWiki time stamp in the format YYYYMMDDHHMMSS
	 */
	private function getMwTimestamp( $isoTimestamp, $precision ) {
		$args = $this->splitIsoTimestamp( $isoTimestamp, $precision );

		// Year must be in the range 0000 to 9999 in an MediaWiki time stamp
		$args[0] = substr( $args[0], -4 );
		// Month/day must default to 1 to not get the last day of the previous year/month
		$args[1] = max( 1, $args[1] );
		$args[2] = max( 1, $args[2] );

		return vsprintf( '%04d%02d%02d%02d%02d%02d', $args );
	}

	/**
	 * @param string $isoTimestamp
	 * @param int $precision
	 *
	 * @throws InvalidArgumentException
	 * @return string[] Year, month, day, hour, minute, second
	 */
	private function splitIsoTimestamp( $isoTimestamp, $precision ) {
		if ( !preg_match(
			'/(\d+)\D+(\d+)\D+(\d+)\D+(\d+)\D+(\d+)\D+(\d+)/',
			$isoTimestamp,
			$matches
		) ) {
			throw new InvalidArgumentException( 'Unable to parse time value.' );
		}

		list( , $year, $month, $day ) = $matches;

		if ( $year == 0 && $precision < TimeValue::PRECISION_YEAR
			|| $month == 0 && $precision >= TimeValue::PRECISION_MONTH
			|| $day == 0 && $precision >= TimeValue::PRECISION_DAY
		) {
			throw new InvalidArgumentException( 'Time value insufficient for precision.' );
		}

		return array_slice( $matches, 1 );
	}

	/**
	 * @param string $isoTimestamp
	 * @param int $precision
	 *
	 * @return string
	 */
	private function getLocalizedYear( $isoTimestamp, $precision ) {
		preg_match( '/^(\D*)(\d*)/', $isoTimestamp, $matches );
		list( , $sign, $year ) = $matches;
		$isBCE = $sign === '-';

		$shift = 1;
		$unshift = 1;
		$func = 'round';

		switch ( $precision ) {
			case TimeValue::PRECISION_YEAR1G:
				$msg = 'Gannum';
				$shift = 1e+9;
				break;
			case TimeValue::PRECISION_YEAR100M:
				$msg = 'Mannum';
				$shift = 1e+8;
				$unshift = 1e+2;
				break;
			case TimeValue::PRECISION_YEAR10M:
				$msg = 'Mannum';
				$shift = 1e+7;
				$unshift = 1e+1;
				break;
			case TimeValue::PRECISION_YEAR1M:
				$msg = 'Mannum';
				$shift = 1e+6;
				break;
			case TimeValue::PRECISION_YEAR100K:
				$msg = 'annum';
				$shift = 1e+5;
				$unshift = 1e+5;
				break;
			case TimeValue::PRECISION_YEAR10K:
				$msg = 'annum';
				$shift = 1e+4;
				$unshift = 1e+4;
				break;
			case TimeValue::PRECISION_YEAR1K:
				$msg = 'millennium';
				$func = 'ceil';
				$shift = 1e+3;
				break;
			case TimeValue::PRECISION_YEAR100:
				$msg = 'century';
				$func = 'ceil';
				$shift = 1e+2;
				break;
			case TimeValue::PRECISION_YEAR10:
				$msg = '10annum';
				$func = 'floor';
				$shift = 1e+1;
				$unshift = 1e+1;
				break;
		}

		$shifted = $this->shiftNumber( $year, $func, $shift, $unshift );
		if ( $shifted == 0
			&& ( $precision < TimeValue::PRECISION_YEAR
				|| ( $isBCE && $precision === TimeValue::PRECISION_YEAR )
			)
		) {
			// Year to small for precision, fall back to year.
			$msg = null;
		} else {
			$year = $shifted;
		}

		$year = str_pad( ltrim( $year, '0' ), 1, '0', STR_PAD_LEFT );

		if ( empty( $msg ) ) {
			// TODO: This needs a message.
			return $year . ( $isBCE ? ' BCE' : '' );
		}

		return wfMessage(
			'wikibase-time-precision-' . ( $isBCE ? 'BCE-' : '' ) . $msg,
			$year
		)
		->inLanguage( $this->language )
		->text();
	}

	/**
	 * @param string $number
	 * @param string $function
	 * @param float $shift
	 * @param float $unshift
	 *
	 * @return string
	 */
	private function shiftNumber( $number, $function, $shift, $unshift ) {
		if ( $shift == 1 && $unshift == 1 ) {
			return $number;
		}

		switch ( $function ) {
			case 'ceil':
				$shifted = ceil( $number / $shift ) * $unshift;
				break;
			case 'floor':
				$shifted = floor( $number / $shift ) * $unshift;
				break;
			default:
				$shifted = round( $number / $shift ) * $unshift;
		}

		return sprintf( '%.0f', $shifted );
	}

}