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

Refactor/islamic calendar #212

Closed
wants to merge 11 commits into from
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
}
],
"require": {
"php": "^8.1",
"php": "^8.2",
"ext-intl": "*",
"nesbot/carbon": "^2.72.1|^3.0"
},
Expand Down
28 changes: 28 additions & 0 deletions lang/turkey/tr/holidays.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"New Year's Day": "Yılbaşı",
"Eid al-Fitr Eve": "Ramazan Bayramı Arifesi",
"Eid al-Fitr": "Ramazan Bayramı 1. Gün",
"Eid al-Fitr Day 2": "Ramazan Bayramı 2. Gün",
"Eid al-Fitr Day 3": "Ramazan Bayramı 3. Gün",
"National Sovereignty and Children's Day": "Ulusal Egemenlik ve Çocuk Bayramı",
"Labor and Solidarity Day": "Emek ve Dayanışma Günü",
"Commemoration of Atatürk, Youth and Sports Day": "Atatürk'ü Anma, Gençlik ve Spor Bayramı",
"Eid al-Adha Eve": "Kurban Bayramı Arifesi",
"Eid al-Adha": "Kurban Bayramı 1. Gün",
"Eid al-Adha Day 2": "Kurban Bayramı 2. Gün",
"Eid al-Adha Day 3": "Kurban Bayramı 3. Gün",
"Eid al-Adha Day 4": "Kurban Bayramı 4. Gün",
"Democracy and National Unity Day": "Demokrasi ve Millî Birlik Günü",
"Victory Day": "Zafer Bayramı",
"Republic Day Eve": "Cumhuriyet Bayramı Arifesi",
"Republic Day": "Cumhuriyet Bayramı",
"2. Eid al-Fitr Eve": "2. Ramazan Bayramı Arifesi",
"2. Eid al-Fitr": "2. Ramazan Bayramı 1. Gün",
"2. Eid al-Fitr Day 2": "2. Ramazan Bayramı 2. Gün",
"2. Eid al-Fitr Day 3": "2. Ramazan Bayramı 3. Gün",
"2. Eid al-Adha Eve": "2. Kurban Bayramı Arifesi",
"2. Eid al-Adha": "2. Kurban Bayramı 1. Gün",
"2. Eid al-Adha Day 2": "2. Kurban Bayramı 2. Gün",
"2. Eid al-Adha Day 3": "2. Kurban Bayramı 3. Gün",
"2. Eid al-Adha Day 4": "2. Kurban Bayramı 4. Gün"
}
80 changes: 80 additions & 0 deletions src/Calendars/IslamicCalendar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

namespace Spatie\Holidays\Calendars;

use Carbon\CarbonImmutable;
use Carbon\CarbonPeriod;
use Spatie\Holidays\Countries\Country;
use Spatie\Holidays\Exceptions\InvalidYear;

