diff --git a/lang/vietnam/en/holidays.json b/lang/vietnam/en/holidays.json new file mode 100644 index 000000000..b074514b0 --- /dev/null +++ b/lang/vietnam/en/holidays.json @@ -0,0 +1,16 @@ +{ + "Tết Dương Lịch":"New Year's Day", + "Ngày Hai Mươi Chín Tết":"12-29 the previous year (in Chinese Calendar)", + "Ngày Ba Mươi Tết":"Lunar New Year's Eve (12-30 in Chinese Calendar)", + "Mùng Một Tết Âm Lịch":"Lunar New Year Day 1", + "Mùng Hai Tết Âm Lịch":"Lunar New Year Day 2", + "Mùng Ba Tết Âm Lịch":"Lunar New Year Day 3", + "Mùng Bốn Tết Âm Lịch":"Lunar New Year Day 4", + "Mùng Năm Tết Âm Lịch":"Lunar New Year Day 5", + "Ngày Giỗ Tổ Hùng Vương":"Hung Kings' Festival", + "Ngày Giải Phóng Miền Nam, Thống Nhất Đất Nước":"Day of Southern Liberation and National Reunification", + "Ngày Quốc Tế Lao Động":"Labour Day", + "Ngày Trước Quốc Khánh":"The Day Before Independence Day", + "Ngày Quốc Khánh":"Independence Day", + "Ngày Sau Quốc Khánh":"The Day After Independence Day" +} \ No newline at end of file diff --git a/src/Calendars/ChineseCalendar.php b/src/Calendars/ChineseCalendar.php new file mode 100644 index 000000000..5ce9996bb --- /dev/null +++ b/src/Calendars/ChineseCalendar.php @@ -0,0 +1,39 @@ +asianTimezone = $asianTimezone; + + return $this; + } + + protected function chineseToGregorianDate(string $input, int $year): CarbonImmutable + { + $timestamp = (int) $this->getFormatter()->parse($year . '-' . $input); + + return (new CarbonImmutable()) + ->setTimeStamp($timestamp) + ->setTimezone(new DateTimeZone($this->asianTimezone)); + } + + protected function getFormatter(): IntlDateFormatter + { + return new IntlDateFormatter( + locale: 'zh-CN@calendar=chinese', + dateType: IntlDateFormatter::SHORT, + timeType: IntlDateFormatter::NONE, + timezone: $this->asianTimezone, + calendar: IntlDateFormatter::TRADITIONAL + ); + } +} \ No newline at end of file diff --git a/src/Countries/Vietnam.php b/src/Countries/Vietnam.php new file mode 100644 index 000000000..e99f75919 --- /dev/null +++ b/src/Countries/Vietnam.php @@ -0,0 +1,117 @@ + '01-01', + // Day of Southern Liberation and National Reunification + 'Ngày Giải Phóng Miền Nam, Thống Nhất Đất Nước' => '04-30', + // Labour Day + 'Ngày Quốc Tế Lao Động' => '05-01', + // Independence Day + 'Ngày Quốc Khánh' => '09-02', + ], $this->variableHolidays($year)); + } + + /** @return array */ + protected function variableHolidays(int $year): array + { + $this->setTimezoneForChineseCalendar('Asia/Ho_Chi_Minh'); + + return array_merge( + $this->getHungKingsFestival($year), + $this->getLunarNewYearHoliday($year), + $this->getTheExtraDayForIndependenceDay($year), + ); + } + + /** @return array */ + protected function getHungKingsFestival(int $year): array + { + return [ + // Hung Kings' Festival + 'Ngày Giỗ Tổ Hùng Vương' => $this->chineseToGregorianDate('03-10', $year), + ]; + } + + /** @return array */ + protected function getLunarNewYearHoliday(int $year): array + { + $firstOfJanInChineseCalendar = $this->chineseToGregorianDate('01-01', $year); + + return [ + // 12-29 the previous year (in Chinese Calendar) + 'Ngày Hai Mươi Chín Tết' => $firstOfJanInChineseCalendar->subDays(2), + // Lunar New Year's Eve (12-30 in Chinese Calendar) + 'Ngày Ba Mươi Tết' => $firstOfJanInChineseCalendar->subDay(), + // Lunar New Year Day 1 + 'Mùng Một Tết Âm Lịch' => $firstOfJanInChineseCalendar, + // Lunar New Year Day 2 + 'Mùng Hai Tết Âm Lịch' => $firstOfJanInChineseCalendar->addDay(), + // Lunar New Year Day 3 + 'Mùng Ba Tết Âm Lịch' => $firstOfJanInChineseCalendar->addDays(2), + // Lunar New Year Day 4 + 'Mùng Bốn Tết Âm Lịch' => $firstOfJanInChineseCalendar->addDays(3), + // Lunar New Year Day 5 + 'Mùng Năm Tết Âm Lịch' => $firstOfJanInChineseCalendar->addDays(4), + ]; + } + + /** @return array */ + protected function getTheExtraDayForIndependenceDay(int $year): array + { + if ($year < 2021) { + return []; + } + + $independenceDay = CarbonImmutable::parse("$year-09-02") + ->setTimeZone("Asia/Ho_Chi_Minh"); + + if ($independenceDay->dayOfWeek == CarbonImmutable::MONDAY) { + return ['Ngày Sau Quốc Khánh' => $independenceDay->addDay()]; + } + + if ($independenceDay->dayOfWeek == CarbonImmutable::TUESDAY) { + return ['Ngày Trước Quốc Khánh' => $independenceDay->subDay()]; + } + + if ($independenceDay->dayOfWeek == CarbonImmutable::WEDNESDAY) { + return ['Ngày Trước Quốc Khánh' => $independenceDay->subDay()]; + } + + if ($independenceDay->dayOfWeek == CarbonImmutable::THURSDAY) { + return ['Ngày Sau Quốc Khánh' => $independenceDay->addDay()]; + } + + if ($independenceDay->dayOfWeek == CarbonImmutable::FRIDAY) { + return ['Ngày Trước Quốc Khánh' => $independenceDay->subDay()]; + } + + if ($independenceDay->dayOfWeek == CarbonImmutable::SATURDAY) { + return ['Ngày Trước Quốc Khánh' => $independenceDay->subDay()]; + } + + if ($independenceDay->dayOfWeek == CarbonImmutable::SUNDAY) { + return ['Ngày Sau Quốc Khánh' => $independenceDay->addDays(2)]; + } + + return []; + } +} diff --git a/tests/.pest/snapshots/Countries/VietnamTest/it_can_calculate_vietnamese_holidays.snap b/tests/.pest/snapshots/Countries/VietnamTest/it_can_calculate_vietnamese_holidays.snap new file mode 100644 index 000000000..3b7cc36f9 --- /dev/null +++ b/tests/.pest/snapshots/Countries/VietnamTest/it_can_calculate_vietnamese_holidays.snap @@ -0,0 +1,54 @@ +[ + { + "name": "T\u1ebft D\u01b0\u01a1ng L\u1ecbch", + "date": "2024-01-01" + }, + { + "name": "Ng\u00e0y Hai M\u01b0\u01a1i Ch\u00edn T\u1ebft", + "date": "2024-02-08" + }, + { + "name": "Ng\u00e0y Ba M\u01b0\u01a1i T\u1ebft", + "date": "2024-02-09" + }, + { + "name": "M\u00f9ng M\u1ed9t T\u1ebft \u00c2m L\u1ecbch", + "date": "2024-02-10" + }, + { + "name": "M\u00f9ng Hai T\u1ebft \u00c2m L\u1ecbch", + "date": "2024-02-11" + }, + { + "name": "M\u00f9ng Ba T\u1ebft \u00c2m L\u1ecbch", + "date": "2024-02-12" + }, + { + "name": "M\u00f9ng B\u1ed1n T\u1ebft \u00c2m L\u1ecbch", + "date": "2024-02-13" + }, + { + "name": "M\u00f9ng N\u0103m T\u1ebft \u00c2m L\u1ecbch", + "date": "2024-02-14" + }, + { + "name": "Ng\u00e0y Gi\u1ed7 T\u1ed5 H\u00f9ng V\u01b0\u01a1ng", + "date": "2024-04-18" + }, + { + "name": "Ng\u00e0y Gi\u1ea3i Ph\u00f3ng Mi\u1ec1n Nam, Th\u1ed1ng Nh\u1ea5t \u0110\u1ea5t N\u01b0\u1edbc", + "date": "2024-04-30" + }, + { + "name": "Ng\u00e0y Qu\u1ed1c T\u1ebf Lao \u0110\u1ed9ng", + "date": "2024-05-01" + }, + { + "name": "Ng\u00e0y Qu\u1ed1c Kh\u00e1nh", + "date": "2024-09-02" + }, + { + "name": "Ng\u00e0y Sau Qu\u1ed1c Kh\u00e1nh", + "date": "2024-09-03" + } +] \ No newline at end of file diff --git a/tests/Countries/VietnamTest.php b/tests/Countries/VietnamTest.php new file mode 100644 index 000000000..edb13028d --- /dev/null +++ b/tests/Countries/VietnamTest.php @@ -0,0 +1,57 @@ +get(); + + expect($holidays) + ->toBeArray() + ->not()->toBeEmpty(); + + expect(formatDates($holidays))->toMatchSnapshot(); +}); + +it('can calculate the Lunar New Year holiday', function ($year, $expectedHoliday) { + CarbonImmutable::setTestNowAndTimezone("{$year}-01-01", 'Asia/Ho_Chi_Minh'); + + $holidays = Holidays::for(country: 'vn')->get(); + + expect($holidays) + ->toBeArray() + ->not()->toBeEmpty(); + + $lunarNewYearHoliday = array_map(fn ($date) => $date['date'], formatDates($holidays)); + + expect($lunarNewYearHoliday)->toContain(...$expectedHoliday); +})->with([ + ['year' => 2023, 'expected_holiday' => ['2023-01-20', '2023-01-21', '2023-01-22', '2023-01-23', '2023-01-24', '2023-01-25', '2023-01-26']], + ['year' => 2022, 'expected_holiday' => ['2022-01-30', '2022-01-31', '2022-02-01', '2022-02-02', '2022-02-03', '2022-02-04', '2022-02-05']], + ['year' => 2021, 'expected_holiday' => ['2021-02-10', '2021-02-11', '2021-02-12', '2021-02-13', '2021-02-14', '2021-02-15', '2021-02-16']], + ['year' => 2020, 'expected_holiday' => ['2020-01-23', '2020-01-24', '2020-01-25', '2020-01-26', '2020-01-27', '2020-01-28', '2020-01-29']], + ['year' => 2019, 'expected_holiday' => ['2019-02-03', '2019-02-04', '2019-02-05', '2019-02-06', '2019-02-07', '2019-02-08', '2019-02-08']], +]); + +it('can calculate the holiday of independence', function ($year, $expectedHoliday) { + CarbonImmutable::setTestNowAndTimezone("{$year}-01-01", 'Asia/Ho_Chi_Minh'); + + $holidays = Holidays::for(country: 'vn')->get(); + + expect($holidays) + ->toBeArray() + ->not()->toBeEmpty(); + + $dates = array_map(fn ($date) => $date['date'], formatDates($holidays)); + + expect($dates)->toContain(...$expectedHoliday); +})->with([ + ['year' => 2023, 'expected_holiday' => ['2023-09-01', '2023-09-02']], + ['year' => 2022, 'expected_holiday' => ['2022-09-01', '2022-09-02']], + ['year' => 2021, 'expected_holiday' => ['2021-09-02', '2021-09-03']], +]);