Created
February 22, 2021 21:10
-
-
Save waylan/2cf4351c6a7cf0c39ee41e7209e78bec to your computer and use it in GitHub Desktop.
A Python object which represents a year that runs from Sept to Aug.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import datetime | |
class ServiceYear(): | |
''' | |
A year which runs from Sept to Aug. | |
Accepts a year in any format which can be converted to an integer. | |
>>> year = ServiceYear(2021) | |
Instances can also be created using the classmethods `today`, | |
`from_date` and `from_str`. | |
>>> ServiceYear.today() | |
>>> ServiceYear.from_date(datetime.date(2021, 2, 1)) | |
>>> ServiceYear.from_str('2021-02-15') | |
A service year coresponds to the year of Jan - Aug of the calendar year. | |
Therefore, the service year 2021 runs from Sept 2020 to Aug 2021. | |
ServiceYear instances work with and understand datetime date objects. | |
To confirm whether a date exists within a year use `in`: | |
>>> datetime.date(2020, 11, 24) in ServiceYear(2021) | |
True | |
>>> datetime.date(2021, 9, 1) in ServiceYear(2021) | |
False | |
Iterating over ServiceYear returns a sequence of datetime.date objects | |
of the first day of each month of the year. | |
>>> for month in ServiceYear(2021): | |
>>> print(month) | |
Add or subtract integers to get next or previous years. A new instance is | |
returned. | |
>>> thisyear = ServiceYear.today() | |
>>> nextyear = thisyear + 1 | |
>>> two_years_ago = thisyear - 2 | |
To calculate the difference (numbr of years) between two instances, | |
subtract the earlier instance from the latter instance. An integer is | |
returned. | |
>>> nextyear - two-years_ago | |
3 | |
All of the comparison operators work when comparing ServiceYear instances. | |
>>> thisyear == ServiceYear.today() | |
True | |
>>> nextyear > thisyear | |
True | |
>>> thisyear < nextyear | |
True | |
>>> thisyear != nextyear | |
True | |
>>> thisyear <= nextyear | |
True | |
>>> thisyear >= nextyear | |
False | |
The built in functions `str()` and `int()` will return a string or integer | |
respectively when passed an instance of ServiceYear. | |
ServiceYear instances are hashable by year. Hashes of seperate instances | |
of the same year are equal. Therefore, a set will contain no more than one | |
instance of any given year. | |
A ServiceYear instance contains two attributes: | |
* ServiceYear.start: datetime.date object of the first day of the year | |
(first day of Sept). | |
* ServiceYear.end: datetime.date object of the last day of the year | |
(last day of Aug). | |
''' | |
def __init__(self, year): | |
self._year = int(year) | |
self.start = datetime.date(self._year - 1, 9, 1) | |
self.end = datetime.date(self._year, 8, 31) | |
def __contains__(self, date): | |
return date >= self.start and date <= self.end | |
def __iter__(self): | |
for m in range(9, 13): | |
yield datetime.date(int(self) - 1, m, 1) | |
for m in range(1, 9): | |
yield datetime.date(int(self), m, 1) | |
def __add__(self, other): | |
return self.__class__(int(self) + other) | |
def __sub__(self, other): | |
if isinstance(other, ServiceYear): | |
return int(self) - int(other) | |
return self.__class__(int(self) - other) | |
def __str__(self): | |
return str(int(self)) | |
def __repr__(self): | |
return f'{self.__class__.__name__}({self})' | |
def __int__(self): | |
return self._year | |
def __hash__(self): | |
return hash((int(self), self.__class__.__name__)) | |
def __eq__(self, other): | |
return isinstance(other, ServiceYear) and int(self) == int(other) | |
def __lt__(self, other): | |
if not isinstance(other, ServiceYear): | |
return NotImplemented | |
return int(self) < int(other) | |
def __le__(self, other): | |
return self == other or self < other | |
def __gt__(self, other): | |
if not isinstance(other, ServiceYear): | |
return NotImplemented | |
return int(self) > int(other) | |
def __ge__(self, other): | |
return self == other or self > other | |
def iter_strings(self, format='%Y-%m'): | |
''' Return an iterator of months as strings using format. ''' | |
return [m.strftime(format) for m in self] | |
@classmethod | |
def from_date(cls, date): | |
''' Return a new ServiceYear instance based on year of given date. ''' | |
if date.month > 8: | |
return cls(date.year + 1) | |
return cls(date.year) | |
@classmethod | |
def today(cls): | |
''' Return a new ServiceYear instance based on year of today's date. ''' | |
return cls.from_date(datetime.date.today()) | |
@classmethod | |
def from_str(cls, date_str, format='%Y-%m-%d'): | |
''' Return a new ServiceYear instance based on year of given date string. ''' | |
return cls.from_date(datetime.datetime.strptime(date_str, format)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import unittest | |
import datetime | |
from service_year import ServiceYear | |
class TestServiceYear(unittest.TestCase): | |
def test_service_year_from_int(self): | |
year = ServiceYear(2021) | |
self.assertIsInstance(year, ServiceYear) | |
self.assertEqual(year._year, 2021) | |
self.assertEqual(year.start, datetime.date(2020, 9, 1)) | |
self.assertEqual(year.end, datetime.date(2021, 8, 31)) | |
self.assertIn(datetime.date(2020, 11, 15), year) | |
self.assertIn(datetime.date(2021, 5, 15), year) | |
self.assertNotIn(datetime.date(2020, 8, 31), year) | |
self.assertNotIn(datetime.date(2021, 9, 1), year) | |
self.assertEqual(len(list(year)), 12) | |
self.assertEqual(str(year), '2021') | |
self.assertEqual(repr(year), 'ServiceYear(2021)') | |
self.assertEqual(int(year), 2021) | |
self.assertEqual(year, ServiceYear(2021)) | |
self.assertNotEqual(year, ServiceYear(2022)) | |
self.assertListEqual( | |
year.iter_strings(), | |
['2020-09', '2020-10', '2020-11', '2020-12', '2021-01', '2021-02', | |
'2021-03', '2021-04', '2021-05', '2021-06', '2021-07', '2021-08'] | |
) | |
def test_service_year_from_str(self): | |
year = ServiceYear('2015') | |
self.assertIsInstance(year, ServiceYear) | |
self.assertEqual(year._year, 2015) | |
self.assertEqual(year.start, datetime.date(2014, 9, 1)) | |
self.assertEqual(year.end, datetime.date(2015, 8, 31)) | |
self.assertIn(datetime.date(2014, 11, 15), year) | |
self.assertIn(datetime.date(2015, 5, 15), year) | |
self.assertNotIn(datetime.date(2014, 8, 31), year) | |
self.assertNotIn(datetime.date(2015, 9, 1), year) | |
self.assertEqual(len(list(year)), 12) | |
self.assertEqual(str(year), '2015') | |
self.assertEqual(repr(year), 'ServiceYear(2015)') | |
self.assertEqual(int(year), 2015) | |
self.assertListEqual( | |
year.iter_strings(), | |
['2014-09', '2014-10', '2014-11', '2014-12', '2015-01', '2015-02', | |
'2015-03', '2015-04', '2015-05', '2015-06', '2015-07', '2015-08'] | |
) | |
def test_service_year_from_date(self): | |
year = ServiceYear.from_date(datetime.date(2025, 10, 12)) | |
self.assertIsInstance(year, ServiceYear) | |
self.assertEqual(year._year, 2026) | |
self.assertEqual(year.start, datetime.date(2025, 9, 1)) | |
self.assertEqual(year.end, datetime.date(2026, 8, 31)) | |
self.assertIn(datetime.date(2025, 11, 15), year) | |
self.assertIn(datetime.date(2026, 5, 15), year) | |
self.assertNotIn(datetime.date(2025, 8, 31), year) | |
self.assertNotIn(datetime.date(2026, 9, 1), year) | |
self.assertEqual(len(list(year)), 12) | |
self.assertEqual(str(year), '2026') | |
self.assertEqual(repr(year), 'ServiceYear(2026)') | |
self.assertEqual(int(year), 2026) | |
self.assertListEqual( | |
year.iter_strings(), | |
['2025-09', '2025-10', '2025-11', '2025-12', '2026-01', '2026-02', | |
'2026-03', '2026-04', '2026-05', '2026-06', '2026-07', '2026-08'] | |
) | |
def test_service_year_today(self): | |
year = ServiceYear.today() | |
today = datetime.date.today() | |
if today.month > 8: | |
thisyear = today.year + 1 | |
else: | |
thisyear = today.year | |
self.assertIsInstance(year, ServiceYear) | |
self.assertEqual(year._year, thisyear) | |
self.assertEqual(year.start, datetime.date(thisyear - 1, 9, 1)) | |
self.assertEqual(year.end, datetime.date(thisyear, 8, 31)) | |
def test_service_year_from_date_format_default(self): | |
year = ServiceYear.from_str('2026-01-15') | |
self.assertIsInstance(year, ServiceYear) | |
self.assertEqual(year._year, 2026) | |
self.assertEqual(year.start, datetime.date(2025, 9, 1)) | |
self.assertEqual(year.end, datetime.date(2026, 8, 31)) | |
self.assertIn(datetime.date(2025, 11, 15), year) | |
self.assertIn(datetime.date(2026, 5, 15), year) | |
self.assertNotIn(datetime.date(2025, 8, 31), year) | |
self.assertNotIn(datetime.date(2026, 9, 1), year) | |
self.assertEqual(len(list(year)), 12) | |
self.assertEqual(str(year), '2026') | |
self.assertEqual(repr(year), 'ServiceYear(2026)') | |
self.assertEqual(int(year), 2026) | |
self.assertListEqual( | |
year.iter_strings(), | |
['2025-09', '2025-10', '2025-11', '2025-12', '2026-01', '2026-02', | |
'2026-03', '2026-04', '2026-05', '2026-06', '2026-07', '2026-08'] | |
) | |
def test_service_year_from_date_format_custom(self): | |
year = ServiceYear.from_str('15-01-2026', format='%d-%m-%Y') | |
self.assertIsInstance(year, ServiceYear) | |
self.assertEqual(year._year, 2026) | |
self.assertEqual(year.start, datetime.date(2025, 9, 1)) | |
self.assertEqual(year.end, datetime.date(2026, 8, 31)) | |
self.assertIn(datetime.date(2025, 11, 15), year) | |
self.assertIn(datetime.date(2026, 5, 15), year) | |
self.assertNotIn(datetime.date(2025, 8, 31), year) | |
self.assertNotIn(datetime.date(2026, 9, 1), year) | |
self.assertEqual(len(list(year)), 12) | |
self.assertEqual(str(year), '2026') | |
self.assertEqual(repr(year), 'ServiceYear(2026)') | |
self.assertEqual(int(year), 2026) | |
self.assertListEqual( | |
year.iter_strings(), | |
['2025-09', '2025-10', '2025-11', '2025-12', '2026-01', '2026-02', | |
'2026-03', '2026-04', '2026-05', '2026-06', '2026-07', '2026-08'] | |
) | |
def test_service_year_add(self): | |
year = ServiceYear(2019) + 2 | |
self.assertIsInstance(year, ServiceYear) | |
self.assertEqual(year._year, 2021) | |
self.assertEqual(year.start, datetime.date(2020, 9, 1)) | |
self.assertEqual(year.end, datetime.date(2021, 8, 31)) | |
self.assertIn(datetime.date(2020, 11, 15), year) | |
self.assertIn(datetime.date(2021, 5, 15), year) | |
self.assertNotIn(datetime.date(2020, 8, 31), year) | |
self.assertNotIn(datetime.date(2021, 9, 1), year) | |
self.assertEqual(len(list(year)), 12) | |
self.assertEqual(str(year), '2021') | |
self.assertEqual(repr(year), 'ServiceYear(2021)') | |
self.assertEqual(int(year), 2021) | |
self.assertListEqual( | |
year.iter_strings(), | |
['2020-09', '2020-10', '2020-11', '2020-12', '2021-01', '2021-02', | |
'2021-03', '2021-04', '2021-05', '2021-06', '2021-07', '2021-08'] | |
) | |
def test_service_year_subtract(self): | |
year = ServiceYear(2022) - 1 | |
self.assertIsInstance(year, ServiceYear) | |
self.assertEqual(year._year, 2021) | |
self.assertEqual(year.start, datetime.date(2020, 9, 1)) | |
self.assertEqual(year.end, datetime.date(2021, 8, 31)) | |
self.assertIn(datetime.date(2020, 11, 15), year) | |
self.assertIn(datetime.date(2021, 5, 15), year) | |
self.assertNotIn(datetime.date(2020, 8, 31), year) | |
self.assertNotIn(datetime.date(2021, 9, 1), year) | |
self.assertEqual(len(list(year)), 12) | |
self.assertEqual(str(year), '2021') | |
self.assertEqual(repr(year), 'ServiceYear(2021)') | |
self.assertEqual(int(year), 2021) | |
self.assertListEqual( | |
year.iter_strings(), | |
['2020-09', '2020-10', '2020-11', '2020-12', '2021-01', '2021-02', | |
'2021-03', '2021-04', '2021-05', '2021-06', '2021-07', '2021-08'] | |
) | |
def test_service_year_diff(self): | |
year = ServiceYear.today() | |
self.assertIsInstance(year - year, int) | |
self.assertEqual((year + 1) - year, 1) | |
self.assertEqual((year + 15) - year, 15) | |
self.assertEqual(year - (year + 1), -1) | |
self.assertEqual(year - year, 0) | |
def test_service_year_comparisons(self): | |
thisyear = ServiceYear.today() | |
nextyear = thisyear + 1 | |
lastyear = thisyear - 1 | |
self.assertTrue(thisyear == ServiceYear.today()) | |
self.assertFalse(thisyear == datetime.date.today().year) | |
self.assertFalse(thisyear == nextyear) | |
self.assertTrue(thisyear != nextyear) | |
self.assertFalse(thisyear != ServiceYear.today()) | |
self.assertTrue(thisyear > lastyear) | |
self.assertFalse(thisyear > nextyear) | |
self.assertTrue(thisyear >= ServiceYear.today()) | |
self.assertTrue(thisyear >= lastyear) | |
self.assertFalse(thisyear >= nextyear) | |
self.assertTrue(thisyear < nextyear) | |
self.assertFalse(thisyear < lastyear) | |
self.assertTrue(thisyear <= ServiceYear.today()) | |
self.assertTrue(thisyear <= nextyear) | |
self.assertFalse(thisyear <= lastyear) | |
yearlist = [nextyear, lastyear, thisyear] | |
self.assertNotEqual( | |
yearlist, | |
[lastyear, thisyear, nextyear] | |
) | |
yearlist.sort() | |
self.assertListEqual( | |
yearlist, | |
[lastyear, thisyear, nextyear] | |
) | |
def test_service_year_hash(self): | |
thisyear = ServiceYear.today() | |
nextyear = thisyear + 1 | |
lastyear = thisyear - 1 | |
self.assertEqual(hash(thisyear), hash(ServiceYear.today())) | |
self.assertNotEqual(hash(thisyear), hash(nextyear)) | |
self.assertEqual(len({thisyear, thisyear, nextyear, lastyear}), 3) | |
self.assertEqual(len({thisyear, datetime.date.today().year}), 2) | |
if __name__ == '__main__': | |
unittest.main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment