debian-workalendar/workalendar/tests/test_core.py

727 lines
24 KiB
Python

from datetime import date
from datetime import datetime
from unittest import TestCase
import pandas
from . import CoreCalendarTest
from ..core import (
MON, TUE, THU, FRI, WED, SAT, SUN,
Calendar, LunarMixin, WesternCalendar,
IslamicMixin, JalaliMixin
)
from ..exceptions import UnsupportedDateType
class CalendarTest(CoreCalendarTest):
def test_private_variables(self):
self.assertTrue(hasattr(self.cal, '_holidays'))
private_holidays = self.cal._holidays
self.assertTrue(isinstance(private_holidays, dict))
self.cal.holidays(2011)
self.cal.holidays(2012)
private_holidays = self.cal._holidays
self.assertTrue(isinstance(private_holidays, dict))
self.assertIn(2011, self.cal._holidays)
self.assertIn(2012, self.cal._holidays)
def test_year(self):
holidays = self.cal.holidays()
self.assertTrue(isinstance(holidays, (tuple, list)))
self.assertEquals(self.cal._holidays[self.year], holidays)
def test_another_year(self):
holidays = self.cal.holidays(2011)
self.assertTrue(isinstance(holidays, (tuple, list)))
self.assertEquals(self.cal._holidays[2011], holidays)
def test_is_working_day(self):
self.assertRaises(
NotImplementedError,
self.cal.is_working_day, date(2012, 1, 1))
def test_nth_weekday(self):
# first monday in january 2013
self.assertEquals(
Calendar.get_nth_weekday_in_month(2013, 1, MON),
date(2013, 1, 7)
)
# second monday in january 2013
self.assertEquals(
Calendar.get_nth_weekday_in_month(2013, 1, MON, 2),
date(2013, 1, 14)
)
# let's test the limits
# Jan 1st is a TUE
self.assertEquals(
Calendar.get_nth_weekday_in_month(2013, 1, TUE),
date(2013, 1, 1)
)
# There's no 6th MONday
self.assertEquals(
Calendar.get_nth_weekday_in_month(2013, 1, MON, 6),
None
)
def test_nth_weekday_start(self):
# first thursday after 18th april
start = date(2013, 4, 18)
self.assertEquals(
Calendar.get_nth_weekday_in_month(2013, 4, THU, start=start),
date(2013, 4, 18)
)
# first friday after 18th april
start = date(2013, 4, 18)
self.assertEquals(
Calendar.get_nth_weekday_in_month(2013, 4, FRI, start=start),
date(2013, 4, 19)
)
def test_last_weekday(self):
# last monday in january 2013
self.assertEquals(
Calendar.get_last_weekday_in_month(2013, 1, MON),
date(2013, 1, 28)
)
# last thursday
self.assertEquals(
Calendar.get_last_weekday_in_month(2013, 1, THU),
date(2013, 1, 31)
)
def test_get_next_weekday_after(self):
# the first monday after Apr 1 2015
self.assertEquals(
Calendar.get_first_weekday_after(date(2015, 4, 1), MON),
date(2015, 4, 6)
)
# the first tuesday after Apr 14 2015
self.assertEquals(
Calendar.get_first_weekday_after(date(2015, 4, 14), TUE),
date(2015, 4, 14)
)
class LunarCalendarTest(TestCase):
def test_lunar_new_year(self):
self.assertEquals(
LunarMixin.lunar(2014, 1, 1),
date(2014, 1, 31)
)
class MockCalendar(Calendar):
def holidays(self, year=None):
return tuple((
(date(year, 12, 25), 'Christmas'),
(date(year, 1, 1), 'New year'),
))
def get_weekend_days(self):
return [] # no week-end, yes, it's sad
class MockCalendarTest(CoreCalendarTest):
cal_class = MockCalendar
def test_holidays_set(self):
self.assertIn(
date(self.year, 12, 25), self.cal.holidays_set(self.year))
self.assertIn(
date(self.year, 1, 1), self.cal.holidays_set(self.year))
def test_sorted_dates(self):
holidays = list(self.cal.holidays(self.year))
day, label = holidays.pop()
for next_day, label in holidays:
self.assertTrue(day <= next_day)
day = next_day
def test_add_workingdays_simple(self):
# day is out of non-working-day
self.assertEquals(
self.cal.add_working_days(date(self.year, 12, 20), 0),
date(self.year, 12, 20)
)
self.assertEquals(
self.cal.add_working_days(date(self.year, 12, 20), 1),
date(self.year, 12, 21)
)
def test_add_workingdays_on_holiday(self):
# day is in holidays
self.assertEquals(
self.cal.add_working_days(date(self.year, 12, 25), 0),
date(self.year, 12, 25)
)
self.assertEquals(
self.cal.add_working_days(date(self.year, 12, 24), 1),
date(self.year, 12, 26)
)
self.assertEquals(
self.cal.add_working_days(date(self.year, 12, 24), 2),
date(self.year, 12, 27)
)
def test_add_workingdays_span(self):
day = date(self.year, 12, 20)
# since this calendar has no weekends, we'll just have a 2-day-shift
self.assertEquals(
self.cal.add_working_days(day, 20),
date(self.year + 1, 1, 11)
)
def test_add_working_days_exceptions(self):
day = date(self.year, 12, 20)
christmas = date(self.year, 12, 25)
boxing = date(self.year, 12, 26)
# exceptional workday
self.assertEquals(
self.cal.add_working_days(day, 20, extra_working_days=[christmas]),
date(self.year + 1, 1, 10)
)
# exceptional holiday + exceptional workday
self.assertEquals(
self.cal.add_working_days(day, 20,
extra_working_days=[christmas],
extra_holidays=[boxing]),
date(self.year + 1, 1, 11)
)
def test_add_exceptions(self):
december_20th = date(self.year, 12, 20)
christmas = date(self.year, 12, 25)
# target_working_day *is* a working day
target_working_day = self.cal.add_working_days(december_20th, 1)
# Add extra working days
extra_working_days = [christmas]
# add extra holidays
extra_holidays = [target_working_day]
self.assertFalse(self.cal.is_working_day(christmas))
self.assertTrue(
self.cal.is_working_day(christmas,
extra_working_days=extra_working_days))
self.assertTrue(self.cal.is_working_day(target_working_day))
self.assertFalse(
self.cal.is_working_day(target_working_day,
extra_holidays=extra_holidays))
# test is_holiday
self.assertTrue(self.cal.is_holiday(christmas))
def test_get_holiday_label(self):
self.assertEqual(
self.cal.get_holiday_label(date(2014, 1, 1)), 'New year')
self.assertIsNone(
self.cal.get_holiday_label(date(2014, 1, 2)))
def test_add_working_days_backwards(self):
day = date(self.year, 1, 3)
# since this calendar has no weekends, we'll just have a 1-day-shift
self.assertEquals(
self.cal.add_working_days(day, -7),
date(self.year - 1, 12, 26)
)
self.assertEquals(
self.cal.sub_working_days(day, 7),
date(self.year - 1, 12, 26)
)
# Negative argument to sub_working_days -> converted to positive.
self.assertEquals(
self.cal.sub_working_days(day, -7),
date(self.year - 1, 12, 26)
)
class IslamicMixinTest(CoreCalendarTest):
cal_class = IslamicMixin
def test_year_conversion(self):
days = self.cal.converted(2013)
self.assertEquals(len(days), 365)
class JalaliMixinTest(CoreCalendarTest):
cal_class = JalaliMixin
def test_year_conversion(self):
days = self.cal.converted(2013)
self.assertEquals(len(days), 365)
class MockChristianCalendar(WesternCalendar):
# WesternCalendar inherits from ChristianMixin
pass
class MockChristianCalendarTest(CoreCalendarTest):
cal_class = MockChristianCalendar
def test_year_2014(self):
holidays = self.cal.holidays_set(2014)
self.assertNotIn(date(2014, 1, 6), holidays) # Epiphany
self.assertNotIn(date(2014, 3, 3), holidays) # Clean Monday
self.assertNotIn(date(2014, 3, 5), holidays) # Ash Wednesday
self.assertNotIn(date(2014, 3, 25), holidays) # Annunciation
self.assertNotIn(date(2014, 4, 17), holidays) # Holy Thursday
self.assertNotIn(date(2014, 4, 18), holidays) # 'Good Friday
self.assertNotIn(date(2014, 4, 19), holidays) # Easter sat
self.assertNotIn(date(2014, 4, 20), holidays) # Easter Sun
self.assertNotIn(date(2014, 4, 21), holidays) # Easter Mon
self.assertNotIn(date(2014, 5, 29), holidays) # Ascension
self.assertNotIn(date(2014, 6, 8), holidays) # Whit Sunday
self.assertNotIn(date(2014, 6, 9), holidays) # Whit Monday
self.assertNotIn(date(2014, 6, 19), holidays) # Corp. Christi
self.assertNotIn(date(2014, 8, 15), holidays) # Assumption
self.assertNotIn(date(2014, 11, 1), holidays) # All Saints
self.assertNotIn(date(2014, 12, 8), holidays) # Imm. Conc.
self.assertNotIn(date(2014, 12, 24), holidays) # Xmas Eve
self.assertNotIn(date(2014, 12, 26), holidays) # Boxing Day
# The only Christian day that is a holiday for every calendar
self.assertIn(date(2014, 12, 25), holidays) # XMas
# Only 2 days: Jan 1st and Christmas
self.assertEquals(len(holidays), 2)
class NoWeekendCalendar(Calendar):
"""
This calendar class has no WEEKEND_DAYS and no `get_weekend_days()` method.
It has to fail when trying to fetch its weekend days / holidays
"""
class NoWeekendCalendarTest(CoreCalendarTest):
cal_class = NoWeekendCalendar
def test_weekend(self):
day = date(2017, 5, 13) # This is a Saturday
with self.assertRaises(NotImplementedError):
self.cal.is_working_day(day)
day = date(2017, 5, 17) # This is a Wednesday
with self.assertRaises(NotImplementedError):
self.cal.is_working_day(day)
class WeekendOnWednesdayCalendar(Calendar):
"""
This calendar class weekend days is on Wednesday and we don't overwrite
the `get_weekend_days()` method. It should be fine.
"""
WEEKEND_DAYS = (WED,)
class WeekendOnWednesdayCalendarTest(CoreCalendarTest):
cal_class = WeekendOnWednesdayCalendar
def test_weekend(self):
day = date(2017, 5, 13) # This is a Saturday
self.assertTrue(self.cal.is_working_day(day))
day = date(2017, 5, 17) # This is a Wednesday
self.assertFalse(self.cal.is_working_day(day))
class OverwriteGetWeekendDaysCalendar(Calendar):
"""
This calendar class has no WEEKEND_DAYS and we overwrite
its `get_weekend_days` method.
Should work.
"""
def get_weekend_days(self):
return (WED,)
class OverwriteGetWeekendDaysCalendarTest(CoreCalendarTest):
cal_class = OverwriteGetWeekendDaysCalendar
def test_weekend(self):
day = date(2017, 5, 13) # This is a Saturday
self.assertTrue(self.cal.is_working_day(day))
day = date(2017, 5, 17) # This is a Wednesday
self.assertFalse(self.cal.is_working_day(day))
class NoHolidayCalendar(Calendar):
WEEKEND_DAYS = (SAT, SUN)
class WorkingDaysDeltatest(TestCase):
def test_zero(self):
days = (
date(2018, 12, 21), # a Thursday
date(2018, 12, 23), # a Sunday
date(2018, 12, 25), # a holiday in Christian calendars
)
for day in days:
cal = NoHolidayCalendar()
self.assertEqual(cal.get_working_days_delta(day, day), 0)
cal = MockChristianCalendar()
self.assertEqual(cal.get_working_days_delta(day, day), 0)
def test_no_holidays_simple(self):
cal = NoHolidayCalendar()
day1 = date(2018, 12, 21)
day2 = date(2018, 12, 26)
delta = cal.get_working_days_delta(day1, day2)
# there are 3 days, because of the week-ends
self.assertEqual(delta, 3)
# No difference if you swap the two dates
delta = cal.get_working_days_delta(day2, day1)
self.assertEqual(delta, 3)
def test_no_holidays_over_2_years(self):
cal = NoHolidayCalendar()
day1 = date(2018, 12, 21)
day2 = date(2019, 1, 4)
delta = cal.get_working_days_delta(day1, day2)
# there are 10 days, because of the week-ends
self.assertEqual(delta, 10)
# No difference if you swap the two dates
delta = cal.get_working_days_delta(day2, day1)
self.assertEqual(delta, 10)
def test_christian_simple(self):
cal = MockChristianCalendar()
day1 = date(2018, 12, 21)
day2 = date(2018, 12, 26)
delta = cal.get_working_days_delta(day1, day2)
# there are 2 days, because of the week-end + Christmas Day
self.assertEqual(delta, 2)
# No difference if you swap the two dates
delta = cal.get_working_days_delta(day2, day1)
self.assertEqual(delta, 2)
def test_christian_over_2_years(self):
cal = MockChristianCalendar()
day1 = date(2018, 12, 21)
day2 = date(2019, 1, 4)
delta = cal.get_working_days_delta(day1, day2)
# there are 8 days, because of the week-ends + Xmas day + New Year
self.assertEqual(delta, 8)
# No difference if you swap the two dates
delta = cal.get_working_days_delta(day2, day1)
self.assertEqual(delta, 8)
def test_with_datetimes(self):
cal = MockChristianCalendar()
day1 = datetime(2018, 12, 21)
day2 = date(2018, 12, 26)
delta = cal.get_working_days_delta(day1, day2)
# there are 2 days, because of the week-end + Christmas Day
self.assertEqual(delta, 2)
# No difference if you swap the two dates
delta = cal.get_working_days_delta(day2, day1)
self.assertEqual(delta, 2)
def test_with_including_first_day(self):
# linked to #393
cal = MockChristianCalendar()
day1 = date(2018, 12, 24) # December 24th: not holiday so working day
day2 = date(2018, 12, 25) # December 25th: Christmas
# not including the first day, should return 0
delta = cal.get_working_days_delta(day1, day2)
self.assertEqual(delta, 0)
# including the first day, should return 1
delta = cal.get_working_days_delta(day1, day2, include_start=True)
self.assertEqual(delta, 1)
class NoDocstring(Calendar):
pass
class EmptyDocstring(Calendar):
""
class OneLineDocstring(Calendar):
"One line"
class MultipleLineDocstring(Calendar):
"""Multiple line
docstrings can span over multiple lines.
"""
class MultipleLineEmptyFirstDocstring(Calendar):
"""
Multiple line empty first
docstrings can span over multiple lines.
"""
class CalendarClassName(TestCase):
def test_no_docstring(self):
self.assertEqual(NoDocstring.name, "NoDocstring")
def test_empty_docstring(self):
self.assertEqual(EmptyDocstring.name, "EmptyDocstring")
def test_oneline_docstring(self):
self.assertEqual(OneLineDocstring.name, "One line")
def test_multiple_line_docstring(self):
self.assertEqual(MultipleLineDocstring.name, "Multiple line")
def test_multiple_line_empty_first_docstring(self):
self.assertEqual(
MultipleLineEmptyFirstDocstring.name, "Multiple line empty first"
)
class TestAcceptableDateTypes(CoreCalendarTest):
"""
Test cases about accepted date and datetime types.
"""
cal_class = MockCalendar
unsupported = ('hello', 1)
def test_unsupported_type_is_working_day(self):
for arg in self.unsupported:
with self.assertRaises(UnsupportedDateType):
self.cal.is_working_day(arg)
# Extra holidays optional argument
for arg in self.unsupported:
with self.assertRaises(UnsupportedDateType):
self.cal.is_working_day(
date(2018, 1, 1),
extra_holidays=[arg]
)
# Extra working days optional argument
for arg in self.unsupported:
with self.assertRaises(UnsupportedDateType):
self.cal.is_working_day(
date(2018, 1, 1),
extra_working_days=[arg]
)
def test_unsupported_type_is_holiday(self):
for arg in self.unsupported:
with self.assertRaises(UnsupportedDateType):
self.cal.is_holiday(arg)
# Extra holidays optional argument
for arg in self.unsupported:
with self.assertRaises(UnsupportedDateType):
self.cal.is_holiday(
date(2018, 1, 1),
extra_holidays=[arg]
)
def test_unsupported_type_holiday_label(self):
for arg in self.unsupported:
with self.assertRaises(UnsupportedDateType):
self.cal.get_holiday_label(arg)
def test_unsupported_type_add_sub_working_days(self):
for arg in self.unsupported:
with self.assertRaises(UnsupportedDateType):
self.cal.add_working_days(arg, 1)
for arg in self.unsupported:
with self.assertRaises(UnsupportedDateType):
self.cal.sub_working_days(arg, 1)
# Extra holidays optional argument
for arg in self.unsupported:
with self.assertRaises(UnsupportedDateType):
self.cal.add_working_days(
date(2018, 1, 1), 1,
extra_holidays=[arg]
)
# Extra working days optional argument
for arg in self.unsupported:
with self.assertRaises(UnsupportedDateType):
self.cal.add_working_days(
date(2018, 1, 1), 1,
extra_working_days=[arg]
)
# NOTE: no need to test "sub", they're calling each other.
def test_unsupported_type_find_following_working_day(self):
for arg in self.unsupported:
with self.assertRaises(UnsupportedDateType):
self.cal.find_following_working_day(arg)
def test_unsupported_type_get_nth_weekday_in_month(self):
for arg in self.unsupported:
with self.assertRaises(UnsupportedDateType):
self.cal.get_nth_weekday_in_month(2018, 1, MON, start=arg)
def test_unsupported_type_get_working_days_delta(self):
for arg in self.unsupported:
with self.assertRaises(UnsupportedDateType):
self.cal.get_working_days_delta(date(2018, 1, 1), arg)
with self.assertRaises(UnsupportedDateType):
self.cal.get_working_days_delta(arg, date(2018, 1, 1))
def test_datetime(self):
self.assertFalse(
self.cal.is_working_day(datetime(2014, 1, 1)))
self.assertTrue(
self.cal.is_holiday(datetime(2014, 1, 1)))
def test_add_working_days_datetime(self):
# datetime inside, date outside
self.assertEquals(
self.cal.add_working_days(
datetime(self.year, 12, 20, 12, 34, 56), 0),
date(self.year, 12, 20)
)
self.assertEquals(
self.cal.add_working_days(
datetime(self.year, 12, 20, 12, 34, 56), 1),
date(self.year, 12, 21)
)
# Use the `keep_datetime` option
self.assertEquals(
self.cal.add_working_days(
datetime(self.year, 12, 20, 12, 34, 56),
0, keep_datetime=True),
datetime(self.year, 12, 20, 12, 34, 56)
)
self.assertEquals(
self.cal.add_working_days(
datetime(self.year, 12, 20, 12, 34, 56),
1, keep_datetime=True),
datetime(self.year, 12, 21, 12, 34, 56)
)
def test_sub_working_days_datetime(self):
# datetime inside, date outside
self.assertEquals(
self.cal.sub_working_days(
datetime(self.year, 12, 20, 12, 34, 56), 0),
date(self.year, 12, 20)
)
self.assertEquals(
self.cal.sub_working_days(
datetime(self.year, 12, 20, 12, 34, 56), 1),
date(self.year, 12, 19)
)
# Use the `keep_datetime` option
self.assertEquals(
self.cal.sub_working_days(
datetime(self.year, 12, 20, 12, 34, 56),
0, keep_datetime=True),
datetime(self.year, 12, 20, 12, 34, 56)
)
self.assertEquals(
self.cal.sub_working_days(
datetime(self.year, 12, 20, 12, 34, 56),
1, keep_datetime=True),
datetime(self.year, 12, 19, 12, 34, 56)
)
def test_get_holiday_label_with_datetime(self):
self.assertEqual(
self.cal.get_holiday_label(datetime(2014, 1, 1)), 'New year')
self.assertIsNone(
self.cal.get_holiday_label(datetime(2014, 1, 2)))
class PandasTimestampTest(CoreCalendarTest):
cal_class = MockCalendar
def test_panda_type_is_working_day(self):
self.assertFalse(
self.cal.is_working_day(pandas.to_datetime("2018-1-1"))
)
# Extra holidays optional argument
self.assertFalse(
self.cal.is_working_day(
date(2018, 1, 2),
extra_holidays=[pandas.to_datetime("2018-1-2")]
)
)
# Extra working days optional argument
self.assertTrue(
self.cal.is_working_day(
date(2018, 1, 1),
extra_working_days=[pandas.to_datetime("2018-1-1")]
)
)
def test_panda_type_is_holiday(self):
self.assertTrue(self.cal.is_holiday(pandas.to_datetime("2018-1-1")))
# Extra holidays optional argument
self.assertTrue(
self.cal.is_holiday(
date(2018, 2, 1),
extra_holidays=[pandas.to_datetime("2018-2-1")]
)
)
def test_panda_type_holiday_label(self):
label = self.cal.get_holiday_label(pandas.to_datetime("2018-1-1"))
self.assertEqual(label, "New year")
def test_panda_type_add_sub_working_days(self):
day = pandas.to_datetime("2018-12-24")
next_day = self.cal.add_working_days(day, 1)
self.assertEqual(next_day, date(2018, 12, 26))
previous_day = self.cal.sub_working_days(next_day, 1)
self.assertEqual(previous_day, date(2018, 12, 24))
next_day = self.cal.add_working_days(
date(2018, 12, 24), 1,
extra_holidays=[pandas.to_datetime("2018-12-26")]
)
self.assertEqual(next_day, date(2018, 12, 27))
next_day = self.cal.add_working_days(
date(2018, 12, 24), 1,
extra_working_days=[pandas.to_datetime("2018-12-25")]
)
self.assertEqual(next_day, date(2018, 12, 25))
def test_unsupported_type_find_following_working_day(self):
following_day = self.cal.find_following_working_day(
pandas.to_datetime("2018-1-1")
)
# No weekend days, the next day is "today"
self.assertEqual(following_day, date(2018, 1, 1))
def test_unsupported_type_get_nth_weekday_in_month(self):
start = pandas.to_datetime("2018-1-4")
monday = self.cal.get_nth_weekday_in_month(2018, 1, MON, start=start)
self.assertEqual(monday, date(2018, 1, 8))
def test_unsupported_type_get_working_days_delta(self):
start, end = date(2018, 12, 23), pandas.to_datetime("2018-12-26")
delta = self.cal.get_working_days_delta(start, end)
self.assertEqual(delta, 2)
delta = self.cal.get_working_days_delta(end, start)
self.assertEqual(delta, 2)
start, end = pandas.to_datetime("2018-12-23"), date(2018, 12, 26)
delta = self.cal.get_working_days_delta(start, end)
self.assertEqual(delta, 2)
delta = self.cal.get_working_days_delta(end, start)
self.assertEqual(delta, 2)