Skip to content

Commit

Permalink
Securing the core function arguments to valid types
Browse files Browse the repository at this point in the history
* only standard library `date` and `datetime` types (or subtypes) are supported,
* added a new dedicated exception,
* added a paragraph in the documentation about this type support.

refs #294
  • Loading branch information
brunobord committed Nov 29, 2018
1 parent 7bc24db commit 8dd1665
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 82 deletions.
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## master (unreleased)

- Removed dependency to `PyEphem`. This package was the "Python2-compatible" library to deal with the xephem system library. Now it's obsolete, so you don't need this dual-dependency handling, because `ephem` is compatible with Python 2 & Python 3 (#296).
- Raise an exception when trying to use unsupported date/datetime types (#294).

## v3.1.1 (2018-11-17)

Expand Down
7 changes: 5 additions & 2 deletions docs/basic.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,13 @@ Let's say you want to know how many working days there are between April 2nd and
50
```

## Standard date(time) types only, please!

## Don't worry about the date types!
For your convenience, we allow both `datetime.date` and `datetime.datetime` types (and their subclasses) when using the core functions.

For your convenience, we allow both `datetime.date` and `datetime.datetime` types when using the core functions. Example:
**WARNING**: We'll only allow "dates" types coming from the Python standard library. If you're manipulating types from external library. Trying to pass a non-standard argument will result in raising a ``UnsupportedDateType`` error.

Example:

```python
>>> from datetime import date, datetime
Expand Down
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ envlist = py27,flake8,py34,py35,py36,py37,py36-cov,py27-cov

[testenv]
deps =
pandas
pytest
cov: pytest-cov

Expand Down
67 changes: 53 additions & 14 deletions workalendar/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from dateutil import easter
from lunardate import LunarDate

from .exceptions import UnsupportedDateType

MON, TUE, WED, THU, FRI, SAT, SUN = range(7)


Expand All @@ -27,6 +29,23 @@ def __get__(self, instance, owner):
return self.getter(owner)


def cleaned_date(day, keep_datetime=False):
"""
Return a "clean" date type.
* keep a `date` unchanged
* convert a datetime into a date,
* convert any "duck date" type into a date using its `date()` method.
"""
if not isinstance(day, (date, datetime)):
raise UnsupportedDateType(
"`{}` is of unsupported type ({})".format(day, type(day)))
if not keep_datetime:
if hasattr(day, 'date') and callable(day.date):
day = day.date()
return day


class Calendar(object):

FIXED_HOLIDAYS = ()
Expand Down Expand Up @@ -79,9 +98,7 @@ def holidays(self, year=None):

def get_holiday_label(self, day):
"""Return the label of the holiday, if the date is a holiday"""
# a little exception: chop the datetime type
if type(day) is datetime:
day = day.date()
day = cleaned_date(day)
return {day: label for day, label in self.holidays(day.year)
}.get(day)

Expand Down Expand Up @@ -117,9 +134,11 @@ def is_working_day(self, day,
``extra_holidays`` list.
"""
# a little exception: chop the datetime type
if type(day) is datetime:
day = day.date()
day = cleaned_date(day)
if extra_working_days:
extra_working_days = tuple(map(cleaned_date, extra_working_days))
if extra_holidays:
extra_holidays = tuple(map(cleaned_date, extra_holidays))

# Extra lists exceptions
if extra_working_days and day in extra_working_days:
Expand All @@ -139,9 +158,10 @@ def is_holiday(self, day, extra_holidays=None):
holidays, even if not in the regular calendar holidays (or weekends).
"""
# a little exception: chop the datetime type
if type(day) is datetime:
day = day.date()
day = cleaned_date(day)

if extra_holidays:
extra_holidays = tuple(map(cleaned_date, extra_holidays))

if extra_holidays and day in extra_holidays:
return True
Expand Down Expand Up @@ -170,6 +190,22 @@ def add_working_days(self, day, delta,
Please note that the ``extra_working_days`` list has priority over the
``extra_holidays`` list.
"""
day = cleaned_date(day, keep_datetime)

if extra_working_days:
for item in extra_working_days:
if not isinstance(item, (date, datetime)):
raise UnsupportedDateType(
"one of the extra_working_days item `{}` is"
" of unsupported type ({})".format(item, type(item)))

if extra_holidays:
for item in extra_holidays:
if not isinstance(item, (date, datetime)):
raise UnsupportedDateType(
"one of the extra_holidays item `{}` is"
" of unsupported type ({})".format(item, type(item)))

days = 0
temp_day = day
if type(temp_day) is datetime and not keep_datetime:
Expand Down Expand Up @@ -221,6 +257,8 @@ def find_following_working_day(self, day):
**WARNING**: this function doesn't take into account the calendar
holidays, only the days of the week and the weekend days parameters.
"""
day = cleaned_date(day)

while day.weekday() in self.get_weekend_days():
day = day + timedelta(days=1)
return day
Expand All @@ -236,6 +274,10 @@ def get_nth_weekday_in_month(year, month, weekday, n=1, start=None):
>>> Calendar.get_nth_weekday_in_month(2013, 1, MON, 2)
datetime.date(2013, 1, 14)
"""
# If start is `None` or Falsy, no need to check and clean
if start:
start = cleaned_date(start)

day = date(year, month, 1)
if start:
day = start
Expand Down Expand Up @@ -305,11 +347,8 @@ def get_working_days_delta(self, start, end):
This method should even work if your ``start`` and ``end`` arguments
are datetimes.
"""
# Sanitize date types first.
if type(start) is datetime:
start = start.date()
if type(end) is datetime:
end = end.date()
start = cleaned_date(start)
end = cleaned_date(end)

if start == end:
return 0
Expand Down
6 changes: 6 additions & 0 deletions workalendar/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@ class CalendarError(Exception):
"""
Base Calendar Error
"""


class UnsupportedDateType(CalendarError):
"""
Raised when trying to use an unsupported date/datetime type.
"""

0 comments on commit 8dd1665

Please sign in to comment.