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

Extend calculations beyond the year 9999 #196

Open
JohnRDOrazio opened this issue Apr 13, 2024 · 4 comments
Open

Extend calculations beyond the year 9999 #196

JohnRDOrazio opened this issue Apr 13, 2024 · 4 comments
Milestone

Comments

@JohnRDOrazio
Copy link
Member

The current limit for dates within the range of the year 9999 is due to ISO8601's limit of a range between 0000-9999.

However PHP does seem to have a way to go beyond this range:

DateTimeInterface::ISO8601_EXPANDED
DATE_ISO8601_EXPANDED
ISO-8601 Expanded (example: +10191-07-26T08:59:52+01:00)
**Note**: This format allows for year ranges outside of ISO-8601's normal range of 0000-9999 by always including a sign character. It also addresses that that timezone part (+01:00) is compatible with ISO-8601.

Perhaps we could implement this interface, and allow for years that go far beyond 9999. Making this calculator as close to truly perpetual as is currently possible by common computing standards.

@JohnRDOrazio JohnRDOrazio added this to the v4.0 milestone Apr 13, 2024
@JohnRDOrazio JohnRDOrazio removed this from the v4.0 milestone Sep 7, 2024
@JohnRDOrazio
Copy link
Member Author

actually it would seem that PHP can handle dates / timestamps beyond the year 9999, it's ISO-8601 that defines a year to have exactly 4 digits, so ISO-8601 cannot represent a date beyond the year 9999.
There shouldn't be any trouble calculating years beyond 9999, the only difficulty might be in representing them in string format, and that's where DateTimeInterface::ISO8601_EXPANDED can help.

@JohnRDOrazio
Copy link
Member Author

This will work on a 64 bit PHP:

<?php
$date = new DateTime();
$date->setTimestamp(569941488000);
echo $date->format(DATE_ISO8601_EXPANDED);
//OUTPUT: +20030-09-23T00:00:00+00:00

However, we can only set the precalculated timestamp for a date. We cannot use any of the DateTime::createFromFormat functions. Which makes this almost impossible to implement, because in order to calculate the timestamp correctly we would have to calculate the seconds in a year for all years taking into account leap years... It starts to be a bit of a mess.

This is a way of doing so, say we want to calculate the timestamp for the date May 11th 20030:

<?php
function countLeapYears($start_year, $end_year) {
    return floor($end_year / 4) - floor(($start_year - 1) / 4)
         - floor($end_year / 100) + floor(($start_year - 1) / 100)
         + floor($end_year / 400) - floor(($start_year - 1) / 400);
}

$baseTimestamp = strtotime('1970-05-11 00:00:00 UTC');

// Calculate the number of seconds from 1970 to 20030
$yearsDifference = 20030 - 1970;
$secondsPerDay = 24 * 60 * 60;
$secondsPerYear = 365 * $secondsPerDay; // Year length in seconds
$additionalSeconds = $yearsDifference * $secondsPerYear;
$leapYears = countLeapYears(1970, 20030);
$additionalSeconds += ($leapYears * $secondsPerDay); // account for leap years

// Add the additional seconds to the base timestamp
$futureTimestamp = $baseTimestamp + $additionalSeconds;

$date = new DateTime();
$date->setTimestamp($futureTimestamp);
echo $date->format(DATE_ISO8601_EXPANDED);
//OUTPUT: +20030-05-11T00:00:00+00:00

@JohnRDOrazio
Copy link
Member Author

JohnRDOrazio commented Jan 15, 2025

We can still do date calculations, and the IntlDateFormatter actually can handle dates beyond the year 9999 without any trouble, this works just fine:

<?php
function countLeapYears($start_year, $end_year) {
    return floor($end_year / 4) - floor(($start_year - 1) / 4)
         - floor($end_year / 100) + floor(($start_year - 1) / 100)
         + floor($end_year / 400) - floor(($start_year - 1) / 400);
}

$baseTimestamp = strtotime('1970-05-11 00:00:00 UTC');

// Calculate the number of seconds from 1970 to 20030
$yearsDifference = 20030 - 1970;
$secondsPerDay = 24 * 60 * 60;
$secondsPerYear = 365 * $secondsPerDay; // Year length in seconds
$additionalSeconds = $yearsDifference * $secondsPerYear;
$leapYears = countLeapYears(1970, 20030);
$additionalSeconds += ($leapYears * $secondsPerDay); // account for leap years

// Add the additional seconds to the base timestamp
$futureTimestamp = $baseTimestamp + $additionalSeconds;

$date = new DateTime();
$date->setTimestamp($futureTimestamp);
$date->modify('+1 day');
echo $date->format(DATE_ISO8601_EXPANDED); // OUTPUT the desired date plus one day

$formatter = IntlDateFormatter::create(
    'it_IT',
    IntlDateFormatter::FULL,
    IntlDateFormatter::NONE,
    'UTC'
);
echo PHP_EOL;
echo $formatter->format($futureTimestamp); // OUPUT the desired date in a specified locale

OUTPUT:

+20030-05-12T00:00:00+00:00
sabato 11 maggio 20030

@JohnRDOrazio
Copy link
Member Author

The only drawback would be when we are validating schemas, timestamps would be fine but RFC formatted dates would not get validated correctly. We would just have to change our schemas to accept an alternative type, validated with regex for example...

@JohnRDOrazio JohnRDOrazio added this to the v5.0 milestone Jan 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant