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

Add Moroccan Holidays #56

Merged
merged 6 commits into from
Jun 13, 2024
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
19 changes: 19 additions & 0 deletions lang/morocco/ar/holidays.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"New Year\\'s Day": "رأس السنة الميلادية",
"Proclamation of Independence Day": "تقديم وثيقة الاستقلال",
"Amazigh New Year (ⵉⴹ ⵏ ⵢⵉⵏⵏⴰⵢⵔ)": "رأس السنة الأمازيغية",
"Labour Day": "عيد الشغل",
"Throne Day": "عيد العرش",
"Oued Ed-Dahab Day": "استرجاع إقليم وادي الذهب",
"Revolution Day": "ثورة الملك والشعب",
"Youth Day": "عيد الشباب",
"Green March": "ذكرى المسيرة الخضراء",
"Independence Day": "عيد الاستقلال",
"Islamic New Year": "رأس السنة الهجرية",
"Birthday of the Prophet Muhammad": "عيد المولد النبوي",
"Birthday of the Prophet Muhammad 2": "تاني ايام عيد المولد النبوي",
"Eid al-Fitr": "عيد الفطر",
"Eid al-Fitr 2": "تاني ايام عيد الفطر",
"Eid al-Adha": "عيد الأضحى",
"Eid al-Adha 2": "تاني ايام عيد الأضحى"
}
153 changes: 153 additions & 0 deletions src/Countries/Morocco.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?php

namespace Spatie\Holidays\Countries;

use Carbon\CarbonImmutable;
use Spatie\Holidays\Concerns\Translatable;
use Spatie\Holidays\Contracts\HasTranslations;
use Spatie\Holidays\Exceptions\InvalidYear;

