diff --git a/i18n/en.json b/i18n/en.json index 1ebc3ce..2d41424 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -17,11 +17,30 @@ "edtf-month": "month", "edtf-year": "year", + "edtf-year-unspecified": "some year", + "edtf-spring": "Spring", "edtf-summer": "Summer", "edtf-autumn": "Autumn", "edtf-winter": "Winter", + "edtf-date-unspecified": "some", + "edtf-date-BC": "BC", + + "edtf-decade": "{{PLURAL:$1|decade|decades}}", + "edtf-century": "{{PLURAL:$1|century|centuries}}", + "edtf-millennium": "{{PLURAL:$1|millennium|millennia}}", + "edtf-decem-millennium": "decem {{PLURAL:$1|millennium|millennia}}", + "edtf-hundreds-of-thousands": "{{PLURAL:$1|hundred|hundreds}} of thousands", + "edtf-million": "{{PLURAL:$1|million|millions}}", + "edtf-tens-of-millions": "{{PLURAL:$1|ten|tens}} of millions", + "edtf-hundreds-of-millions": "{{PLURAL:$1|hundred|hundreds}} of millions", + "edtf-billion": "{{PLURAL:$1|billion|billions}}", + "edtf-tens-of-billions": "{{PLURAL:$1|ten|tens}} of billions", + "edtf-hundreds-of-billions": "{{PLURAL:$1|hundred|hundreds}} of billions", + "edtf-thousands-of-billions": "{{PLURAL:$1|thousand|thousands}} of billions", + "edtf-trillion": "{{PLURAL:$1|trillion|trillions}}", + "edtf-spring-north": "Spring (Northern Hemisphere)", "edtf-summer-north": "Summer (Northern Hemisphere)", "edtf-autumn-north": "Autumn (Northern Hemisphere)", diff --git a/src/Model/ExtDate.php b/src/Model/ExtDate.php index 74ff27d..f5292d8 100644 --- a/src/Model/ExtDate.php +++ b/src/Model/ExtDate.php @@ -175,6 +175,27 @@ private function resolveMaxDay( $year, $month ): int { return null === $this->day ? $lastDayOfMonth : $this->day; } + public function getUnspecifiedYearScale() : int { + if ( $this->unspecifiedDigit->unspecified( 'year' ) ) { + $ret = $this->unspecifiedDigit->getYear(); + + if ( $this->getYear() === 0 ) { + return $ret; + } + + return $ret + 1; + } + return 0; + } + + public function getSpecifiedYears() : int { + return $this->getYear() / ( pow( 10, $this->unspecifiedDigit->getYear() ) ); + } + + public function isBC(): bool { + return $this->unspecifiedDigit->isBC(); + } + /** * This function is applicable for 2-digits placeholders (month, day). * Means that decimal: 0 < n < 10 diff --git a/src/Model/UnspecifiedDigit.php b/src/Model/UnspecifiedDigit.php index 50ec03f..1745ce1 100644 --- a/src/Model/UnspecifiedDigit.php +++ b/src/Model/UnspecifiedDigit.php @@ -76,6 +76,10 @@ public function getDay(): int { return $this->day; } + public function isBC(): bool { + return ( (int)str_replace( "X", "1", $this->rawYear ) < 0 ); + } + public function century(): bool { if ( $this->year == 2 && substr( $this->rawYear, -2 ) == "XX" ) { return true; @@ -91,4 +95,4 @@ public function decade(): bool { return false; } -} \ No newline at end of file +} diff --git a/src/PackagePrivate/Humanizer/InternationalizedHumanizer.php b/src/PackagePrivate/Humanizer/InternationalizedHumanizer.php index 66add18..da7aad2 100644 --- a/src/PackagePrivate/Humanizer/InternationalizedHumanizer.php +++ b/src/PackagePrivate/Humanizer/InternationalizedHumanizer.php @@ -215,10 +215,7 @@ private function humanizeDateWithoutUncertainty( ExtDate $date ): string { $day = $date->getDay(); if ( $year !== null ) { - $year = $this->humanizeYear( - $year, - $date->getUnspecifiedDigit() - ); + $year = $this->humanizeYear( $year, $date ); } if ( $month !== null ) { @@ -247,7 +244,55 @@ private function humanizeYearMonthDay( ?string $year, ?string $month, ?string $d ); } - private function humanizeYear( int $year, UnspecifiedDigit $unspecifiedDigit ): string { + private function scaleToMessageKey( int $scale ): string { + switch( $scale ) { + case 1 : return 'edtf-year'; // X + case 2 : return 'edtf-decade'; // XX + case 3 : return 'edtf-century'; // XXX + case 4 : return 'edtf-millennium'; // XXXX + case 5 : return 'edtf-decem-millennium'; // XXXXX + case 6 : return 'edtf-hundreds-of-thousands'; // XXXXXX + case 7 : return 'edtf-million'; // XXXXXXX + case 8 : return 'edtf-tens-of-millions'; // XXXXXXXX + case 9 : return 'edtf-hundreds-of-millions'; // XXXXXXXXX + case 10 : return 'edtf-billion'; // XXXXXXXXXX + case 11 : return 'edtf-tens-of-billions'; // XXXXXXXXXXX + case 12 : return 'edtf-hundreds-of-billions'; // XXXXXXXXXXXX + case 13 : return 'edtf-trillion'; // XXXXXXXXXXXXX + } + + // FIXME: reuse recursively the scale with trillions + // e.g. tens-of-trillions etc., + return 'edtf-tens-of-trillions'; + } + + private function humanizeYear( int $year, ExtDate $date ): string { + $unspecifiedYearScale = $date->getUnspecifiedYearScale(); + $unspecifiedDigit = $date->getUnspecifiedDigit(); + $specifiedYears = $date->getSpecifiedYears(); + + if ( $unspecifiedYearScale === 0 || + ( $this->needsYearEndingChar( $unspecifiedDigit ) && $specifiedYears !== 0 ) ) { + return $this->humanizeYearSpecified( $year, $unspecifiedDigit ); + } + + $specifiedYearsStr = (string)abs( $specifiedYears ); + + $ret = ( $specifiedYears === 0 && $unspecifiedYearScale != 0 ? $this->message( "edtf-date-unspecified" ) + : $specifiedYearsStr ); + + if ( $unspecifiedYearScale > 0 ) { + $ret .= " " . $this->message( $this->scaleToMessageKey( $unspecifiedYearScale ), $specifiedYearsStr ); + } + + if ( $date->isBC() ) { + $ret .= " " . $this->message( "edtf-date-BC" ); + } + + return $ret; + } + + private function humanizeYearSpecified( int $year, UnspecifiedDigit $unspecifiedDigit ): string { $yearStr = (string)abs( $year ); if ( $year <= -1000 ) { diff --git a/src/PackagePrivate/Parser/RegexMatchesMapper.php b/src/PackagePrivate/Parser/RegexMatchesMapper.php index 15e6fae..a768f79 100644 --- a/src/PackagePrivate/Parser/RegexMatchesMapper.php +++ b/src/PackagePrivate/Parser/RegexMatchesMapper.php @@ -42,6 +42,8 @@ private function mapDate( array $rawDateMatches ): Date { ); } + // FIXME: ensure that -XXXXXXX4 or -XXXXX4XX throws an error + // see https://github.com/ProfessionalWiki/EDTF/pull/88 private function prepareNumValue( string $str ): ?int { $value = (int)str_replace( 'X', '0', $str ); return $value !== 0 ? $value : null; @@ -89,4 +91,4 @@ private function regroupMatches( array $matches ): array { return $regrouped; } -} \ No newline at end of file +} diff --git a/tests/Functional/EnglishHumanizationTest.php b/tests/Functional/EnglishHumanizationTest.php index 7135520..635ef61 100644 --- a/tests/Functional/EnglishHumanizationTest.php +++ b/tests/Functional/EnglishHumanizationTest.php @@ -33,6 +33,26 @@ public function humanizationProvider(): Generator { yield 'Month only' => [ 'XXXX-12-XX', 'December' ]; yield 'Day only' => [ 'XXXX-XX-12', '12th' ]; + + // https://github.com/ProfessionalWiki/EDTF/issues/80 + yield 'Some year (4 digits)' => [ 'XXXX', 'some millennium' ]; + + yield 'Some year (1 digit)' => [ 'X', 'some year' ]; + yield 'Some year (2 digit)' => [ 'XX', 'some decade' ]; + yield 'Some year (3 digits)' => [ 'XXX', 'some century' ]; + yield 'Scales (4 digits minus)' => [ '-5XXXX', '5 decem millennia BC' ]; + yield 'Scales (5 digits)' => [ '5XXXXX', '5 hundreds of thousands' ]; + yield 'Scales (6 digits)' => [ '5XXXXXX', '5 millions' ]; + yield 'Scales (7 digits)' => [ '5XXXXXXX', '5 tens of millions' ]; + yield 'Scales (8 digits)' => [ '5XXXXXXXX', '5 hundreds of millions' ]; + yield 'Scales (9 digits)' => [ '5XXXXXXXXX', '5 billions' ]; + yield 'Scales (10 digits)' => [ '5XXXXXXXXXX', '5 tens of billions' ]; + yield 'Scales (11 digits)' => [ '5XXXXXXXXXXX', '5 hundreds of billions' ]; + + // TODO throw error + // yield 'Scales (throw error 1)' => [ 'XXXXXXXXXX4', '' ]; + // yield 'Scales (throw error 1)' => [ 'XXXXXX4XXXX', '' ]; + yield 'Month and day' => [ 'XXXX-12-11', 'December 11th' ]; yield 'Year and day' => [ '2020-XX-11', '11th of unknown month, 2020' ]; yield 'Unspecified year decade' => [ '197X', '1970s' ];