diff options
Diffstat (limited to 'www/wiki/extensions/SemanticMediaWiki/src/DataValues/ValueFormatters/TimeValueFormatter.php')
-rw-r--r-- | www/wiki/extensions/SemanticMediaWiki/src/DataValues/ValueFormatters/TimeValueFormatter.php | 402 |
1 files changed, 402 insertions, 0 deletions
diff --git a/www/wiki/extensions/SemanticMediaWiki/src/DataValues/ValueFormatters/TimeValueFormatter.php b/www/wiki/extensions/SemanticMediaWiki/src/DataValues/ValueFormatters/TimeValueFormatter.php new file mode 100644 index 00000000..40440e91 --- /dev/null +++ b/www/wiki/extensions/SemanticMediaWiki/src/DataValues/ValueFormatters/TimeValueFormatter.php @@ -0,0 +1,402 @@ +<?php + +namespace SMW\DataValues\ValueFormatters; + +use RuntimeException; +use SMW\DataValues\Time\IntlTimeFormatter; +use SMW\Localizer; +use SMWDataValue as DataValue; +use SMWDITime as DITime; +use SMWTimeValue as TimeValue; + +/** + * @license GNU GPL v2+ + * @since 2.4 + * + * @author mwjames + * @author Markus Krötzsch + * @author Fabian Howahl + * @author Terry A. Hurlbut + */ +class TimeValueFormatter extends DataValueFormatter { + + /** + * @since 2.4 + * + * {@inheritDoc} + */ + public function isFormatterFor( DataValue $dataValue ) { + return $dataValue instanceof TimeValue; + } + + /** + * @since 2.4 + * + * {@inheritDoc} + */ + public function format( $type, $linker = null ) { + + if ( !$this->dataValue instanceof TimeValue ) { + throw new RuntimeException( "The formatter is missing a valid TimeValue object" ); + } + + if ( + $this->dataValue->isValid() && + ( $type === self::WIKI_SHORT || $type === self::HTML_SHORT ) ) { + return ( $this->dataValue->getCaption() !== false ) ? $this->dataValue->getCaption() : $this->getPreferredCaption(); + } + + if ( + $this->dataValue->isValid() && + ( $type === self::WIKI_LONG || $type === self::HTML_LONG ) ) { + return $this->getPreferredCaption(); + } + + // #1074 + return $this->dataValue->getCaption() !== false ? $this->dataValue->getCaption() : ''; + } + + /** + * @private + * + * Compute a string representation that largely follows the ISO8601 standard + * of representing dates. Large year numbers may have more than 4 digits, + * which is not strictly conforming to the standard. The date includes year, + * month, and day regardless of the input precision, but will only include + * time when specified. + * + * Conforming to the 2000 version of ISO8601, year 1 BC(E) is represented + * as "0000", year 2 BC(E) as "-0001" and so on. + * + * @since 2.4 + * + * @param DITime $dataItem + * @param boolean $mindefault determining whether values below the + * precision of our input should be completed with minimal or maximal + * conceivable values + * + * @return string + */ + public function getISO8601Date( $mindefault = true ) { + + $dataItem = $this->dataValue->getDataItemForCalendarModel( DITime::CM_GREGORIAN ); + $precision = $dataItem->getPrecision(); + + $result = $dataItem->getYear() > 0 ? '' : '-'; + $result .= str_pad( $dataItem->getYear(), 4, "0", STR_PAD_LEFT ); + + $monthnum = $precision >= DITime::PREC_YM ? $dataItem->getMonth() : ( $mindefault ? 1 : 12 ); + $result .= '-' . str_pad( $monthnum, 2, "0", STR_PAD_LEFT ); + + $day = $dataItem->getDay(); + + if ( !$mindefault && $precision < DITime::PREC_YMD ) { + $day = DITime::getDayNumberForMonth( $monthnum, $dataItem->getYear(), DITime::CM_GREGORIAN ); + } + + $result .= '-' . str_pad( $day, 2, "0", STR_PAD_LEFT ); + + if ( $precision === DITime::PREC_YMDT ) { + $result .= 'T' . $this->getTimeString( ( $mindefault ? '00:00:00' : '23:59:59' ) ); + } + + return $result; + } + + /** + * @private + * + * Use MediaWiki's date and time formatting. It can't handle all inputs + * properly, but has superior i18n support. + * + * @since 2.4 + * + * @param DITime $dataItem + * + * @return string + */ + public function getMediaWikiDate() { + + $dataItem = $this->dataValue->getDataItemForCalendarModel( DITime::CM_GREGORIAN ); + $precision = $dataItem->getPrecision(); + + $language = Localizer::getInstance()->getLanguage( + $this->dataValue->getOption( DataValue::OPT_USER_LANGUAGE ) + ); + + $year = $dataItem->getYear(); + + if ( $year < 0 || $year > 9999 ) { + $year = '0000'; + } + + $year = str_pad( $year, 4, "0", STR_PAD_LEFT ); + + if ( $precision <= DITime::PREC_Y ) { + return $language->formatNum( $year, true ); + } + + $month = str_pad( $dataItem->getMonth(), 2, "0", STR_PAD_LEFT ); + $day = str_pad( $dataItem->getDay(), 2, "0", STR_PAD_LEFT ); + + // date/timeanddate options: + // 1. Whether to adjust the time output according to the user + // configured offset ($timecorrection) + // 2. format, if it's false output the default one (default true) + // 3. timecorrection, the time offset as returned from + // Special:Preferences + + if ( $precision <= DITime::PREC_YMD ) { + return $language->date( "$year$month$day" . '000000', false, true, false ); + } + + $time = str_replace( ':', '', $this->getTimeString() ); + + return $language->timeanddate( "$year$month$day$time", false, true, false ); + } + + /** + * @private + * + * @todo Internationalize the CE and BCE strings. + * + * Compute a suitable string to display the given date item. + * + * @note MediaWiki's date functions are not applicable for the range of + * historic dates we support. + * + * @since 2.4 + * + * @param DITime $dataitem + * + * @return string + */ + public function getCaptionFromDataItem( DITime $dataItem ) { + + // If the language code is empty then the content language code is used + $lang = Localizer::getInstance()->getLang( + Localizer::getInstance()->getContentLanguage() + ); + + // https://en.wikipedia.org/wiki/Anno_Domini + // "...placing the "AD" abbreviation before the year number ... BC is + // placed after the year number (for example: AD 2016, but 68 BC)..." + // Chicago Manual of Style 2010, pp. 476–7; Goldstein 2007, p. 6. + + if ( $dataItem->getYear() > 0 ) { + $cestring = $dataItem->getEra() > 0 ? 'AD' : ''; + $result = ( $cestring ? ( $cestring . ' ' ) : '' ) . number_format( $dataItem->getYear(), 0, '.', '' ); + } else { + $bcestring = 'BC'; + $result = number_format( -( $dataItem->getYear() ), 0, '.', '' ) . ( $bcestring ? ( ' ' . $bcestring ) : '' ); + } + + if ( $dataItem->getPrecision() >= DITime::PREC_YM ) { + $result = $lang->getMonthLabel( $dataItem->getMonth() ) . " " . $result; + } + + if ( $dataItem->getPrecision() >= DITime::PREC_YMD ) { + $result = $dataItem->getDay() . " " . $result; + } + + if ( $dataItem->getPrecision() >= DITime::PREC_YMDT ) { + $result .= " " . $this->getTimeString(); + } + + $result .= $this->hintCalendarModel( $dataItem ); + + return $result; + } + + /** + * @private + * + * Return the time as a string. The time string has the format HH:MM:SS, + * without any timezone information (see class documentation for details + * on current timezone handling). + * The parameter $default optionally specifies the value returned + * if the date is valid but has no explicitly specified time. It can + * also be set to false to detect this situation. + * + * @since 2.4 + * + * @param string $default + * + * @return string + */ + public function getTimeString( $default = '00:00:00' ) { + + $dataItem = $this->dataValue->getDataItemForCalendarModel( DITime::CM_GREGORIAN ); + + if ( $dataItem->getPrecision() < DITime::PREC_YMDT ) { + return $default; + } + + return sprintf( "%02d", $dataItem->getHour() ) . ':' . + sprintf( "%02d", $dataItem->getMinute() ) . ':' . + sprintf( "%02d", $dataItem->getSecond() ); + } + + /** + * @since 2.4 + * + * @param DITime|null $dataItem + * + * @return string + */ + public function getCaptionFromFreeFormat( DITime $dataItem = null ) { + + $language = Localizer::getInstance()->getLanguage( + $this->dataValue->getOption( DataValue::OPT_USER_LANGUAGE ) + ); + + // Prehistory dates are not supported when using this output format + // Only match options encapsulated by [ ... ] + if ( + $dataItem !== null && + $dataItem->getYear() > DITime::PREHISTORY && + preg_match("/\[([^\]]*)\]/", $this->dataValue->getOutputFormat(), $matches ) ) { + $intlTimeFormatter = new IntlTimeFormatter( $dataItem, $language ); + + if ( ( $caption = $intlTimeFormatter->format( $matches[1] ) ) !== false ) { + + if ( $intlTimeFormatter->containsValidDateFormatRule( $matches[1] ) ) { + $caption .= $this->hintCalendarModel( $dataItem ); + } + + return $caption; + } + } + + return $this->getISO8601Date(); + } + + /** + * @private + * + * @since 2.4 + * + * @param DITime|null $dataItem + * + * @return string + */ + public function getLocalizedFormat( DITime $dataItem = null ) { + + if ( $dataItem === null ) { + return ''; + } + + if ( $dataItem->getYear() < DITime::PREHISTORY ) { + return $this->getISO8601Date(); + } + + $outputFormat = $this->dataValue->getOutputFormat(); + $formatFlag = IntlTimeFormatter::LOCL_DEFAULT; + $hasTimeCorrection = false; + + if ( strpos( $outputFormat, 'TO' ) !== false ) { + $formatFlag = IntlTimeFormatter::LOCL_TIMEOFFSET; + $outputFormat = str_replace( '#TO', '', $outputFormat ); + } + + if ( strpos( $outputFormat, 'TZ' ) !== false ) { + $formatFlag = $formatFlag | IntlTimeFormatter::LOCL_TIMEZONE; + $outputFormat = str_replace( '#TZ', '', $outputFormat ); + } + + if ( ( $language = Localizer::getInstance()->getLanguageCodeFrom( $outputFormat ) ) === false ) { + $language = $this->dataValue->getOption( DataValue::OPT_USER_LANGUAGE ); + } + + $language = Localizer::getInstance()->getLanguage( $language ); + + $intlTimeFormatter = new IntlTimeFormatter( + $dataItem, + $language + ); + + // Avoid an exception on "DateTime::__construct(): Failed to parse time + // string (2147483647-01-01 00:00:0.0000000) at position 17 (0): Double + // time specification" for an annotation like [[Date::Jan 10000000000]] + try { + $localizedFormat = $intlTimeFormatter->getLocalizedFormat( $formatFlag ) . + $this->hintTimeCorrection( $intlTimeFormatter->hasLocalTimeCorrection() ) . + $this->hintCalendarModel( $dataItem ); + } catch ( \Exception $e ) { + $localizedFormat = $this->getISO8601Date(); + } + + return $localizedFormat; + } + + /** + * Compute a suitable string to display this date, taking into account the + * output format and the preferable calendar models for the data. + * + * @note MediaWiki's date functions are not applicable for the range + * of historic dates we support. + * + * @return string + */ + protected function getPreferredCaption() { + + $dataItem = $this->dataValue->getDataItem(); + $format = strtoupper( $this->dataValue->getOutputFormat() ); + + if ( $format == 'ISO' || $this->dataValue->getOutputFormat() == '-' ) { + return $this->getISO8601Date(); + } elseif ( $format == 'MEDIAWIKI' ) { + return $this->getMediaWikiDate(); + } elseif ( $format == 'SORTKEY' ) { + return $dataItem->getSortKey(); + } elseif ( $format == 'JD' ) { + return $dataItem->getJD(); + } + + // Does the formatting require calendar conversion? + $model = $dataItem->getCalendarModel(); + + if ( + ( strpos( $format, 'JL' ) !== false ) || + ( $dataItem->getJD() < TimeValue::J1582 && strpos( $format, 'GR' ) === false ) ) { + $model = DITime::CM_JULIAN; + } elseif ( strpos( $format, 'GR' ) !== false ) { + $model = DITime::CM_GREGORIAN; + } + + if ( strpos( $format, '-F[' ) !== false ) { + return $this->getCaptionFromFreeFormat( $this->dataValue->getDataItemForCalendarModel( $model ) ); + } elseif ( strpos( $format, 'LOCL' ) !== false ) { + return $this->getLocalizedFormat( $this->dataValue->getDataItemForCalendarModel( $model ) ); + } elseif ( $dataItem->getYear() > TimeValue::PREHISTORY && $dataItem->getPrecision() >= DITime::PREC_YM ) { + // Do not convert between Gregorian and Julian if only + // year is given (years largely overlap in history, but + // assuming 1 Jan as the default date, the year number + // would change in conversion). + // Also do not convert calendars in prehistory: not + // meaningful (getDataItemForCalendarModel may return null). + return $this->getCaptionFromDataItem( $this->dataValue->getDataItemForCalendarModel( $model ) ); + } + + return $this->getCaptionFromDataItem( $dataItem ); + } + + private function hintTimeCorrection( $hasTimeCorrection ) { + + if ( $hasTimeCorrection ) { + return ' ' . \Html::rawElement( 'sup', [ 'title' => 'ISO: ' . $this->getISO8601Date() ], 'ᴸ' ); + } + + return ''; + } + + private function hintCalendarModel( $dataItem ) { + + if ( $this->dataValue->isEnabledFeature( SMW_DV_TIMEV_CM ) && $dataItem->getCalendarModel() !== DITime::CM_GREGORIAN ) { + return ' ' . \Html::rawElement( 'sup', [], $dataItem->getCalendarModelLiteral() ); + } + + return ''; + } + +} |