debian-workalendar/workalendar/europe/scotland/__init__.py

500 lines
14 KiB
Python

"""
Scotland specific module.
The main source of information is:
https://en.wikipedia.org/wiki/Public_and_bank_holidays_in_Scotland
Note on "inheritance":
* Carnoustie and Monifieth area is a subdivision of Angus
* Lanark is part of South Lanarkshire
FIXME:
* Galashiels is part of the Scottish Borders
* Hawick is part of the Scottish Borders
* Kilmarnock is probably part of AyrShire (?)
* Lanark is part of South Lanarkshire
* Linlithgow... part of N/A
* Lochaber... part of N/A
* ...
"""
# Since Scotland territories have a lot of different variations, it has become
# necessary to split this module and associated tests
from datetime import date, timedelta
import warnings
from ...core import WesternCalendar, MON, THU, FRI
from .mixins import (
SpringHolidayFirstMondayApril,
SpringHolidaySecondMondayApril,
SpringHolidayTuesdayAfterFirstMondayMay,
SpringHolidayLastMondayMay,
SpringHolidayFirstMondayJune,
VictoriaDayFourthMondayMay,
VictoriaDayLastMondayMay,
VictoriaDayFirstMondayJune,
FairHolidayLastMondayJune,
FairHolidayFirstMondayJuly,
FairHolidaySecondMondayJuly,
FairHolidayThirdMondayJuly,
FairHolidayLastMondayJuly,
FairHolidayFourthFridayJuly,
FairHolidayFirstMondayAugust,
LateSummer,
BattleStirlingBridge,
AutumnHolidayLastMondaySeptember,
AutumnHolidayFirstMondayOctober,
AutumnHolidaySecondMondayOctober,
AutumnHolidayThirdMondayOctober,
AyrGoldCup,
)
class Scotland(WesternCalendar):
"Scotland"
FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + (
(1, 2, "New Year Holiday"),
(12, 26, "Boxing Day"),
)
include_spring_holiday = False
spring_holiday_label = "Spring Holiday"
include_fair_holiday = False
include_autumn_holiday = False
include_saint_andrew = False
include_victoria_day = False
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
warnings.warn(
"""
Please bear in mind that every Scotland (sub)calendar is highly experimental.
It appeared throughout out searches that Scottish calendars have many
exceptions and somes sources of information contradicts with each other.
As a consequence, we advise our user to treat this Scotland submodule with
as much care as possible.
"""
)
def get_may_day(self, year):
"""
May Day is the first Monday in May
"""
return (
Scotland.get_nth_weekday_in_month(year, 5, MON),
"May Day"
)
def get_spring_holiday(self, year):
"""
Return spring holiday date and label.
You need to implement it as soon as the flag `include_spring_holiday`
is True.
"""
raise NotImplementedError(
"Tried to add spring holiday while "
"`get_spring_holiday` is not implemented.")
def get_fair_holiday(self, year):
"""
Return fair holiday date and label.
You need to implement it as soon as the flag `include_fair_holiday`
is True.
"""
raise NotImplementedError(
"Tried to add fair holiday while "
"`get_fair_holiday` is not implemented.")
def get_autumn_holiday(self, year):
"""
Return autumn holiday date and label.
You need to implement it as soon as the flag `include_autumn_holiday`
is True.
"""
raise NotImplementedError(
"Tried to add autumn holiday while "
"`get_autumn_holiday` is not implemented.")
def get_victoria_day(self, year):
"""
Return Victoria day date and label.
You need to implement it as soon as the flag `include_victoria_day`
is True.
"""
raise NotImplementedError(
"Tried to add autumn holiday while "
"`include_victoria_day` is not implemented.")
def get_variable_days(self, year):
days = super().get_variable_days(year)
days.append(self.get_may_day(year))
if self.include_spring_holiday:
# This method is included in the spring_holiday mixins
days.append(self.get_spring_holiday(year))
if self.include_fair_holiday:
# This method is included in the fair_holiday mixins
days.append(self.get_fair_holiday(year))
if self.include_autumn_holiday:
# This method is included in the autumn_holiday mixins
days.append(self.get_autumn_holiday(year))
if self.include_victoria_day:
# This method is included in the victoria_day mixins
days.append(self.get_victoria_day(year))
return days
def get_fixed_holidays(self, year):
days = super().get_fixed_holidays(year)
if self.include_saint_andrew:
days.append((date(year, 11, 30), "Saint Andrew's Day"))
return days
class Aberdeen(
FairHolidaySecondMondayJuly,
AutumnHolidayLastMondaySeptember,
Scotland):
"Aberdeen"
include_good_friday = True
class Angus(
SpringHolidaySecondMondayApril,
AutumnHolidayLastMondaySeptember,
Scotland):
"Angus"
include_saint_andrew = True
class Arbroath(FairHolidayThirdMondayJuly, Scotland):
"Arbroath"
class Ayr(SpringHolidayLastMondayMay, AyrGoldCup, Scotland):
"Ayr"
include_good_friday = True
include_easter_monday = True
class CarnoustieMonifieth(
SpringHolidayFirstMondayApril,
AutumnHolidayFirstMondayOctober,
Scotland):
"Carnoustie & Monifieth"
class Clydebank(
SpringHolidayTuesdayAfterFirstMondayMay,
Scotland):
"Clydebank"
class DumfriesGalloway(Scotland):
"Dumfries & Galloway"
include_good_friday = True
class Dundee(
SpringHolidayFirstMondayApril,
VictoriaDayLastMondayMay,
FairHolidayLastMondayJuly,
AutumnHolidayFirstMondayOctober,
Scotland):
"Dundee"
class EastDunbartonshire(
SpringHolidayLastMondayMay,
FairHolidayThirdMondayJuly,
AutumnHolidayLastMondaySeptember,
Scotland):
"East Dunbartonshire"
include_good_friday = True
include_easter_monday = True
class Edinburgh(Scotland):
"Edinburgh"
include_good_friday = True
include_easter_monday = True
include_spring_holiday = True
include_victoria_day = True
include_autumn_holiday = True
def get_spring_holiday(self, year):
"""
Return Spring Holiday for Edinburgh.
Set to the 3rd Monday of April, unless it falls on Easter Monday, then
it's shifted to previous week.
"""
easter = self.get_easter_monday(year)
spring_holiday = self.get_nth_weekday_in_month(year, 4, MON, 3)
if easter == spring_holiday:
spring_holiday = self.get_nth_weekday_in_month(
year, 4, MON, 2)
return (spring_holiday, self.spring_holiday_label)
def get_victoria_day(self, year):
"""
Return Victoria Day for Edinburgh.
Set to the Monday strictly before May 24th. It means that if May 24th
is a Monday, it's shifted to the week before.
"""
may_24th = date(year, 5, 24)
# Since "MON(day) == 0", it's either the difference between MON and the
# current weekday (starting at 0), or 7 days before the May 24th
shift = may_24th.weekday() or 7
victoria_day = may_24th - timedelta(days=shift)
return (victoria_day, "Victoria Day")
def get_autumn_holiday(self, year):
"""
Return Autumn Holiday for Edinburgh.
Set to the third Monday in September. Since it's the only region to
follow this rule, we won't have a Mixin associated to it.
"""
return (
self.get_nth_weekday_in_month(year, 9, MON, 3),
"Autumn Holiday"
)
class Elgin(
SpringHolidaySecondMondayApril,
FairHolidayLastMondayJune,
LateSummer,
AutumnHolidayThirdMondayOctober,
Scotland):
"Elgin"
class Falkirk(FairHolidayFirstMondayJuly, BattleStirlingBridge, Scotland):
"Falkirk"
include_good_friday = True
include_easter_monday = True
class Fife(
VictoriaDayFirstMondayJune,
FairHolidayThirdMondayJuly,
AutumnHolidayThirdMondayOctober,
Scotland):
"Fife"
include_saint_andrew = True
def get_variable_days(self, year):
days = super().get_variable_days(year)
# Special computation rule, Fife has TWO spring holidays
# 1. First Monday in April
days.append((
self.get_nth_weekday_in_month(year, 4, MON),
"Spring Holiday"
))
# 2. First Monday in June
days.append((
self.get_nth_weekday_in_month(year, 6, MON),
"Spring Holiday"
))
return days
class Galashiels(SpringHolidayFirstMondayJune, Scotland):
"Galashiels"
def get_variable_days(self, year):
days = super().get_variable_days(year)
# Adding Braw Lads Gathering, 1st Friday in July
days.append((
self.get_nth_weekday_in_month(year, 7, FRI),
"Braw Lads Gathering"
))
return days
class Glasgow(
SpringHolidayLastMondayMay,
FairHolidayThirdMondayJuly,
AutumnHolidayLastMondaySeptember,
Scotland):
"Glasgow"
include_easter_monday = True
class Hawick(Scotland):
"Hawick"
def get_variable_days(self, year):
days = super().get_variable_days(year)
# Common Riding happens on the FRI after the first MON in June
first_monday = self.get_nth_weekday_in_month(year, 6, MON)
friday = first_monday + timedelta(days=4)
# And on the saturday. Adding this one, even if SAT is already a
# non-working day.
saturday = first_monday + timedelta(days=5)
days.append((friday, "Common Riding Day 1"))
days.append((saturday, "Common Riding Day 2"))
return days
class Inverclyde(LateSummer, Scotland):
"Inverclyde"
include_good_friday = True
include_easter_monday = True
def get_variable_days(self, year):
days = super().get_variable_days(year)
# Special computation rule, Inverclyde has TWO spring holidays
# 1. First Monday in April
days.append((
self.get_last_weekday_in_month(year, 4, MON),
"Spring Holiday"
))
# 2. First Monday in June
days.append((
self.get_nth_weekday_in_month(year, 6, MON),
"Spring Holiday"
))
return days
class Inverness(
SpringHolidayFirstMondayApril,
FairHolidayFirstMondayJuly,
AutumnHolidayFirstMondayOctober,
Scotland):
"Inverness"
def get_variable_days(self, year):
days = super().get_variable_days(year)
days.append((
self.get_nth_weekday_in_month(year, 2, MON),
"Winter Holiday (February)"
))
days.append((
self.get_nth_weekday_in_month(year, 3, MON),
"Winter Holiday (March)"
))
days.append((
self.get_nth_weekday_in_month(year, 11, MON),
"Samhain Holiday"
))
return days
class Kilmarnock(AyrGoldCup, Scotland):
"Kilmarnock"
include_good_friday = True
include_easter_monday = True
class Lanark(Scotland):
"Lanark"
def get_variable_days(self, year):
days = super().get_variable_days(year)
# Lanimer is 2nd THU in June
days.append((
self.get_nth_weekday_in_month(year, 6, THU, 2),
"Lanimer Day"
))
return days
class Linlithgow(Scotland):
"Linlithgow"
def get_variable_days(self, year):
days = super().get_variable_days(year)
# Linlithgow Marches is on TUE after the 2nd THU in June
second_thursday = self.get_nth_weekday_in_month(year, 6, THU, 2)
marches_day = second_thursday + timedelta(days=5)
days.append((
marches_day,
"Linlithgow Marches"
))
return days
class Lochaber(Scotland):
"Lochaber"
def get_variable_days(self, year):
days = super().get_variable_days(year)
# Winter holiday is on the last MON of march
days.append((
self.get_last_weekday_in_month(year, 3, MON),
"Winter holiday"
))
return days
class NorthLanarkshire(
SpringHolidayLastMondayMay,
FairHolidayThirdMondayJuly,
AutumnHolidayLastMondaySeptember,
Scotland):
"North Lanarkshire"
include_easter_monday = True
class Paisley(
VictoriaDayLastMondayMay,
FairHolidayFirstMondayAugust,
AutumnHolidayLastMondaySeptember,
Scotland):
"Paisley"
include_good_friday = True
include_easter_monday = True
class Perth(
SpringHolidayFirstMondayApril,
VictoriaDayFourthMondayMay,
BattleStirlingBridge,
AutumnHolidayFirstMondayOctober,
Scotland):
"Perth"
class ScottishBorders(
SpringHolidayFirstMondayApril,
FairHolidayFourthFridayJuly,
AutumnHolidaySecondMondayOctober,
Scotland):
"Scottish Borders"
include_saint_andrew = True
class SouthLanarkshire(
SpringHolidayLastMondayMay,
FairHolidayThirdMondayJuly,
AutumnHolidayLastMondaySeptember,
Scotland):
"South Lanarkshire"
include_good_friday = True
include_easter_monday = True
class Stirling(
SpringHolidayTuesdayAfterFirstMondayMay,
BattleStirlingBridge,
Scotland):
"Stirling"
include_good_friday = True
include_easter_monday = True
class WestDunbartonshire(
AutumnHolidayLastMondaySeptember,
Scotland):
"West Dunbartonshire"
include_good_friday = True
include_easter_monday = True