# -*- coding: utf-8 -*- # python-holidays # --------------- # A fast, efficient Python library for generating country, province and state # specific sets of holidays on the fly. It aims to make determining whether a # specific date is a holiday as fast and flexible as possible. # # Author: ryanss (c) 2014-2017 # dr-prodigy (c) 2017-2020 # Website: https://github.com/dr-prodigy/python-holidays # License: MIT (see LICENSE file) from datetime import date, datetime, timedelta from dateutil.easter import easter from dateutil.relativedelta import relativedelta as rd, FR, SA, MO from holidays.constants import JAN, APR, MAY, JUL, SEP, OCT, \ DEC from holidays.constants import MON, TUE, WED, THU, FRI, SAT, SUN from holidays.holiday_base import HolidayBase class HongKong(HolidayBase): # https://www.gov.hk/en/about/abouthk/holiday/2020.htm # https://en.wikipedia.org/wiki/Public_holidays_in_Hong_Kong def __init__(self, **kwargs): self.country = "HK" HolidayBase.__init__(self, **kwargs) def _populate(self, year): day_following = "The day following " # The first day of January name = "The first day of January" first_date = date(year, JAN, 1) if self.observed: if first_date.weekday() == SUN: self[first_date + rd(days=+1)] = day_following + \ self.first_lower(name) first_date = first_date + rd(days=+1) else: self[first_date] = name else: self[first_date] = name # Lunar New Year name = "Lunar New Year's Day" preceding_day_lunar = "The day preceding Lunar New Year's Day" second_day_lunar = "The second day of Lunar New Year" third_day_lunar = "The third day of Lunar New Year" fourth_day_lunar = "The fourth day of Lunar New Year" dt = self.get_solar_date(year, 1, 1) new_year_date = date(dt.year, dt.month, dt.day) if self.observed: self[new_year_date] = name if new_year_date.weekday() in [MON, TUE, WED, THU]: self[new_year_date] = name self[new_year_date + rd(days=+1)] = second_day_lunar self[new_year_date + rd(days=+2)] = third_day_lunar elif new_year_date.weekday() == FRI: self[new_year_date] = name self[new_year_date + rd(days=+1)] = second_day_lunar self[new_year_date + rd(days=+3)] = fourth_day_lunar elif new_year_date.weekday() == SAT: self[new_year_date] = name self[new_year_date + rd(days=+2)] = third_day_lunar self[new_year_date + rd(days=+3)] = fourth_day_lunar elif new_year_date.weekday() == SUN: if year in [2006, 2007, 2010]: self[new_year_date + rd(days=-1)] = preceding_day_lunar self[new_year_date + rd(days=+1)] = second_day_lunar self[new_year_date + rd(days=+2)] = third_day_lunar else: self[new_year_date + rd(days=+1)] = second_day_lunar self[new_year_date + rd(days=+2)] = third_day_lunar self[new_year_date + rd(days=+3)] = fourth_day_lunar else: self[new_year_date] = name self[new_year_date + rd(days=+1)] = second_day_lunar self[new_year_date + rd(days=+2)] = third_day_lunar # Ching Ming Festival name = "Ching Ming Festival" if self.isLeapYear(year) or (self.isLeapYear(year - 1) and year > 2008): ching_ming_date = date(year, APR, 4) else: ching_ming_date = date(year, APR, 5) if self.observed: if ching_ming_date.weekday() == SUN: self[ching_ming_date + rd(days=+1)] = day_following + name ching_ming_date = ching_ming_date + rd(days=+1) else: self[ching_ming_date] = name else: self[ching_ming_date] = name # Easter Holiday good_friday = "Good Friday" easter_monday = "Easter Monday" if self.observed: self[easter(year) + rd(weekday=FR(-1))] = good_friday self[easter(year) + rd(weekday=SA(-1))] = day_following + \ good_friday if ching_ming_date == easter(year) + rd(weekday=MO): self[easter(year) + rd(weekday=MO) + rd(days=+1)] = \ day_following + easter_monday else: self[easter(year) + rd(weekday=MO)] = easter_monday else: self[easter(year) + rd(weekday=FR(-1))] = good_friday self[easter(year) + rd(weekday=SA(-1))] = day_following + \ good_friday self[easter(year) + rd(weekday=MO)] = easter_monday # Birthday of the Buddha name = "Birthday of the Buddha" dt = self.get_solar_date(year, 4, 8) buddha_date = date(dt.year, dt.month, dt.day) if self.observed: if buddha_date.weekday() == SUN: self[buddha_date + rd(days=+1)] = day_following + name else: self[buddha_date] = name else: self[buddha_date] = name # Labour Day name = "Labour Day" labour_date = date(year, MAY, 1) if self.observed: if labour_date.weekday() == SUN: self[labour_date + rd(days=+1)] = day_following + name else: self[labour_date] = name else: self[labour_date] = name # Tuen Ng Festival name = "Tuen Ng Festival" dt = self.get_solar_date(year, 5, 5) tuen_ng_date = date(dt.year, dt.month, dt.day) if self.observed: if tuen_ng_date.weekday() == SUN: self[tuen_ng_date + rd(days=+1)] = day_following + name else: self[tuen_ng_date] = name else: self[tuen_ng_date] = name # Hong Kong Special Administrative Region Establishment Day name = "Hong Kong Special Administrative Region Establishment Day" hksar_date = date(year, JUL, 1) if self.observed: if hksar_date.weekday() == SUN: self[hksar_date + rd(days=+1)] = day_following + name else: self[hksar_date] = name else: self[hksar_date] = name # Special holiday on 2015 - The 70th anniversary day of the victory # of the Chinese people's war of resistance against Japanese aggression name = "The 70th anniversary day of the victory of the Chinese " + \ "people's war of resistance against Japanese aggression" if year == 2015: self[date(year, SEP, 3)] = name # Chinese Mid-Autumn Festival name = "Chinese Mid-Autumn Festival" dt = self.get_solar_date(year, 8, 15) mid_autumn_date = date(dt.year, dt.month, dt.day) if self.observed: if mid_autumn_date.weekday() == SAT: self[mid_autumn_date] = name else: self[mid_autumn_date + rd(days=+1)] = day_following + \ "the " + name mid_autumn_date = mid_autumn_date + rd(days=+1) else: self[mid_autumn_date] = name # National Day name = "National Day" national_date = date(year, OCT, 1) if self.observed: if (national_date.weekday() == SUN or national_date == mid_autumn_date): self[national_date + rd(days=+1)] = day_following + name else: self[national_date] = name else: self[national_date] = name # Chung Yeung Festival name = "Chung Yeung Festival" dt = self.get_solar_date(year, 9, 9) chung_yeung_date = date(dt.year, dt.month, dt.day) if self.observed: if chung_yeung_date.weekday() == SUN: self[chung_yeung_date + rd(days=+1)] = day_following + name else: self[chung_yeung_date] = name else: self[chung_yeung_date] = name # Christmas Day name = "Christmas Day" first_after_christmas = "The first weekday after " + name second_after_christmas = "The second weekday after " + name christmas_date = date(year, DEC, 25) if self.observed: if christmas_date.weekday() == SUN: self[christmas_date] = name self[christmas_date + rd(days=+1)] = first_after_christmas self[christmas_date + rd(days=+2)] = second_after_christmas elif christmas_date.weekday() == SAT: self[christmas_date] = name self[christmas_date + rd(days=+2)] = first_after_christmas else: self[christmas_date] = name self[christmas_date + rd(days=+1)] = first_after_christmas else: self[christmas_date] = name self[christmas_date + rd(days=+1)] = day_following + name def isLeapYear(self, year): if year % 4 != 0: return False elif year % 100 != 0: return True elif year % 400 != 0: return False else: return True def first_lower(self, s): return s[0].lower() + s[1:] # Store the number of days per year from 1901 to 2099, and the number of # days from the 1st to the 13th to store the monthly (including the month # of the month), 1 means that the month is 30 days. 0 means the month is # 29 days. The 12th to 15th digits indicate the month of the next month. # If it is 0x0F, it means that there is no leap month. g_lunar_month_days = [ 0xF0EA4, 0xF1D4A, 0x52C94, 0xF0C96, 0xF1536, 0x42AAC, 0xF0AD4, 0xF16B2, 0x22EA4, 0xF0EA4, # 1901-1910 0x6364A, 0xF164A, 0xF1496, 0x52956, 0xF055A, 0xF0AD6, 0x216D2, 0xF1B52, 0x73B24, 0xF1D24, # 1911-1920 0xF1A4A, 0x5349A, 0xF14AC, 0xF056C, 0x42B6A, 0xF0DA8, 0xF1D52, 0x23D24, 0xF1D24, 0x61A4C, # 1921-1930 0xF0A56, 0xF14AE, 0x5256C, 0xF16B4, 0xF0DA8, 0x31D92, 0xF0E92, 0x72D26, 0xF1526, 0xF0A56, # 1931-1940 0x614B6, 0xF155A, 0xF0AD4, 0x436AA, 0xF1748, 0xF1692, 0x23526, 0xF152A, 0x72A5A, 0xF0A6C, # 1941-1950 0xF155A, 0x52B54, 0xF0B64, 0xF1B4A, 0x33A94, 0xF1A94, 0x8152A, 0xF152E, 0xF0AAC, 0x6156A, # 1951-1960 0xF15AA, 0xF0DA4, 0x41D4A, 0xF1D4A, 0xF0C94, 0x3192E, 0xF1536, 0x72AB4, 0xF0AD4, 0xF16D2, # 1961-1970 0x52EA4, 0xF16A4, 0xF164A, 0x42C96, 0xF1496, 0x82956, 0xF055A, 0xF0ADA, 0x616D2, 0xF1B52, # 1971-1980 0xF1B24, 0x43A4A, 0xF1A4A, 0xA349A, 0xF14AC, 0xF056C, 0x60B6A, 0xF0DAA, 0xF1D92, 0x53D24, # 1981-1990 0xF1D24, 0xF1A4C, 0x314AC, 0xF14AE, 0x829AC, 0xF06B4, 0xF0DAA, 0x52D92, 0xF0E92, 0xF0D26, # 1991-2000 0x42A56, 0xF0A56, 0xF14B6, 0x22AB4, 0xF0AD4, 0x736AA, 0xF1748, 0xF1692, 0x53526, 0xF152A, # 2001-2010 0xF0A5A, 0x4155A, 0xF156A, 0x92B54, 0xF0BA4, 0xF1B4A, 0x63A94, 0xF1A94, 0xF192A, 0x42A5C, # 2011-2020 0xF0AAC, 0xF156A, 0x22B64, 0xF0DA4, 0x61D52, 0xF0E4A, 0xF0C96, 0x5192E, 0xF1956, 0xF0AB4, # 2021-2030 0x315AC, 0xF16D2, 0xB2EA4, 0xF16A4, 0xF164A, 0x63496, 0xF1496, 0xF0956, 0x50AB6, 0xF0B5A, # 2031-2040 0xF16D4, 0x236A4, 0xF1B24, 0x73A4A, 0xF1A4A, 0xF14AA, 0x5295A, 0xF096C, 0xF0B6A, 0x31B54, # 2041-2050 0xF1D92, 0x83D24, 0xF1D24, 0xF1A4C, 0x614AC, 0xF14AE, 0xF09AC, 0x40DAA, 0xF0EAA, 0xF0E92, # 2051-2060 0x31D26, 0xF0D26, 0x72A56, 0xF0A56, 0xF14B6, 0x52AB4, 0xF0AD4, 0xF16CA, 0x42E94, 0xF1694, # 2061-2070 0x8352A, 0xF152A, 0xF0A5A, 0x6155A, 0xF156A, 0xF0B54, 0x4174A, 0xF1B4A, 0xF1A94, 0x3392A, # 2071-2080 0xF192C, 0x7329C, 0xF0AAC, 0xF156A, 0x52B64, 0xF0DA4, 0xF1D4A, 0x41C94, 0xF0C96, 0x8192E, # 2081-2090 0xF0956, 0xF0AB6, 0x615AC, 0xF16D4, 0xF0EA4, 0x42E4A, 0xF164A, 0xF1516, 0x22936, # 2090-2099 ] # Define range of years START_YEAR, END_YEAR = 1901, 1900 + len(g_lunar_month_days) # 1901 The 1st day of the 1st month of the Gregorian calendar is 1901/2/19 LUNAR_START_DATE, SOLAR_START_DATE = (1901, 1, 1), datetime(1901, 2, 19) # The Gregorian date for December 30, 2099 is 2100/2/8 LUNAR_END_DATE, SOLAR_END_DATE = (2099, 12, 30), datetime(2100, 2, 18) def get_leap_month(self, lunar_year): return (self.g_lunar_month_days[lunar_year - self.START_YEAR] >> 16) \ & 0x0F def lunar_month_days(self, lunar_year, lunar_month): return 29 + ((self.g_lunar_month_days[lunar_year - self.START_YEAR] >> lunar_month) & 0x01) def lunar_year_days(self, year): days = 0 months_day = self.g_lunar_month_days[year - self.START_YEAR] for i in range(1, 13 if self.get_leap_month(year) == 0x0F else 14): day = 29 + ((months_day >> i) & 0x01) days += day return days # Calculate the Gregorian date according to the lunar calendar def get_solar_date(self, year, month, day): span_days = 0 for y in range(self.START_YEAR, year): span_days += self.lunar_year_days(y) leap_month = self.get_leap_month(year) for m in range(1, month + (month > leap_month)): span_days += self.lunar_month_days(year, m) span_days += day - 1 return self.SOLAR_START_DATE + timedelta(span_days) class HK(HongKong): pass class HKG(HongKong): pass