Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Account for different handling of historical dates by ICU and Posix #5

Merged
merged 1 commit into from
Apr 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/php-8.1-strftime.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use DateTimeInterface;
use Exception;
use IntlDateFormatter;
use IntlGregorianCalendar;
use InvalidArgumentException;

/**
Expand Down Expand Up @@ -85,7 +86,18 @@ function strftime (string $format, $timestamp = null, ?string $locale = null) :
$pattern = $intl_formats[$format];
}

return (new IntlDateFormatter($locale, $date_type, $time_type, $tz, null, $pattern))->format($timestamp);
// In October 1582, the Gregorian calendar replaced the Julian in much of Europe, and
// the 4th October was followed by the 15th October.
// ICU (including IntlDateFormattter) interprets and formats dates based on this cutover.
// Posix (including strftime) and timelib (including DateTimeImmutable) instead use
// a "proleptic Gregorian calendar" - they pretend the Gregorian calendar has existed forever.
// This leads to the same instants in time, as expressed in Unix time, having different representations
// in formatted strings.
// To adjust for this, a custom calendar can be supplied with a cutover date arbitrarily far in the past.
$calendar = IntlGregorianCalendar::createInstance();
$calendar->setGregorianChange(PHP_INT_MIN);

return (new IntlDateFormatter($locale, $date_type, $time_type, $tz, $calendar, $pattern))->format($timestamp);
};

// Same order as https://www.php.net/manual/en/function.strftime.php
Expand Down
26 changes: 26 additions & 0 deletions tests/strftimeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -176,4 +176,30 @@ public function testLocale () {
$result = strftime('%B', '20220306 13:02:03', 'eu');
$this->assertEquals('martxoa', $result, '%B: Full month name, based on the locale');
}

/**
* In October 1582, the Gregorian calendar replaced the Julian in much of Europe, and
* the 4th October was followed by the 15th October.
* ICU (including IntlDateFormattter) interprets and formats dates based on this cutover.
* Posix (including strftime) and timelib (including DateTimeImmutable) instead use
* a "proleptic Gregorian calendar" - they pretend the Gregorian calendar has existed forever.
* This leads to the same instants in time, as expressed in Unix time, having different representations
* in formatted strings.
*/
public function testJulianCutover () {
// 1st October 1582 in proleptic Gregorian is the same date as 21st September 1582 Julian
$prolepticTimestamp = DateTimeImmutable::createFromFormat('Y-m-d|', '1582-10-01')->getTimestamp();
$result = strftime('%x', $prolepticTimestamp, 'eu');
$this->assertEquals('82/10/1', $result);

// In much of Europe, the 10th October 1582 never existed
$prolepticTimestamp = DateTimeImmutable::createFromFormat('Y-m-d|', '1582-10-10')->getTimestamp();
$result = strftime('%x', $prolepticTimestamp, 'eu');
$this->assertEquals('82/10/10', $result);

// The 15th October was the first day after the cutover, after which both systems agree
$prolepticTimestamp = DateTimeImmutable::createFromFormat('Y-m-d|', '1582-10-15')->getTimestamp();
$result = strftime('%x', $prolepticTimestamp, 'eu');
$this->assertEquals('82/10/15', $result);
}
}