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

Fallback Calculation easter_days (when calendar extension is not loaded) #55

Merged
merged 1 commit into from
Feb 6, 2017
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
61 changes: 58 additions & 3 deletions src/Yasumi/Provider/ChristianHolidays.php
Original file line number Diff line number Diff line change
Expand Up @@ -564,9 +564,17 @@ public function reformationDay($year, $timezone, $locale, $type = Holiday::TYPE_
* Easter is a festival and holiday celebrating the resurrection of Jesus Christ from the dead. Easter is celebrated
* on a date based on a certain number of days after March 21st.
*
* This function uses the standard PHP 'easter_days'.
* This function uses the standard PHP 'easter_days' function if the calendar extension is enabled. In case the
* calendar function is not enabled, a fallback calculation has been implemented that is based on the same
* 'easter_days' c function.
*
* @see easter_days
* Note: In calendrical calculations, frequently operations called integer division are used.
*
* @see easter_days
*
* @link https:/php/php-src/blob/c8aa6f3a9a3d2c114d0c5e0c9fdd0a465dbb54a5/ext/calendar/easter.c
* @link http://www.gmarts.org/index.php?go=415#EasterMallen
* @link http://www.tondering.dk/claus/cal/easter.php
*
* @param int $year the year for which Easter needs to be calculated
* @param string $timezone the timezone in which Easter is celebrated
Expand All @@ -575,8 +583,55 @@ public function reformationDay($year, $timezone, $locale, $type = Holiday::TYPE_
*/
protected function calculateEaster($year, $timezone)
{
if (extension_loaded('calendar')) {
$easter_days = \easter_days($year);
} else {
$golden = (int)(($year % 19) + 1); // The Golden Number

// The Julian calendar applies to the original method from 326AD. The Gregorian calendar was first
// introduced in October 1582 in Italy. Easter algorithms using the Gregorian calendar apply to years
// 1583 AD to 4099 (A day adjustment is required in or shortly after 4100 AD).
// After 1752, most western churches have adopted the current algorithm.
if ($year <= 1752) {
$dom = ($year + (int)($year / 4) + 5) % 7; // The 'Dominical number' - finding a Sunday
if ($dom < 0) {
$dom += 7;
}

$pfm = (3 - (11 * $golden) - 7) % 30; // Uncorrected date of the Paschal full moon
if ($pfm < 0) {
$pfm += 30;
}
} else {
$dom = ($year + (int)($year / 4) - (int)($year / 100) + (int)($year / 400)) % 7; // The 'Dominical number' - finding a Sunday
if ($dom < 0) {
$dom += 7;
}

$solar = (int)(($year - 1600) / 100) - (int)(($year - 1600) / 400); // The solar correction
$lunar = (int)(((int)(($year - 1400) / 100) * 8) / 25); // The lunar correction

$pfm = (3 - (11 * $golden) + $solar - $lunar) % 30; // Uncorrected date of the Paschal full moon
if ($pfm < 0) {
$pfm += 30;
}
}

// Corrected date of the Paschal full moon, - days after 21st March
if (($pfm == 29) || ($pfm == 28 && $golden > 11)) {
--$pfm;
}

$tmp = (4 - $pfm - $dom) % 7;
if ($tmp < 0) {
$tmp += 7;
}

$easter_days = (int)($pfm + $tmp + 1); // Easter as the number of days after 21st March
}

$easter = new DateTime("$year-3-21", new DateTimeZone($timezone));
$easter->add(new DateInterval('P' . \easter_days($year) . 'D'));
$easter->add(new DateInterval('P' . $easter_days . 'D'));

return $easter;
}
Expand Down
4 changes: 2 additions & 2 deletions tests/Australia/EasterMondayTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ public function HolidayDataProvider()

for ($y = 0; $y < 50; $y++) {
$year = $this->generateRandomYear();
$date = new DateTime("$year-3-21", new DateTimeZone($this->timezone));
$date->add(new DateInterval('P' . (easter_days($year) + 1) . 'D'));
$date = $this->calculateEaster($year, $this->timezone);
$date->add(new DateInterval('P1D'));

$data[] = [$year, $date->format('Y-m-d')];
}
Expand Down
3 changes: 1 addition & 2 deletions tests/Australia/GoodFridayTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ public function HolidayDataProvider()

for ($y = 0; $y < 50; $y++) {
$year = $this->generateRandomYear();
$date = new DateTime("$year-3-21", new DateTimeZone($this->timezone));
$date->add(new DateInterval('P' . easter_days($year) . 'D'));
$date = $this->calculateEaster($year, $this->timezone);
$date->sub(new DateInterval('P2D'));
$data[] = [$year, $date->format('Y-m-d')];
}
Expand Down
4 changes: 2 additions & 2 deletions tests/Ireland/EasterMondayTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ public function HolidayDataProvider()

for ($y = 0; $y < self::TEST_ITERATIONS; $y++) {
$year = $this->generateRandomYear();
$date = new DateTime("$year-3-21", new DateTimeZone(self::TIMEZONE));
$date->add(new DateInterval('P' . (easter_days($year) + 1) . 'D'));
$date = $this->calculateEaster($year, self::TIMEZONE);
$date->add(new DateInterval('P1D'));

$data[] = [$year, $date->format('Y-m-d')];
}
Expand Down
4 changes: 1 addition & 3 deletions tests/Ireland/EasterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

namespace Yasumi\tests\Ireland;

use DateInterval;
use DateTime;
use DateTimeZone;
use Yasumi\Holiday;
Expand Down Expand Up @@ -53,8 +52,7 @@ public function HolidayDataProvider()

for ($y = 0; $y < self::TEST_ITERATIONS; $y++) {
$year = $this->generateRandomYear();
$date = new DateTime("$year-3-21", new DateTimeZone(self::TIMEZONE));
$date->add(new DateInterval('P' . easter_days($year) . 'D'));
$date = $this->calculateEaster($year, self::TIMEZONE);

$data[] = [$year, $date->format('Y-m-d')];
}
Expand Down
3 changes: 1 addition & 2 deletions tests/Ireland/GoodFridayTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ public function HolidayDataProvider()

for ($y = 0; $y < self::TEST_ITERATIONS; $y++) {
$year = $this->generateRandomYear();
$date = new DateTime("$year-3-21", new DateTimeZone(self::TIMEZONE));
$date->add(new DateInterval('P' . easter_days($year) . 'D'));
$date = $this->calculateEaster($year, self::TIMEZONE);
$date->sub(new DateInterval('P2D'));
$data[] = [$year, $date->format('Y-m-d')];
}
Expand Down
3 changes: 1 addition & 2 deletions tests/Ireland/PentecostTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ public function HolidayDataProvider()
//for ($y = 0; $y < self::TEST_ITERATIONS; $y++) {
for ($y = 0; $y < 2; $y++) {
$year = $this->generateRandomYear();
$date = new DateTime("$year-3-21", new DateTimeZone(self::TIMEZONE));
$date->add(new DateInterval('P' . \easter_days($year) . 'D'));
$date = $this->calculateEaster($year, self::TIMEZONE);
$date->add(new DateInterval('P49D'));
$data[] = [$year, $date->format('Y-m-d')];
}
Expand Down
3 changes: 1 addition & 2 deletions tests/Ireland/pentecostMondayTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ public function HolidayDataProvider()

for ($y = 0; $y < self::TEST_ITERATIONS; $y++) {
$year = $this->generateRandomYear(1000, self::ABOLISHMENT_YEAR);
$date = new DateTime("$year-3-21", new DateTimeZone(self::TIMEZONE));
$date->add(new DateInterval('P' . \easter_days($year) . 'D'));
$date = $this->calculateEaster($year, self::TIMEZONE);
$date->add(new DateInterval('P50D'));
$data[] = [$year, $date->format('Y-m-d')];
}
Expand Down
4 changes: 2 additions & 2 deletions tests/NewZealand/EasterMondayTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ public function HolidayDataProvider()

for ($y = 0; $y < 50; $y++) {
$year = $this->generateRandomYear();
$date = new DateTime("$year-3-21", new DateTimeZone(self::TIMEZONE));
$date->add(new DateInterval('P' . (easter_days($year) + 1) . 'D'));
$date = $this->calculateEaster($year, self::TIMEZONE);
$date->add(new DateInterval('P1D'));

$data[] = [$year, $date->format('Y-m-d')];
}
Expand Down
3 changes: 1 addition & 2 deletions tests/NewZealand/GoodFridayTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ public function HolidayDataProvider()

for ($y = 0; $y < 50; $y++) {
$year = $this->generateRandomYear();
$date = new DateTime("$year-3-21", new DateTimeZone(self::TIMEZONE));
$date->add(new DateInterval('P' . easter_days($year) . 'D'));
$date = $this->calculateEaster($year, self::TIMEZONE);
$date->sub(new DateInterval('P2D'));
$data[] = [$year, $date->format('Y-m-d')];
}
Expand Down
3 changes: 1 addition & 2 deletions tests/SouthAfrica/FamilyDayTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,7 @@ public function HolidayDataProvider()

for ($y = 0; $y < 50; $y++) {
$year = $this->generateRandomYear(self::ESTABLISHMENT_YEAR);
$date = new DateTime("$year-3-21", new DateTimeZone(self::TIMEZONE));
$date->add(new DateInterval('P' . easter_days($year) . 'D'));
$date = $this->calculateEaster($year, self::TIMEZONE);
$date->add(new DateInterval('P1D'));
$data[] = [$year, $date->format('Y-m-d')];
}
Expand Down
3 changes: 1 addition & 2 deletions tests/SouthAfrica/GoodFridayTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,7 @@ public function HolidayDataProvider()

for ($y = 0; $y < 50; $y++) {
$year = $this->generateRandomYear(self::ESTABLISHMENT_YEAR);
$date = new DateTime("$year-3-21", new DateTimeZone(self::TIMEZONE));
$date->add(new DateInterval('P' . easter_days($year) . 'D'));
$date = $this->calculateEaster($year, self::TIMEZONE);
$date->sub(new DateInterval('P2D'));
$data[] = [$year, $date->format('Y-m-d')];
}
Expand Down
4 changes: 2 additions & 2 deletions tests/UnitedKingdom/EasterMondayTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ public function HolidayDataProvider()

for ($y = 0; $y < 50; $y++) {
$year = $this->generateRandomYear();
$date = new DateTime("$year-3-21", new DateTimeZone(self::TIMEZONE));
$date->add(new DateInterval('P' . (easter_days($year) + 1) . 'D'));
$date = $this->calculateEaster($year, self::TIMEZONE);
$date->add(new DateInterval('P1D'));

$data[] = [$year, $date->format('Y-m-d')];
}
Expand Down
79 changes: 79 additions & 0 deletions tests/YasumiBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

namespace Yasumi\tests;

use DateInterval;
use DateTime;
use DateTimeZone;
use Faker\Factory as Faker;
Expand Down Expand Up @@ -219,4 +220,82 @@ public function generateRandomYear($lowerLimit = 1000, $upperLimit = 9999)
{
return (int)Faker::create()->numberBetween($lowerLimit, $upperLimit);
}

/**
* Calculates the date for Easter.
*
* Easter is a festival and holiday celebrating the resurrection of Jesus Christ from the dead. Easter is celebrated
* on a date based on a certain number of days after March 21st.
*
* This function uses the standard PHP 'easter_days' function if the calendar extension is enabled. In case the
* calendar function is not enabled, a fallback calculation has been implemented that is based on the same
* 'easter_days' c function.
*
* Note: In calendrical calculations, frequently operations called integer division are used.
*
* @see easter_days
*
* @link https:/php/php-src/blob/c8aa6f3a9a3d2c114d0c5e0c9fdd0a465dbb54a5/ext/calendar/easter.c
* @link http://www.gmarts.org/index.php?go=415#EasterMallen
* @link http://www.tondering.dk/claus/cal/easter.php
*
* @param int $year the year for which Easter needs to be calculated
* @param string $timezone the timezone in which Easter is celebrated
*
* @return \DateTime date of Easter
*/
protected function calculateEaster($year, $timezone)
{
if (extension_loaded('calendar')) {
$easter_days = \easter_days($year);
} else {
$golden = (int)(($year % 19) + 1); // The Golden Number

// The Julian calendar applies to the original method from 326AD. The Gregorian calendar was first
// introduced in October 1582 in Italy. Easter algorithms using the Gregorian calendar apply to years
// 1583 AD to 4099 (A day adjustment is required in or shortly after 4100 AD).
// After 1752, most western churches have adopted the current algorithm.
if ($year <= 1752) {
$dom = ($year + (int)($year / 4) + 5) % 7; // The 'Dominical number' - finding a Sunday
if ($dom < 0) {
$dom += 7;
}

$pfm = (3 - (11 * $golden) - 7) % 30; // Uncorrected date of the Paschal full moon
if ($pfm < 0) {
$pfm += 30;
}
} else {
$dom = ($year + (int)($year / 4) - (int)($year / 100) + (int)($year / 400)) % 7; // The 'Dominical number' - finding a Sunday
if ($dom < 0) {
$dom += 7;
}

$solar = (int)(($year - 1600) / 100) - (int)(($year - 1600) / 400); // The solar correction
$lunar = (int)(((int)(($year - 1400) / 100) * 8) / 25); // The lunar correction

$pfm = (3 - (11 * $golden) + $solar - $lunar) % 30; // Uncorrected date of the Paschal full moon
if ($pfm < 0) {
$pfm += 30;
}
}

// Corrected date of the Paschal full moon, - days after 21st March
if (($pfm == 29) || ($pfm == 28 && $golden > 11)) {
--$pfm;
}

$tmp = (4 - $pfm - $dom) % 7;
if ($tmp < 0) {
$tmp += 7;
}

$easter_days = (int)($pfm + $tmp + 1); // Easter as the number of days after 21st March
}

$easter = new DateTime("$year-3-21", new DateTimeZone($timezone));
$easter->add(new DateInterval('P' . $easter_days . 'D'));

return $easter;
}
}