Skip to content

Commit

Permalink
Merge pull request #55 from azuyalabs/easter_days
Browse files Browse the repository at this point in the history
Fallback Calculation easter_days (when calendar extension is not loaded)
  • Loading branch information
stelgenhof authored Feb 6, 2017
2 parents c58dbbe + 6d8cc15 commit ee930a1
Show file tree
Hide file tree
Showing 14 changed files with 153 additions and 28 deletions.
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://github.com/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://github.com/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;
}
}

0 comments on commit ee930a1

Please sign in to comment.