@@ -4954,3 +4954,338 @@ def _populate(self, year):
49544954
49554955class PE (Peru ):
49564956 pass
4957+
4958+
4959+ class Singapore (HolidayBase ):
4960+
4961+ # Holidays Act: https://sso.agc.gov.sg/Act/HA1998
4962+ # https://www.mom.gov.sg/employment-practices/public-holidays
4963+ # https://en.wikipedia.org/wiki/Public_holidays_in_Singapore
4964+
4965+ # Holidays prior to 1969 (Act 24 of 1968—Holidays (Amendment) Act 1968)
4966+ # are estimated.
4967+
4968+ # Holidays prior to 2000 may not be accurate.
4969+
4970+ # Holidays after 2020: the following four annual scheduled but moving date
4971+ # holidays (announced yearly) are estimated:
4972+ # - Hari Raya Puasa*
4973+ # - Hari Raya Haji*
4974+ # - Vesak Day
4975+ # - Deepavali
4976+ # *only if hijri-converter library is installed, otherwise a warning is
4977+ # raised that this holiday is missing. hjiri-converter requires
4978+ # Python >= 3.6
4979+
4980+ def __init__ (self , ** kwargs ):
4981+ self .country = "SG"
4982+ HolidayBase .__init__ (self , ** kwargs )
4983+
4984+ def _populate (self , year ):
4985+ if year < 1969 :
4986+ return
4987+
4988+ def storeholiday (self , hol_date , hol_name ):
4989+ """
4990+ Function to store the holiday name in the appropriate
4991+ date and to implement Section 4(2) of the Holidays Act:
4992+ 'if any day specified in the Schedule falls on a Sunday,
4993+ the day next following not being itself a public holiday
4994+ is declared a public holiday in Singapore.'
4995+ """
4996+ if hol_date .weekday () == SUN :
4997+ self [hol_date ] = hol_name + " [Sunday]"
4998+ self [hol_date + rd (days = + 1 )] = "Monday following " + hol_name
4999+ else :
5000+ self [hol_date ] = hol_name
5001+
5002+ # New Year's Day
5003+ storeholiday (self , date (year , JAN , 1 ), "New Year's Day" )
5004+
5005+ # Chinese New Year (two days)
5006+ hol_date = self .get_lunar_n_y_date (year )
5007+ self [hol_date ] = "Chinese New Year"
5008+ storeholiday (self , hol_date + rd (days = + 1 ), "Chinese New Year" )
5009+
5010+ # Hari Raya Puasa
5011+ # aka Eid al-Fitr
5012+ # date of observance is announced yearly
5013+ dates_obs = {2001 : [(DEC , 16 )], 2002 : [(DEC , 6 )],
5014+ 2003 : [(NOV , 25 )], 2004 : [(NOV , 14 )], 2005 : [(NOV , 3 )],
5015+ 2006 : [(OCT , 24 )], 2007 : [(OCT , 13 )], 2008 : [(OCT , 1 )],
5016+ 2009 : [(SEP , 20 )], 2010 : [(SEP , 10 )], 2011 : [(AUG , 30 )],
5017+ 2012 : [(AUG , 19 )], 2013 : [(AUG , 8 )], 2014 : [(JUL , 28 )],
5018+ 2015 : [(JUL , 17 )], 2016 : [(JUL , 6 )], 2017 : [(JUN , 25 )],
5019+ 2018 : [(JUN , 15 )], 2019 : [(JUN , 5 )], 2020 : [(MAY , 24 )]}
5020+ if year in dates_obs :
5021+ for date_obs in dates_obs [year ]:
5022+ hol_date = date (year , * date_obs )
5023+ storeholiday (self , hol_date , "Hari Raya Puasa" )
5024+ # Second day of Hari Raya Puasa (up to and including 1968)
5025+ if year <= 1968 :
5026+ storeholiday (self , hol_date + rd (days = + 1 ),
5027+ "Second day of Hari Raya Puasa" )
5028+ else :
5029+ for date_obs in self .get_hrp_date (year ):
5030+ hol_date = date_obs
5031+ storeholiday (self , hol_date ,
5032+ "Hari Raya Puasa* (*estimated)" )
5033+ # Second day of Hari Raya Puasa (up to and including 1968)
5034+ if year <= 1968 :
5035+ storeholiday (self , hol_date + rd (days = + 1 ),
5036+ "Second day of Hari Raya Puasa* (*estimated)" )
5037+
5038+ # Hari Raya Haji
5039+ # aka Eid al-Adha
5040+ # date of observance is announced yearly
5041+ dates_obs = {2001 : [(MAR , 6 )], 2002 : [(FEB , 23 )],
5042+ 2003 : [(FEB , 12 )], 2004 : [(FEB , 1 )], 2005 : [(JAN , 21 )],
5043+ 2006 : [(JAN , 10 )], 2007 : [(DEC , 20 )], 2008 : [(DEC , 8 )],
5044+ 2009 : [(NOV , 27 )], 2010 : [(NOV , 17 )], 2011 : [(NOV , 6 )],
5045+ 2012 : [(OCT , 26 )], 2013 : [(OCT , 15 )], 2014 : [(OCT , 5 )],
5046+ 2015 : [(SEP , 24 )], 2016 : [(SEP , 12 )], 2017 : [(SEP , 1 )],
5047+ 2018 : [(AUG , 22 )], 2019 : [(AUG , 11 )], 2020 : [(JUL , 31 )]}
5048+ if year in dates_obs :
5049+ for date_obs in dates_obs [year ]:
5050+ hol_date = date (year , * date_obs )
5051+ storeholiday (self , hol_date ,
5052+ "Hari Raya Haji" )
5053+ else :
5054+ for date_obs in self .get_hrh_date (year ):
5055+ hol_date = date_obs
5056+ storeholiday (self , hol_date ,
5057+ "Hari Raya Haji* (*estimated)" )
5058+
5059+ # Holy Saturday (up to and including 1968)
5060+ if year <= 1968 :
5061+ self [easter (year ) + rd (weekday = SA (- 1 ))] = "Holy Saturday"
5062+
5063+ # Good Friday
5064+ self [easter (year ) + rd (weekday = FR (- 1 ))] = "Good Friday"
5065+
5066+ # Easter Monday
5067+ if year <= 1968 :
5068+ self [easter (year ) + rd (weekday = MO (1 ))] = "Easter Monday"
5069+
5070+ # Labour Day
5071+ storeholiday (self , date (year , MAY , 1 ), "Labour Day" )
5072+
5073+ # Vesak Day
5074+ # date of observance is announced yearly
5075+ # https://en.wikipedia.org/wiki/Vesak#Dates_of_observance
5076+ dates_obs = {2001 : (MAY , 7 ), 2002 : (MAY , 27 ),
5077+ 2003 : (MAY , 15 ), 2004 : (JUN , 2 ), 2005 : (MAY , 23 ),
5078+ 2006 : (MAY , 12 ), 2007 : (MAY , 31 ), 2008 : (MAY , 19 ),
5079+ 2009 : (MAY , 9 ), 2010 : (MAY , 28 ), 2011 : (MAY , 17 ),
5080+ 2012 : (MAY , 5 ), 2013 : (MAY , 24 ), 2014 : (MAY , 13 ),
5081+ 2015 : (JUN , 1 ), 2016 : (MAY , 20 ), 2017 : (MAY , 10 ),
5082+ 2018 : (MAY , 29 ), 2019 : (MAY , 19 ), 2020 : (MAY , 7 )}
5083+ if year in dates_obs :
5084+ hol_date = date (year , * dates_obs [year ])
5085+ storeholiday (self , hol_date , "Vesak Day" )
5086+ else :
5087+ storeholiday (self , self .get_vesak_date (year ),
5088+ "Vesak Day* (*estimated; ~10% chance +/- 1 day)" )
5089+
5090+ # National Day
5091+ storeholiday (self , date (year , AUG , 9 ), "National Day" )
5092+
5093+ # Deepavali
5094+ # aka Diwali
5095+ # date of observance is announced yearly
5096+ dates_obs = {2001 : (NOV , 14 ), 2002 : (NOV , 3 ),
5097+ 2003 : (OCT , 23 ), 2004 : (NOV , 11 ), 2005 : (NOV , 1 ),
5098+ 2006 : (OCT , 21 ), 2007 : (NOV , 8 ), 2008 : (OCT , 27 ),
5099+ 2009 : (OCT , 17 ), 2010 : (NOV , 5 ), 2011 : (OCT , 26 ),
5100+ 2012 : (NOV , 13 ), 2013 : (NOV , 2 ), 2014 : (OCT , 22 ),
5101+ 2015 : (NOV , 10 ), 2016 : (OCT , 29 ), 2017 : (OCT , 18 ),
5102+ 2018 : (NOV , 6 ), 2019 : (OCT , 27 ), 2020 : (NOV , 14 )}
5103+ if year in dates_obs :
5104+ hol_date = date (year , * dates_obs [year ])
5105+ storeholiday (self , hol_date , "Deepavali" )
5106+ else :
5107+ storeholiday (self , self .get_s_diwali_date (year ),
5108+ "Deepavali* (*estimated; rarely on day after)" )
5109+
5110+ # Christmas Day
5111+ storeholiday (self , date (year , DEC , 25 ), "Christmas Day" )
5112+
5113+ # Boxing day (up to and including 1968)
5114+ if year <= 1968 :
5115+ storeholiday (self , date (year , DEC , 26 ), "Boxing Day" )
5116+
5117+ # Polling Day
5118+ dates_obs = {2001 : (NOV , 3 ), 2006 : (MAY , 6 ),
5119+ 2011 : (MAY , 7 ), 2015 : (SEP , 11 )}
5120+ if year in dates_obs :
5121+ self [date (year , * dates_obs [year ])] = "Polling Day"
5122+
5123+ # SG50 Public holiday
5124+ # Announced on 14 March 2015
5125+ # https://www.mom.gov.sg/newsroom/press-releases/2015/sg50-public-holiday-on-7-august-2015
5126+ if year == 2015 :
5127+ self [date (2015 , AUG , 7 )] = "SG50 Public Holiday"
5128+
5129+ # The below is used to calcluate lunar new year (i.e. Chinese new year)
5130+ # Code borrowed from Hong Kong entry as of 16-Nov-19
5131+ # Should probably be a function available to multiple countries
5132+
5133+ # Store the number of days per year from 1901 to 2099, and the number of
5134+ # days from the 1st to the 13th to store the monthly (including the month
5135+ # of the month), 1 means that the month is 30 days. 0 means the month is
5136+ # 29 days. The 12th to 15th digits indicate the month of the next month.
5137+ # If it is 0x0F, it means that there is no leap month.
5138+ g_lunar_month_days = [
5139+ 0xF0EA4 , 0xF1D4A , 0x52C94 , 0xF0C96 , 0xF1536 ,
5140+ 0x42AAC , 0xF0AD4 , 0xF16B2 , 0x22EA4 , 0xF0EA4 , # 1901-1910
5141+ 0x6364A , 0xF164A , 0xF1496 , 0x52956 , 0xF055A ,
5142+ 0xF0AD6 , 0x216D2 , 0xF1B52 , 0x73B24 , 0xF1D24 , # 1911-1920
5143+ 0xF1A4A , 0x5349A , 0xF14AC , 0xF056C , 0x42B6A ,
5144+ 0xF0DA8 , 0xF1D52 , 0x23D24 , 0xF1D24 , 0x61A4C , # 1921-1930
5145+ 0xF0A56 , 0xF14AE , 0x5256C , 0xF16B4 , 0xF0DA8 ,
5146+ 0x31D92 , 0xF0E92 , 0x72D26 , 0xF1526 , 0xF0A56 , # 1931-1940
5147+ 0x614B6 , 0xF155A , 0xF0AD4 , 0x436AA , 0xF1748 ,
5148+ 0xF1692 , 0x23526 , 0xF152A , 0x72A5A , 0xF0A6C , # 1941-1950
5149+ 0xF155A , 0x52B54 , 0xF0B64 , 0xF1B4A , 0x33A94 ,
5150+ 0xF1A94 , 0x8152A , 0xF152E , 0xF0AAC , 0x6156A , # 1951-1960
5151+ 0xF15AA , 0xF0DA4 , 0x41D4A , 0xF1D4A , 0xF0C94 ,
5152+ 0x3192E , 0xF1536 , 0x72AB4 , 0xF0AD4 , 0xF16D2 , # 1961-1970
5153+ 0x52EA4 , 0xF16A4 , 0xF164A , 0x42C96 , 0xF1496 ,
5154+ 0x82956 , 0xF055A , 0xF0ADA , 0x616D2 , 0xF1B52 , # 1971-1980
5155+ 0xF1B24 , 0x43A4A , 0xF1A4A , 0xA349A , 0xF14AC ,
5156+ 0xF056C , 0x60B6A , 0xF0DAA , 0xF1D92 , 0x53D24 , # 1981-1990
5157+ 0xF1D24 , 0xF1A4C , 0x314AC , 0xF14AE , 0x829AC ,
5158+ 0xF06B4 , 0xF0DAA , 0x52D92 , 0xF0E92 , 0xF0D26 , # 1991-2000
5159+ 0x42A56 , 0xF0A56 , 0xF14B6 , 0x22AB4 , 0xF0AD4 ,
5160+ 0x736AA , 0xF1748 , 0xF1692 , 0x53526 , 0xF152A , # 2001-2010
5161+ 0xF0A5A , 0x4155A , 0xF156A , 0x92B54 , 0xF0BA4 ,
5162+ 0xF1B4A , 0x63A94 , 0xF1A94 , 0xF192A , 0x42A5C , # 2011-2020
5163+ 0xF0AAC , 0xF156A , 0x22B64 , 0xF0DA4 , 0x61D52 ,
5164+ 0xF0E4A , 0xF0C96 , 0x5192E , 0xF1956 , 0xF0AB4 , # 2021-2030
5165+ 0x315AC , 0xF16D2 , 0xB2EA4 , 0xF16A4 , 0xF164A ,
5166+ 0x63496 , 0xF1496 , 0xF0956 , 0x50AB6 , 0xF0B5A , # 2031-2040
5167+ 0xF16D4 , 0x236A4 , 0xF1B24 , 0x73A4A , 0xF1A4A ,
5168+ 0xF14AA , 0x5295A , 0xF096C , 0xF0B6A , 0x31B54 , # 2041-2050
5169+ 0xF1D92 , 0x83D24 , 0xF1D24 , 0xF1A4C , 0x614AC ,
5170+ 0xF14AE , 0xF09AC , 0x40DAA , 0xF0EAA , 0xF0E92 , # 2051-2060
5171+ 0x31D26 , 0xF0D26 , 0x72A56 , 0xF0A56 , 0xF14B6 ,
5172+ 0x52AB4 , 0xF0AD4 , 0xF16CA , 0x42E94 , 0xF1694 , # 2061-2070
5173+ 0x8352A , 0xF152A , 0xF0A5A , 0x6155A , 0xF156A ,
5174+ 0xF0B54 , 0x4174A , 0xF1B4A , 0xF1A94 , 0x3392A , # 2071-2080
5175+ 0xF192C , 0x7329C , 0xF0AAC , 0xF156A , 0x52B64 ,
5176+ 0xF0DA4 , 0xF1D4A , 0x41C94 , 0xF0C96 , 0x8192E , # 2081-2090
5177+ 0xF0956 , 0xF0AB6 , 0x615AC , 0xF16D4 , 0xF0EA4 ,
5178+ 0x42E4A , 0xF164A , 0xF1516 , 0x22936 , # 2090-2099
5179+ ]
5180+ # Define range of years
5181+ START_YEAR , END_YEAR = 1901 , 1900 + len (g_lunar_month_days )
5182+ # 1901 The 1st day of the 1st month of the Gregorian calendar is 1901/2/19
5183+ LUNAR_START_DATE , SOLAR_START_DATE = (1901 , 1 , 1 ), date (1901 , 2 , 19 )
5184+ # The Gregorian date for December 30, 2099 is 2100/2/8
5185+ LUNAR_END_DATE , SOLAR_END_DATE = (2099 , 12 , 30 ), date (2100 , 2 , 18 )
5186+
5187+ def get_leap_month (self , lunar_year ):
5188+ return (self .g_lunar_month_days [lunar_year - self .START_YEAR ] >> 16 ) \
5189+ & 0x0F
5190+
5191+ def lunar_month_days (self , lunar_year , lunar_month ):
5192+ return 29 + ((self .g_lunar_month_days [lunar_year - self .START_YEAR ] >>
5193+ lunar_month ) & 0x01 )
5194+
5195+ def lunar_year_days (self , year ):
5196+ days = 0
5197+ months_day = self .g_lunar_month_days [year - self .START_YEAR ]
5198+ for i in range (1 , 13 if self .get_leap_month (year ) == 0x0F else 14 ):
5199+ day = 29 + ((months_day >> i ) & 0x01 )
5200+ days += day
5201+ return days
5202+
5203+ # Calculate Gregorian date of lunar new year
5204+ def get_lunar_n_y_date (self , year ):
5205+ span_days = 0
5206+ for y in range (self .START_YEAR , year ):
5207+ span_days += self .lunar_year_days (y )
5208+ leap_month = self .get_leap_month (year )
5209+ for m in range (1 , 1 + (1 > leap_month )):
5210+ span_days += self .lunar_month_days (year , m )
5211+ return self .SOLAR_START_DATE + timedelta (span_days )
5212+
5213+ # Estimate Gregorian date of Vesak
5214+ def get_vesak_date (self , year ):
5215+ span_days = 0
5216+ for y in range (self .START_YEAR , year ):
5217+ span_days += self .lunar_year_days (y )
5218+ leap_month = self .get_leap_month (year )
5219+ for m in range (1 , 4 + (4 > leap_month )):
5220+ span_days += self .lunar_month_days (year , m )
5221+ span_days += 14
5222+ return (self .SOLAR_START_DATE + timedelta (span_days ))
5223+
5224+ # Estimate Gregorian date of Southern India Diwali
5225+ def get_s_diwali_date (self , year ):
5226+ span_days = 0
5227+ for y in range (self .START_YEAR , year ):
5228+ span_days += self .lunar_year_days (y )
5229+ leap_month = self .get_leap_month (year )
5230+ for m in range (1 , 10 + (10 > leap_month )):
5231+ span_days += self .lunar_month_days (year , m )
5232+ span_days -= 2
5233+ return (self .SOLAR_START_DATE + timedelta (span_days ))
5234+
5235+ # Estimate Gregorian date(s) of Hara Rasa Puasa
5236+ def get_hrp_date (self , year ):
5237+ try :
5238+ from hijri_converter import convert
5239+ except ImportError :
5240+ import warnings
5241+
5242+ def warning_on_one_line (message , category , filename ,
5243+ lineno , file = None , line = None ):
5244+ return filename + ': ' + str (message ) + '\n '
5245+ warnings .formatwarning = warning_on_one_line
5246+ warnings .warn ("Hari Raja Puasa is missing." +
5247+ "To estimate, install hijri-converter library" )
5248+ warnings .warn ("pip install -U hijri-converter" )
5249+ warnings .warn ("(see https://hijri-converter.readthedocs.io/ )" )
5250+ return []
5251+ Hyear = convert .Gregorian (year , 1 , 1 ).to_hijri ().datetuple ()[0 ]
5252+ hrps = []
5253+ hrps .append (convert .Hijri (Hyear - 1 , 10 , 1 ).to_gregorian ())
5254+ hrps .append (convert .Hijri (Hyear , 10 , 1 ).to_gregorian ())
5255+ hrps .append (convert .Hijri (Hyear + 1 , 10 , 1 ).to_gregorian ())
5256+ hrp_dates = []
5257+ for hrp in hrps :
5258+ if hrp .year == year :
5259+ hrp_dates .append (date (* hrp .datetuple ()))
5260+ return hrp_dates
5261+
5262+ # Estimate Gregorian date(s) of Hara Rasa Haji
5263+ def get_hrh_date (self , year ):
5264+ try :
5265+ from hijri_converter import convert
5266+ except ImportError :
5267+ import warnings
5268+
5269+ def warning_on_one_line (message , category , filename , lineno ,
5270+ file = None , line = None ):
5271+ return filename + ': ' + str (message ) + '\n '
5272+ warnings .formatwarning = warning_on_one_line
5273+ warnings .warn ("Hari Raja Haji is missing." +
5274+ "To estimate, install hijri-converter library" )
5275+ warnings .warn ("pip install -U hijri-converter" )
5276+ warnings .warn ("(see https://hijri-converter.readthedocs.io/ )" )
5277+ return []
5278+ Hyear = convert .Gregorian (year , 1 , 1 ).to_hijri ().datetuple ()[0 ]
5279+ hrhs = []
5280+ hrhs .append (convert .Hijri (Hyear - 1 , 12 , 10 ).to_gregorian ())
5281+ hrhs .append (convert .Hijri (Hyear , 12 , 10 ).to_gregorian ())
5282+ hrhs .append (convert .Hijri (Hyear + 1 , 12 , 10 ).to_gregorian ())
5283+ hrh_dates = []
5284+ for hrh in hrhs :
5285+ if hrh .year == year :
5286+ hrh_dates .append (date (* hrh .datetuple ()))
5287+ return hrh_dates
5288+
5289+
5290+ class SG (Singapore ):
5291+ pass
0 commit comments