Windows 11 64 bit; PHP < 8.3.
This code only yields up to year 2037. I thought the year 2037 was limited only to 32 bit systems. XAMPP v3.30 stops on 2037 but same code online gives 1970 01 January for every year beyond 2037. Thanks.
$year = 2025;
while ($year <= 2050)
{
$easterDate = easter_date($year);
echo $easterYear = date('Y', $easterDate), " ";
echo $easterDay = date('d', $easterDate), " ";
echo $easterMonth = date('F', $easterDate), "<br>";
$year++;
}
Windows 11 64 bit; PHP < 8.3.
This code only yields up to year 2037. I thought the year 2037 was limited only to 32 bit systems. XAMPP v3.30 stops on 2037 but same code online gives 1970 01 January for every year beyond 2037. Thanks.
$year = 2025;
while ($year <= 2050)
{
$easterDate = easter_date($year);
echo $easterYear = date('Y', $easterDate), " ";
echo $easterDay = date('d', $easterDate), " ";
echo $easterMonth = date('F', $easterDate), "<br>";
$year++;
}
Share
Improve this question
edited Jan 19 at 9:57
hakre
198k55 gold badges447 silver badges855 bronze badges
Recognized by PHP Collective
asked Jan 18 at 16:01
Travel2LearnTravel2Learn
192 bronze badges
9
|
Show 4 more comments
3 Answers
Reset to default 2The changelog of
the easter_date()
function says:
8.3.0 On 64-bit systems, the year parameter now accepts values within the range of 1970 to 2,000,000,000.
That means that, before PHP 8.3, the year was limited to 2037 even for 64-bit versions of PHP.
To understand why, let's look at easter.c from PHP 8.2:
if (gm && (year<1970 || year>2037)) { /* out of range for timestamps */
zend_argument_value_error(1, "must be between 1970 and 2037 (inclusive)");
RETURN_THROWS();
}
This check is always done (32 and 64 bits).
Now in easter.c from PHP 8.3:
#ifdef ZEND_ENABLE_ZVAL_LONG64
/* Compiling for 64bit, allow years between 1970 and 2.000.000.000 */
if (gm && year < 1970) {
/* timestamps only start after 1970 */
zend_argument_value_error(1, "must be a year after 1970 (inclusive)");
RETURN_THROWS();
}
if (gm && year > 2000000000) {
/* timestamps only go up to the year 2.000.000.000 */
zend_argument_value_error(1, "must be a year before 2.000.000.000 (inclusive)");
RETURN_THROWS();
}
#else
/* Compiling for 32bit, allow years between 1970 and 2037 */
if (gm && (year < 1970 || year > 2037)) {
zend_argument_value_error(1, "must be between 1970 and 2037 (inclusive)");
RETURN_THROWS();
}
#endif
We see that the check is now different for 32 and 64-bit versions.
I thought the year 2037 was limited only to 32 bit systems.
Yes, by the timestamp (integer), but also no: The function itself can be limited to the years 1970 and 2037 (inclusive).
For example, as Honk der Hase has revealed in a comment, they only got it to work with a year greater 2037 on PHP 8.3.
This perhaps does not come as a surprise when we look at this change set in php-src (license):
Commit 8ef0e4c Arne1303 authored on Aug 4, 2023:
Allow easter_date to process years after 2037 on 64bit systems (#11862)
Added a check to easter_date to allow it to run with years past 2037 when on a 64bit platform.
It was released with PHP 8.3 and the pull request gives more insights of the motivation about the change:
Until now, easter_date always threw an error if executed with a year after 2037. This was done to prevent an integer overflow with Unix timestamps in the years after 2038 from appearing. (Y2K38 bug)
Since the overflow does not happen with signed 64-bit integers, I added a check to allow easter_date to run on 64bit systems with years after 2037.
This is again written by Arne1303, so they confirm your thoughts that actually on a 32bit system it was necessary but on a 64bit system it isn't any longer.
Understanding this, may also help to understand more why that is/was and why it seems to not be that clear from the documentation/integer size of the system.
What PHP actually did was not implementing that functionality fully but delegated to a system function. You can probably say in the spirit of not re-inventing the wheel. This is documented as well on the manual page.
Now this has exactly that caveat: If the underlying system function does not have the ability to support the years you are looking for (let's say what has been understood as the 32bit behaviour), then the PHP function will not as well.
As it is then the system function that is leading it depends on the concrete implementation there. And this is the caveat: While we may expect that on a 64bit system it would support 2038+, this must not technically be the case, because it depends on the concrete system in use and which libraries have been bound there. E.g. it may still be possible that while running on 64bit system a 32bit library is bound. Who would do that you may ask yourself and we probably would not recommend that but better trust the terrain than the map.
Now this leads to another problem: As time goes by, more and more systems moved from 32bit architectures to 64bit. So the PHP source can only be after that. Unless adopted, as Arne1303 did, it will remain with that (now perhaps received outdated) behaviour.
So better document with your question the PHP version as well, as such things can then be more easily revealed.
The "map" has some guidance though: The PHP manual for that function notes about the limitation even it may not fully explain it to all its internal depths (those you'll find in the php-src repository), and gives a pointer what to do if you run into this limitation, e.g. which other function to use in the see also section (bold by me):
- easter_days() - Get number of days after March 21 on which Easter falls for a given year for calculating Easter before 1970 or after 2037
You have found it already as you have linked it in your question, so this "See also" probably has already done its work.
And note that it has been removed in many PHP >= 5.4 configurations (ref: https://3v4l./Jfvi6) - but less than what Jim would consider many.
At the end of the day, it is probably noteworthy that whenever you rely on system capabilities, it becomes more and more important that within your development environment you better match the production environment as close as possible and document the parts that don't (probably within code as you can handle this by wrapping inside a function). It is also then when integration testing with the production configuration comes at play for fun and profit.
Effective tests are done when you test with the values around the boundaries, here the years:
# | Lower | Upper |
---|---|---|
-2 | 1968 | 2035 |
-1 | 1969 | 2036 |
0 | 1970 | 2037 |
+1 | 1971 | 2038 |
+2 | 1972 | 2039 |
If you pick +/- 2 you commonly also catch off-by-one errors either in your own or third party parts.
I would not rely on easter_date()
as it has a lot of caveats. If you look at the notes in the documentation, it shows a better way that uses PHP's built-in date function to calculate Easter for a given year and allows going beyond 2037 since it doesn't rely on unix timestamps. The easter_days()
function which returns the numbers of days Easter is from March 21:
function get_easter_datetime($year) {
$base = new DateTime("$year-03-21");
$days = easter_days($year);
return $base->add(new DateInterval("P{$days}D"));
}
foreach (range(2012, 2153) as $year) {
printf("Easter in %d is on %s\n",
$year,
get_easter_datetime($year)->format('F j'));
}
Outputs:
Easter in 2012 is on April 8
Easter in 2013 is on March 31
Easter in 2014 is on April 20
...
Easter in 2151 is on April 4
Easter in 2152 is on April 23
Easter in 2153 is on April 15
php -r 'echo (PHP_INT_SIZE===8)?"64 bit ":"32 bit ";'
(run it from CLI) – Honk der Hase Commented Jan 18 at 17:08var_dump($easterDate)
.. php docu says, it returns a UNIX timestamp, but on 64bit system I would expect a value exceeding the 32 bit.date()
however might only consider the 32bit part... so you might be fooled by date() :) Try feeding the $easterDate to a DateTime object and see what it gives you – Honk der Hase Commented Jan 18 at 21:54