class Morocco extends Country implements HasTranslations
{
use Translatable;

public function countryCode(): string
{
return 'ma';
}

public function defaultLocale(): string
{
return 'en';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be Arabic by default?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanx for the review

In fact, I assumed that the Package would be used globally and therefore set English as the default language.

}

protected function allHolidays(int $year): array
{
return array_merge([
'New Year\'s Day' => '01-01',
'Proclamation of Independence Day' => '01-11',
'Amazigh New Year (ⵉⴹ ⵏ ⵢⵉⵏⵏⴰⵢⵔ)' => '01-14',
'Labour Day' => '05-01',
'Throne Day' => '07-30',
'Oued Ed-Dahab Day' => '08-14',
'Revolution Day' => '08-20',
'Youth Day' => '08-21',
'Green March' => '11-06',
'Independence Day' => '11-18',
], $this->variableHolidays($year));
}

/** @return array<string, CarbonImmutable> */
protected function variableHolidays(int $year): array
{
// Calculate the current Hijri year based on the Gregorian year
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can have a look at other Islamic countries and how their lunar based holidays are resolved. For example: Turkey, Bahrein and Egypt.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reviewed the other contributions, and it seems they've hardcoded the expected dates of Islamic holidays. Personally, I don't find that approach ideal. Instead, I opted to use a date converter for more dynamism. While it may not be 100% accurate, it offers flexibility. If you still prefer the hardcoded holiday expectation dates, I'm willing to make the change tonight.

$currentHijriYear = 1444 + ($year - 2022);

/**
* The following holidays are considered public holidays in Morocco. However, their dates vary each year,
* as they are based on the Islamic Hijri (lunar) calendar. These holidays do not have a fixed date and
* occur based on the lunar calendar sequence. The order listed reflects the chronological occurrence
* of these holidays throughout the year.
*/

// Define Islamic holidays on the Hijri calendar
$islamicHolidaysOnHijri = [
'Islamic New Year' => '01-01',
'Birthday of the Prophet Muhammad' => '03-12',
'Birthday of the Prophet Muhammad 2' => '03-13',
'Eid al-Fitr' => '10-01',
'Eid al-Fitr 2' => '10-02',
'Eid al-Adha' => '12-10',
'Eid al-Adha 2' => '12-11',
];

$islamicHolidaysOnGregorian = [];
// Convert Hijri dates to Gregorian and filter based on the input year
foreach ($islamicHolidaysOnHijri as $holidayTitle => $hijriHolidayDate) {
[$hijriHolidayMonth, $hijriHolidayDay] = explode('-', $hijriHolidayDate);
$vlideYear = null;

$GregorianDate = $this->islamicToGregorian($currentHijriYear, (int) $hijriHolidayMonth, (int) $hijriHolidayDay);
$vlideYear = $GregorianDate['year'];
$tempCurrentHijriYear = $currentHijriYear;
while ($vlideYear != $year) {
// Convert the current Hijri holiday to Gregorian
$GregorianDate = $this->islamicToGregorian($tempCurrentHijriYear--, (int) $hijriHolidayMonth, (int) $hijriHolidayDay);
$vlideYear = $GregorianDate['year'];
if ($vlideYear < 1976) {
throw InvalidYear::yearTooLow(1976);
}
}
// Store the Gregorian date of the Islamic holiday
$islamicHolidaysOnGregorian[$holidayTitle] = CarbonImmutable::createFromFormat('Y-m-d', sprintf('%s-%s-%s', $GregorianDate['year'], $GregorianDate['month'], $GregorianDate['day']));
}

return $islamicHolidaysOnGregorian;
}

/**
* Converts a Hijri date to the corresponding Gregorian date.
* This function is adapted from the conversion tool used on the Moroccan
* Minister of Endowments and Islamic Affairs official website.
* https://www.habous.gov.ma/محول-التاريخ
*
* @param int $y The Hijri year.
* @param int $m The Hijri month.
* @param int $d The Hijri day.
* @return array{year: int, month: int, day: int} An array containing the corresponding Gregorian date in the format ['year' => YYYY, 'month' => MM, 'day' => DD].
*/
private function islamicToGregorian(int $y, int $m, int $d): array
{
$delta = 0;
$jd = $this->intPart((11 * $y + 3) / 30) + 354 * $y + 30 * $m - $this->intPart(($m - 1) / 2) + $d + 1948440 - 385 + $delta;
if ($jd > 2299160) {
$l = $jd + 68569;
$n = $this->intPart((4 * $l) / 146097);
$l = $l - $this->intPart((146097 * $n + 3) / 4);
$i = $this->intPart((4000 * ($l + 1)) / 1461001);
$l = $l - $this->intPart((1461 * $i) / 4) + 31;
$j = $this->intPart((80 * $l) / 2447);
$d = $l - $this->intPart((2447 * $j) / 80);
$l = $this->intPart($j / 11);
$m = $j + 2 - 12 * $l;
$y = 100 * ($n - 49) + $i + $l;
} else {
$j = $jd + 1402;
$k = $this->intPart(($j - 1) / 1461);
$l = $j - 1461 * $k;
$n = $this->intPart(($l - 1) / 365) - $this->intPart($l / 1461);
$i = $l - 365 * $n + 30;
$j = $this->intPart((80 * $i) / 2447);
$d = $i - $this->intPart((2447 * $j) / 80);
$i = $this->intPart($j / 11);
$m = $j + 2 - 12 * $i;
$y = 4 * $k + $n + $i - 4716;
}

return [
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do I have the right to use geniusts/hijri-dates to convert Date convert Gregorian date to Hijri date and vice versa ?
i will try to test it today to check how much it's accurate

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a necessary feature to not exclude half the holidays from Muslim countries

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not really familiar with this. Could you shortly explain what this package would do and how we could benefit from it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The geniusts/hijri-dates package is a reliable PHP library designed to facilitate the conversion between Gregorian and Hijri dates. In our case, it can be incredibly useful for handling Islamic holidays that are based on the Hijri calendar.

The Islamic calendar follows a lunar system, which means that its months and years are determined by the moon's phases. This leads to variations in the lengths of months and the occurrence of Islamic holidays. By integrating this package, we gain the ability to accurately convert dates between the Gregorian and Hijri calendars.

I am currently in the process of testing the package to ensure its efficacy. I have a slight doubt about its accuracy, but my friend from the Maldives has used it in his PR#63. I will conduct thorough testing today to evaluate its performance and accuracy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Nielsvanpach,

I utilized the geniusts/hijri-dates package to compute Islamic holidays for Morocco. I have included tests for the years 2023, 2024, 2025, and 2026. The results match the date converter provided by the Minister of Religious Endowments and Islamic Affairs.

Please note that it's not possible to predict Islamic holidays with 100% certainty. Ministry observers use the naked-eye observation of the New Moon at the end of each lunar month to determine if the current month is 29 or 30 days, impacting the declaration of the new month.

Copy link
Member

@Nielsvanpach Nielsvanpach Jan 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for that! Just for clarity; do countries with Islamic public holidays all have the same dates for them? So is Eid al-Fitr celebrated everywhere on the same day, or are there exceptions?

If so, is it possible to not limit this to only Morocco, but make the calculations accessible for all countries with Islamic holidays? I would prefer a single implementation for those calculations instead of it being scattered all around the codebase for different countries.

Copy link

@medilies medilies Jan 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Nielsvanpach It is common for Islamic countries but the phase of the moon is not the same everywhere; For example Eid Al-Fitr may occur on the 10th of April for Algeria and on the 11th of April for Morocco.

Also, the duration of days off may differ between countries, Some may take a week and some may take 2 days.

Btw, this #90 is also making use of the same package.

Copy link
Contributor Author

@YazidKHALDI YazidKHALDI Jan 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Nielsvanpach

The topic is more intricate than it seems, and there are ongoing debates in Islamic countries. There are two main differences between countries:

  1. Observation Methods: Not all countries share the same date due to differences of approximately ±1 day. This is because each country employs its own method to observe the new moon, whether through naked-eye observation, telescopes, or astronomical calculations. Some countries find it impossible to determine the new moon using the naked eye.

    As an illustration, consider the current Hijri month and the next one. If you check the Hijri date today in all Islamic countries, it will be 09/07/1445. To understand why they all have the same date, check this image, representing the visibility of the moon on the first day of the current Hijri month "Rajab" (source).

    In this image, all countries easily observe the new moon with the naked eye, leading them to declare the 13th of January 2024 as the first day of the month "Rajab."

    Now, observe the visibility of the moon on the first day of the next Hijri month in this image (source).

    You can see that only the countries of West Africa (Morocco, Algeria, etc.) can easily observe the new moon. They declare the 11th of February 2024 as the first day of the month "Shaban." However, Egypt and other Middle Eastern countries may face challenges in seeing the new moon. Consequently, some will declare February 11, 2024, as the first day based on astronomical calculations, while others will wait until February 12, 2024.

    To address these differences, I used an adjustment of -1 day to make the calculations more accurate for Morocco.

    Hijri::setDefaultAdjustment(-1);
  2. Holiday Duration: Another difference is the number of holidays and the number of days taken as holidays. For example, "Eid al-Fitr" is observed for just 2 days in Morocco, but it's 3 days in Algeria and 4 days in Saudi Arabia.

In conclusion, having the same date for all countries is challenging, and achieving 100% accuracy in calculations is difficult because each country has its own protocol for the observation of the new moon.

PS: I fixed the PHPStan failure.

'year' => (int) $y,
'month' => (int) $m,
'day' => (int) $d,
];
}

/**
* Rounds a floating-point number to the nearest integer.
* If the floating-point number is negative, it uses ceil function.
* If the floating-point number is positive, it uses floor function.
*
* @param float $floatNum The floating-point number to be rounded.
* @return float The rounded integer value.
*/
private function intPart($floatNum)
{
// Check if the floating-point number is negative
if ($floatNum < -0.0000001) {
// If negative, round up using ceil
return ceil($floatNum - 0.0000001);
}

// If positive or zero, round down using floor
return floor($floatNum + 0.0000001);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
[
{
"name": "New Year's Day",
"date": "2023-01-01"
},
{
"name": "Proclamation of Independence Day",
"date": "2023-01-11"
},
{
"name": "Amazigh New Year (\u2d49\u2d39 \u2d4f \u2d62\u2d49\u2d4f\u2d4f\u2d30\u2d62\u2d54)",
"date": "2023-01-14"
},
{
"name": "Eid al-Fitr",
"date": "2023-04-22"
},
{
"name": "Eid al-Fitr 2",
"date": "2023-04-23"
},
{
"name": "Labour Day",
"date": "2023-05-01"
},
{
"name": "Eid al-Adha",
"date": "2023-06-29"
},
{
"name": "Eid al-Adha 2",
"date": "2023-06-30"
},
{
"name": "Islamic New Year",
"date": "2023-07-19"
},
{
"name": "Throne Day",
"date": "2023-07-30"
},
{
"name": "Oued Ed-Dahab Day",
"date": "2023-08-14"
},
{
"name": "Revolution Day",
"date": "2023-08-20"
},
{
"name": "Youth Day",
"date": "2023-08-21"
},
{
"name": "Birthday of the Prophet Muhammad",
"date": "2023-09-27"
},
{
"name": "Birthday of the Prophet Muhammad 2",
"date": "2023-09-28"
},
{
"name": "Green March",
"date": "2023-11-06"
},
{
"name": "Independence Day",
"date": "2023-11-18"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
[
{
"name": "New Year's Day",
"date": "2024-01-01"
},
{
"name": "Proclamation of Independence Day",
"date": "2024-01-11"
},
{
"name": "Amazigh New Year (\u2d49\u2d39 \u2d4f \u2d62\u2d49\u2d4f\u2d4f\u2d30\u2d62\u2d54)",
"date": "2024-01-14"
},
{
"name": "Eid al-Fitr",
"date": "2024-04-10"
},
{
"name": "Eid al-Fitr 2",
"date": "2024-04-11"
},
{
"name": "Labour Day",
"date": "2024-05-01"
},
{
"name": "Eid al-Adha",
"date": "2024-06-17"
},
{
"name": "Eid al-Adha 2",
"date": "2024-06-18"
},
{
"name": "Islamic New Year",
"date": "2024-07-08"
},
{
"name": "Throne Day",
"date": "2024-07-30"
},
{
"name": "Oued Ed-Dahab Day",
"date": "2024-08-14"
},
{
"name": "Revolution Day",
"date": "2024-08-20"
},
{
"name": "Youth Day",
"date": "2024-08-21"
},
{
"name": "Birthday of the Prophet Muhammad",
"date": "2024-09-16"
},
{
"name": "Birthday of the Prophet Muhammad 2",
"date": "2024-09-17"
},
{
"name": "Green March",
"date": "2024-11-06"
},
{
"name": "Independence Day",
"date": "2024-11-18"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
[
{
"name": "New Year's Day",
"date": "2025-01-01"
},
{
"name": "Proclamation of Independence Day",
"date": "2025-01-11"
},
{
"name": "Amazigh New Year (\u2d49\u2d39 \u2d4f \u2d62\u2d49\u2d4f\u2d4f\u2d30\u2d62\u2d54)",
"date": "2025-01-14"
},
{
"name": "Eid al-Fitr",
"date": "2025-03-31"
},
{
"name": "Eid al-Fitr 2",
"date": "2025-04-01"
},
{
"name": "Labour Day",
"date": "2025-05-01"
},
{
"name": "Eid al-Adha",
"date": "2025-06-07"
},
{
"name": "Eid al-Adha 2",
"date": "2025-06-08"
},
{
"name": "Islamic New Year",
"date": "2025-06-27"
},
{
"name": "Throne Day",
"date": "2025-07-30"
},
{
"name": "Oued Ed-Dahab Day",
"date": "2025-08-14"
},
{
"name": "Revolution Day",
"date": "2025-08-20"
},
{
"name": "Youth Day",
"date": "2025-08-21"
},
{
"name": "Birthday of the Prophet Muhammad",
"date": "2025-09-05"
},
{
"name": "Birthday of the Prophet Muhammad 2",
"date": "2025-09-06"
},
{
"name": "Green March",
"date": "2025-11-06"
},
{
"name": "Independence Day",
"date": "2025-11-18"
}
]
Loading