Opa_omroep-automatiseren/venv/lib/python3.8/site-packages/holidays/countries/singapore.py
Eljakim Herrewijnen f26bbbf103 initial
2020-12-27 21:00:11 +01:00

318 lines
14 KiB
Python

# -*- 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 <ryanssdev@icloud.com> (c) 2014-2017
# dr-prodigy <maurizio.montel@gmail.com> (c) 2017-2020
# Website: https://github.com/dr-prodigy/python-holidays
# License: MIT (see LICENSE file)
from datetime import date, timedelta
from dateutil.easter import easter
from dateutil.relativedelta import relativedelta as rd, SA, FR, MO
from holidays.constants import JAN, FEB, MAR, MAY, JUN, JUL, AUG, SEP, OCT, \
NOV, DEC
from holidays.constants import SUN
from holidays.holiday_base import HolidayBase
from holidays.utils import get_gre_date
class Singapore(HolidayBase):
# Holidays Act: https://sso.agc.gov.sg/Act/HA1998
# https://www.mom.gov.sg/employment-practices/public-holidays
# https://en.wikipedia.org/wiki/Public_holidays_in_Singapore
# Holidays prior to 1969 (Act 24 of 1968—Holidays (Amendment) Act 1968)
# are estimated.
# Holidays prior to 2000 may not be accurate.
# Holidays after 2021: the following four moving date holidays whose exact
# date is announced yearly are estimated (and so denoted):
# - Hari Raya Puasa*
# - Hari Raya Haji*
# - Vesak Day
# - Deepavali
# *only if hijri-converter library is installed, otherwise a warning is
# raised that this holiday is missing. hijri-converter requires
# Python >= 3.6
def __init__(self, **kwargs):
self.country = "SG"
HolidayBase.__init__(self, **kwargs)
def _populate(self, year):
# New Year's Day
self[date(year, JAN, 1)] = "New Year's Day"
# Chinese New Year (two days)
hol_date = self.get_lunar_n_y_date(year)
self[hol_date] = "Chinese New Year"
self[hol_date + rd(days=+1)] = "Chinese New Year"
# Hari Raya Puasa
# aka Eid al-Fitr
# date of observance is announced yearly
dates_obs = {2001: [(DEC, 16)], 2002: [(DEC, 6)],
2003: [(NOV, 25)], 2004: [(NOV, 14)], 2005: [(NOV, 3)],
2006: [(OCT, 24)], 2007: [(OCT, 13)], 2008: [(OCT, 1)],
2009: [(SEP, 20)], 2010: [(SEP, 10)], 2011: [(AUG, 30)],
2012: [(AUG, 19)], 2013: [(AUG, 8)], 2014: [(JUL, 28)],
2015: [(JUL, 17)], 2016: [(JUL, 6)], 2017: [(JUN, 25)],
2018: [(JUN, 15)], 2019: [(JUN, 5)], 2020: [(MAY, 24)],
2021: [(MAY, 13)]}
if year in dates_obs:
for date_obs in dates_obs[year]:
hol_date = date(year, *date_obs)
self[hol_date] = "Hari Raya Puasa"
# Second day of Hari Raya Puasa (up to and including 1968)
# Removed since we don't have Hari Raya Puasa dates for the
# the years <= 1968:
# if year <= 1968:
# self[hol_date + rd(days=+1),
# "Second day of Hari Raya Puasa")
else:
for date_obs in self.get_hrp_date(year):
hol_date = date_obs
self[hol_date] = "Hari Raya Puasa* (*estimated)"
# Second day of Hari Raya Puasa (up to and including 1968)
if year <= 1968:
hol_date += rd(days=+1)
self[hol_date] = ("Second day of Hari Raya Puasa*"
" (*estimated)")
# Hari Raya Haji
# aka Eid al-Adha
# date of observance is announced yearly
dates_obs = {2001: [(MAR, 6)], 2002: [(FEB, 23)],
2003: [(FEB, 12)], 2004: [(FEB, 1)], 2005: [(JAN, 21)],
2006: [(JAN, 10)], 2007: [(DEC, 20)], 2008: [(DEC, 8)],
2009: [(NOV, 27)], 2010: [(NOV, 17)], 2011: [(NOV, 6)],
2012: [(OCT, 26)], 2013: [(OCT, 15)], 2014: [(OCT, 5)],
2015: [(SEP, 24)], 2016: [(SEP, 12)], 2017: [(SEP, 1)],
2018: [(AUG, 22)], 2019: [(AUG, 11)], 2020: [(JUL, 31)],
2021: [(JUL, 20)]}
if year in dates_obs:
for date_obs in dates_obs[year]:
hol_date = date(year, *date_obs)
self[hol_date] = "Hari Raya Haji"
else:
for date_obs in self.get_hrh_date(year):
hol_date = date_obs
self[hol_date] = "Hari Raya Haji* (*estimated)"
# Holy Saturday (up to and including 1968)
if year <= 1968:
self[easter(year) + rd(weekday=SA(-1))] = "Holy Saturday"
# Good Friday
self[easter(year) + rd(weekday=FR(-1))] = "Good Friday"
# Easter Monday
if year <= 1968:
self[easter(year) + rd(weekday=MO(1))] = "Easter Monday"
# Labour Day
self[date(year, MAY, 1)] = "Labour Day"
# Vesak Day
# date of observance is announced yearly
# https://en.wikipedia.org/wiki/Vesak#Dates_of_observance
dates_obs = {2001: (MAY, 7), 2002: (MAY, 27),
2003: (MAY, 15), 2004: (JUN, 2), 2005: (MAY, 23),
2006: (MAY, 12), 2007: (MAY, 31), 2008: (MAY, 19),
2009: (MAY, 9), 2010: (MAY, 28), 2011: (MAY, 17),
2012: (MAY, 5), 2013: (MAY, 24), 2014: (MAY, 13),
2015: (JUN, 1), 2016: (MAY, 20), 2017: (MAY, 10),
2018: (MAY, 29), 2019: (MAY, 19), 2020: (MAY, 7),
2021: (MAY, 26)}
if year in dates_obs:
hol_date = date(year, *dates_obs[year])
self[hol_date] = "Vesak Day"
else:
hol_date = self.get_vesak_date(year)
self[hol_date] = "Vesak Day* (*estimated; ~10% chance +/- 1 day)"
# National Day
self[date(year, AUG, 9)] = "National Day"
# Deepavali
# aka Diwali
# date of observance is announced yearly
dates_obs = {2001: (NOV, 14), 2002: (NOV, 3),
2003: (OCT, 23), 2004: (NOV, 11), 2005: (NOV, 1),
2006: (OCT, 21), 2007: (NOV, 8), 2008: (OCT, 27),
2009: (OCT, 17), 2010: (NOV, 5), 2011: (OCT, 26),
2012: (NOV, 13), 2013: (NOV, 2), 2014: (OCT, 22),
2015: (NOV, 10), 2016: (OCT, 29), 2017: (OCT, 18),
2018: (NOV, 6), 2019: (OCT, 27), 2020: (NOV, 14),
2021: (NOV, 4)}
if year in dates_obs:
hol_date = date(year, *dates_obs[year])
self[hol_date] = "Deepavali"
else:
hol_date = self.get_s_diwali_date(year)
self[hol_date] = "Deepavali* (*estimated; rarely on day after)"
# Christmas Day
self[date(year, DEC, 25)] = "Christmas Day"
# Boxing day (up to and including 1968)
if year <= 1968:
self[date(year, DEC, 26)] = "Boxing Day"
# Polling Day
dates_obs = {2001: (NOV, 3), 2006: (MAY, 6), 2011: (MAY, 7),
2015: (SEP, 11), 2020: (JUL, 10)}
if year in dates_obs:
self[date(year, *dates_obs[year])] = "Polling Day"
# SG50 Public holiday
# Announced on 14 March 2015
# https://www.mom.gov.sg/newsroom/press-releases/2015/sg50-public-holiday-on-7-august-2015
if year == 2015:
self[date(2015, AUG, 7)] = "SG50 Public Holiday"
# Check for holidays that fall on a Sunday and implement Section 4(2)
# of the Holidays Act: "if any day specified in the Schedule falls on
# a Sunday, the day next following not being itself a public holiday
# is declared a public holiday in Singapore."
for (hol_date, hol_name) in list(self.items()):
if hol_date.weekday() == SUN:
self[hol_date] += ' [Sunday]'
in_lieu_date = hol_date + rd(days=+1)
while in_lieu_date in self:
in_lieu_date += rd(days=+1)
self[in_lieu_date] = hol_name + '[In lieu]'
# The below is used to calculate lunar new year (i.e. Chinese new year)
# Code borrowed from Hong Kong entry as of 16-Nov-19
# Should probably be a function available to multiple countries
# 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), date(1901, 2, 19)
# The Gregorian date for December 30, 2099 is 2100/2/8
LUNAR_END_DATE, SOLAR_END_DATE = (2099, 12, 30), date(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 Gregorian date of lunar new year
def get_lunar_n_y_date(self, year):
span_days = 0
for y in range(self.START_YEAR, year):
span_days += self.lunar_year_days(y)
# Always in first month (by definition)
# leap_month = self.get_leap_month(year)
# for m in range(1, 1 + (1 > leap_month)):
# span_days += self.lunar_month_days(year, m)
return self.SOLAR_START_DATE + timedelta(span_days)
# Estimate Gregorian date of Vesak
def get_vesak_date(self, year):
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, 4 + (4 > leap_month)):
span_days += self.lunar_month_days(year, m)
span_days += 14
return (self.SOLAR_START_DATE + timedelta(span_days))
# Estimate Gregorian date of Southern India Diwali
def get_s_diwali_date(self, year):
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, 10 + (10 > leap_month)):
span_days += self.lunar_month_days(year, m)
span_days -= 2
return (self.SOLAR_START_DATE + timedelta(span_days))
# Estimate Gregorian date(s) of Hara Rasa Puasa
def get_hrp_date(self, year):
return get_gre_date(year, 10, 1)
# Estimate Gregorian date(s) of Hara Rasa Haji
def get_hrh_date(self, year):
return get_gre_date(year, 12, 10)
class SG(Singapore):
pass
class SGP(Singapore):
pass