/** @mixin Country */
trait IslamicCalendar
{
/** @return CarbonPeriod|array<CarbonPeriod> */
public function eidAlFitr(int $year, int $totalDays = 3): CarbonPeriod|array
{
return $this->getHoliday(self::eidAlFitr, $year, $totalDays);
}

/** @return CarbonPeriod|array<CarbonPeriod> */
public function eidAlAdha(int $year, int $totalDays = 4): CarbonPeriod|array
{
return $this->getHoliday(self::eidAlAdha, $year, $totalDays);
}

protected function getHoliday(array $collection, int $year, int $totalDays): CarbonPeriod|array
{
try {
$date = $collection[$year];
} catch (\Exception) {
throw InvalidYear::range($this->countryCode(), 1970, 2037);
}

$overlap = $this->getOverlapping(self::eidAlFitr, $year, $totalDays);

if ($overlap) {
$date = [$date, $overlap];
}

if (! is_array($date)) {
$start = CarbonImmutable::createFromFormat('Y-m-d', "{$year}-{$date}")->startOfDay();
$end = $start->addDays($totalDays - 1)->startOfDay();

return CarbonPeriod::create($start, '1 day', $end);
}

// Twice a year
$periods = [];
$dates = $date;

foreach ($dates as $date) {
$start = CarbonImmutable::createFromFormat('Y-m-d', "{$year}-{$date}")->startOfDay();
$end = $start->addDays($totalDays - 1)->startOfDay();
$periods[] = CarbonPeriod::create($start, '1 day', $end);
}

return $periods;
}

protected function getOverlapping(array $collection, int $year, $totalDays): ?string
{
try {
$date = $collection[$year - 1];
} catch (\Exception) {
throw InvalidYear::range($this->countryCode(), 1970, 2037);
}

if (is_array($date)) {
$date = end($date);
}

$start = CarbonImmutable::createFromFormat('Y-m-d', "{$year}-{$date}")->startOfDay();
$end = $start->addDays($totalDays - 1)->startOfDay();

if ($end->year !== $year) {
return $date;
}

return null;
}
}
15 changes: 4 additions & 11 deletions src/Concerns/Translatable.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,19 @@ trait Translatable
{
public function translate(string $country, string $name, ?string $locale = null): string
{
if ($locale === null) {
return $name;

}

if ($locale === $this->defaultLocale()) {
return $name;
}

$locale = $locale ?? $this->defaultLocale();

$countryName = strtolower($country);
$filePath = __DIR__."/../../lang/{$countryName}/{$locale}/holidays.json";

if (file_exists($filePath)) {
$content = file_get_contents($filePath);
} else {
throw InvalidLocale::notFound($country, $locale);
return $name;
}

if ($content === false) {
Expand All @@ -33,10 +30,6 @@ public function translate(string $country, string $name, ?string $locale = null)
/** @var array<string, string> $data */
$data = json_decode($content, true);

if (! isset($data[$name])) {
return $name;
}

return $data[$name];
return $data[$name] ?? $name;
}
}
12 changes: 12 additions & 0 deletions src/Contracts/Islamic.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Spatie\Holidays\Contracts;

use Carbon\CarbonPeriod;

interface Islamic
{
public function islamicHolidays(int $year): array;

public function eidAlFitr(int $year): CarbonPeriod|array;
}
84 changes: 37 additions & 47 deletions src/Countries/Bahrain.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@

use Carbon\CarbonImmutable;
use Carbon\CarbonInterface;
use Carbon\CarbonPeriod;
use RuntimeException;
use Spatie\Holidays\Calendars\IslamicCalendar;
use Spatie\Holidays\Concerns\Translatable;
use Spatie\Holidays\Contracts\HasTranslations;
use Spatie\Holidays\Exceptions\InvalidYear;

class Bahrain extends Country implements HasTranslations
{
use IslamicCalendar;
use Translatable;

protected const EID_AL_FITR_HOLIDAYS = [
protected const eidAlFitr = [
2020 => '05-24',
2021 => '05-13',
2022 => '05-02',
Expand All @@ -33,7 +37,7 @@ class Bahrain extends Country implements HasTranslations
2037 => '11-09',
];

protected const ARAFAT_DAY_HOLIDAYS = [
protected const arafatDay = [
2020 => '07-30',
2021 => '07-19',
2022 => '07-09',
Expand All @@ -54,7 +58,7 @@ class Bahrain extends Country implements HasTranslations
2037 => '01-26',
];

protected const EID_AL_ADHA_HOLIDAYS = [
protected const eidAlAdha = [
2020 => '07-31',
2021 => '07-20',
2022 => '07-09',
Expand All @@ -75,7 +79,7 @@ class Bahrain extends Country implements HasTranslations
2037 => '01-27',
];

protected const ISLAMIC_NEW_YEAR_HOLIDAYS = [
protected const islamicNewYear = [
2020 => '08-20',
2021 => '08-09',
2022 => '07-30',
Expand All @@ -96,7 +100,7 @@ class Bahrain extends Country implements HasTranslations
2037 => '02-17',
];

protected const ASHURA_HOLIDAYS = [
protected const ashura = [
2020 => '08-30',
2021 => '08-19',
2022 => '08-08',
Expand All @@ -117,7 +121,7 @@ class Bahrain extends Country implements HasTranslations
2037 => '02-25',
];

protected const PROPHET_MUHAMMAD_BIRTHDAY_HOLIDAYS = [
protected const prophetMuhammadBirthday = [
2020 => '10-29',
2021 => '10-21',
2022 => '10-08',
Expand Down Expand Up @@ -166,56 +170,42 @@ protected function allHolidays(int $year): array
protected function variableHolidays(int $year): array
{
$holidays = [
['EID_AL_FITR_HOLIDAYS', 'Eid al-Fitr', 3],
['EID_AL_ADHA_HOLIDAYS', 'Eid al-Adha', 3],
['ARAFAT_DAY_HOLIDAYS', 'Arafat Day'],
['ISLAMIC_NEW_YEAR_HOLIDAYS', 'Islamic New Year'],
['ASHURA_HOLIDAYS', 'Ashura', 2],
['PROPHET_MUHAMMAD_BIRTHDAY_HOLIDAYS', 'Birthday of the Prophet Muhammad'],
'Eid al-Fitr' => $this->eidAlFitr($year),
'Eid al-Adha' => $this->eidAlAdha($year),
'Arafat Day' => self::arafatDay[$year],
'Islamic New Year' => self::islamicNewYear[$year],
'Ashura' => $this->ashura($year),
'Birthday of the Prophet Muhammad' => self::prophetMuhammadBirthday[$year],
];

$dates = [];
foreach ($holidays as $holiday) {
$dates = array_merge($dates, $this->getIslamicHolidayDatesForYear(constant('self::'.$holiday[0]), $year, $holiday[1], $holiday[2] ?? 1));
}

return $dates;
return $this->convertPeriods($holidays, $year);
}

/**
* Prepare holiday dates for the given year.
*
* @param array<int, string> $holidayDates Array mapping years to dates.
* @param int $year The year for which to prepare holiday dates.
* @param string $holidayName The name of the holiday.
* @param int $duration The duration of the holiday in days.
* @return array<string, CarbonImmutable> An array of holiday dates.
*/
private function getIslamicHolidayDatesForYear(array $holidayDates, int $year, string $holidayName, int $duration = 1): array
protected function eidAlAdha(int $year): CarbonPeriod
{
$dates = [];

if ($year < 2020) {
throw InvalidYear::yearTooLow(2020);
try {
$date = self::eidAlAdha[$year];
} catch (RuntimeException) {
throw InvalidYear::range($this->countryCode(), 1970, 2037);
}

if (! isset($holidayDates[$year])) {
return $dates;
}
$start = CarbonImmutable::createFromFormat('Y-m-d', "{$year}-{$date}");
$end = $start->addDays(2);

return CarbonPeriod::create($start, '1 day', $end);
}

$startDay = CarbonImmutable::createFromFormat('Y-m-d', sprintf('%s-%s', $year, $holidayDates[$year]));

if ($duration === 1) {
// For single-day holidays, use the holiday name without "Day"
$dates[$holidayName] = $startDay;
} else {
// For multi-day holidays, append "Day N" for the second day onwards
for ($i = 0; $i < $duration; $i++) {
$dayLabel = $i === 0 ? $holidayName : sprintf('%s Day %d', $holidayName, $i + 1);
$dates[$dayLabel] = $startDay->addDays($i);
}
protected function ashura(int $year): CarbonPeriod
{
try {
$date = self::ashura[$year];
} catch (RuntimeException) {
throw InvalidYear::range($this->countryCode(), 1970, 2037);
}

return $dates;
$start = CarbonImmutable::createFromFormat('Y-m-d', "{$year}-{$date}");
$end = $start->addDay();

return CarbonPeriod::create($start, '1 day', $end);
}
}
36 changes: 36 additions & 0 deletions src/Countries/Country.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
namespace Spatie\Holidays\Countries;

use Carbon\CarbonImmutable;
use Carbon\CarbonInterface;
use Carbon\CarbonPeriod;
use Spatie\Holidays\Contracts\HasTranslations;
use Spatie\Holidays\Exceptions\InvalidCountry;
use Spatie\Holidays\Exceptions\InvalidYear;
Expand Down Expand Up @@ -120,4 +122,38 @@ protected function ensureYearCanBeCalculated(int $year): void
throw InvalidYear::yearTooHigh(2038);
}
}

protected function convertPeriods(
array $holidays,
int $year,
string $suffix = 'Day',
string $prefix = ''
): array {
$result = [];

foreach ($holidays as $name => $holiday) {
if ($holiday instanceof CarbonPeriod) {
/** @var CarbonInterface $day */
foreach ($holiday as $index => $day) {
if ($day->year !== $year) {
continue;
}

if ($index === 0) {
$formattedSuffix = '';
} else {
$formattedSuffix = " {$suffix} ".$index + 1;
}

$holidayName = "{$prefix}{$name}{$formattedSuffix}";

$result[$holidayName] = $day->toImmutable();
}
} else {
$result[$name] = $holiday;
}
}

return $result;
}
}
Loading