Skip to content

Commit

Permalink
Add Calendar and CalendarDateRange for real date types (#348)
Browse files Browse the repository at this point in the history
Date and DateRange as kepts for hybrid date/datetime for compatibility.
  • Loading branch information
randomstuff authored and philippjfr committed Aug 2, 2019
1 parent 4d6d346 commit 44a95ca
Show file tree
Hide file tree
Showing 6 changed files with 324 additions and 9 deletions.
60 changes: 53 additions & 7 deletions param/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1812,7 +1812,7 @@ def get_range(self):

class Date(Number):
"""
Date parameter of datetime type.
Date parameter of datetime or date type.
"""

def __init__(self, default=None, **kwargs):
Expand All @@ -1827,10 +1827,35 @@ def _validate(self, val):
return

if not isinstance(val, dt_types) and not (self.allow_None and val is None):
raise ValueError("Date '%s' only takes datetime types."%self.name)
raise ValueError("Date '%s' only takes datetime and date types."%self.name)

if self.step is not None and not isinstance(self.step, dt_types):
raise ValueError("Step parameter can only be None or a datetime type")
raise ValueError("Step parameter can only be None, a datetime or datetime type")

self._checkBounds(val)


class CalendarDate(Number):
"""
CalendarDate parameter of date type.
"""

def __init__(self, default=None, **kwargs):
super(CalendarDate, self).__init__(default=default, **kwargs)

def _validate(self, val):
"""
Checks that the value is numeric and that it is within the hard
bounds; if not, an exception is raised.
"""
if self.allow_None and val is None:
return

if not isinstance(val, dt.date) and not (self.allow_None and val is None):
raise ValueError("CalendarDate '%s' only takes datetime types."%self.name)

if self.step is not None and not isinstance(self.step, dt.date):
raise ValueError("Step parameter can only be None or a date type")

self._checkBounds(val)

Expand Down Expand Up @@ -1929,10 +1954,9 @@ def _checkBounds(self, val):

class DateRange(Range):
"""
A date range specified as (start_date, end_date).
A datetime or date range specified as (start, end).
Dates must be specified as datetime-like types (see
param.dt_types).
Bounds must be specified as datetime or date types (see param.dt_types).
"""
def _validate(self, val):
if self.allow_None and val is None:
Expand All @@ -1944,9 +1968,31 @@ def _validate(self, val):

start, end = val
if not end >= start:
raise ValueError("DateRange '%s': end date %s is before start date %s."%(self.name,val[1],val[0]))
raise ValueError("DateRange '%s': end datetime %s is before start datetime %s."%(self.name,val[1],val[0]))

# Calling super(DateRange, self)._check(val) would also check
# values are numeric, which is redundant, so just call
# _checkBounds().
self._checkBounds(val)


class CalendarDateRange(Range):
"""
A date range specified as (start_date, end_date).
"""
def _validate(self, val):
if self.allow_None and val is None:
return

for n in val:
if not isinstance(n, dt.date):
raise ValueError("CalendarDateRange '%s' only takes date types: %s"%(self.name,val))

start, end = val
if not end >= start:
raise ValueError("CalendarDateRange '%s': end date %s is before start date %s."%(self.name,val[1],val[0]))

# Calling super(CalendarDateRange, self)._check(val) would also check
# values are numeric, which is redundant, so just call
# _checkBounds().
self._checkBounds(val)
53 changes: 53 additions & 0 deletions tests/API0/testcalendardateparam.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""
Unit test for CalendarDate parameters.
"""

import unittest
import datetime as dt
import param


class TestDateTimeParameters(unittest.TestCase):

def test_initialization_out_of_bounds(self):
try:
class Q(param.Parameterized):
q = param.Date(dt.date(2017,2,27),
bounds=(dt.date(2017,2,1),
dt.date(2017,2,26)))
except ValueError:
pass
else:
raise AssertionError("No exception raised on out-of-bounds date")

def test_set_out_of_bounds(self):
class Q(param.Parameterized):
q = param.Date(bounds=(dt.date(2017,2,1),
dt.date(2017,2,26)))
try:
Q.q = dt.date(2017,2,27)
except ValueError:
pass
else:
raise AssertionError("No exception raised on out-of-bounds date")

def test_set_exclusive_out_of_bounds(self):
class Q(param.Parameterized):
q = param.Date(bounds=(dt.date(2017,2,1),
dt.date(2017,2,26)),
inclusive_bounds=(True, False))
try:
Q.q = dt.date(2017,2,26)
except ValueError:
pass
else:
raise AssertionError("No exception raised on out-of-bounds date")

def test_get_soft_bounds(self):
q = param.Date(dt.date(2017,2,25),
bounds=(dt.date(2017,2,1),
dt.date(2017,2,26)),
softbounds=(dt.date(2017,2,1),
dt.date(2017,2,25)))
self.assertEqual(q.get_soft_bounds(), (dt.date(2017,2,1),
dt.date(2017,2,25)))
79 changes: 79 additions & 0 deletions tests/API0/testcalendardaterangeparam.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""
Unit tests for CalendarDateRange parameter.
"""

import unittest
import datetime as dt
import param


# Assuming tests of range parameter cover most of what's needed to
# test date range.

class TestDateTimeRange(unittest.TestCase):

bad_range = (dt.date(2017,2,27),dt.date(2017,2,26))

def test_wrong_type_default(self):
try:
class Q(param.Parameterized):
a = param.CalendarDateRange(default=(1.0,2.0))
except ValueError:
pass
else:
raise AssertionError("Bad date type was accepted.")

def test_wrong_type_init(self):
class Q(param.Parameterized):
a = param.CalendarDateRange()

try:
Q(a=self.bad_range)
except ValueError:
pass
else:
raise AssertionError("Bad date type was accepted.")

def test_wrong_type_set(self):
class Q(param.Parameterized):
a = param.CalendarDateRange()
q = Q()

try:
q.a = self.bad_range
except ValueError:
pass
else:
raise AssertionError("Bad date type was accepted.")

def test_start_before_end_default(self):
try:
class Q(param.Parameterized):
a = param.CalendarDateRange(default=self.bad_range)
except ValueError:
pass
else:
raise AssertionError("Bad date range was accepted.")

def test_start_before_end_init(self):
class Q(param.Parameterized):
a = param.CalendarDateRange()

try:
Q(a=self.bad_range)
except ValueError:
pass
else:
raise AssertionError("Bad date range was accepted.")

def test_start_before_end_set(self):
class Q(param.Parameterized):
a = param.CalendarDateRange()

q = Q()
try:
q.a = self.bad_range
except ValueError:
pass
else:
raise AssertionError("Bad date range was accepted.")
54 changes: 54 additions & 0 deletions tests/API1/testcalendardateparam.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""
Unit test for CalendarDate parameters.
"""


import datetime as dt
import param
from . import API1TestCase


class TestDateTimeParameters(API1TestCase):

def test_initialization_out_of_bounds(self):
try:
class Q(param.Parameterized):
q = param.CalendarDate(dt.date(2017,2,27),
bounds=(dt.date(2017,2,1),
dt.date(2017,2,26)))
except ValueError:
pass
else:
raise AssertionError("No exception raised on out-of-bounds date")

def test_set_out_of_bounds(self):
class Q(param.Parameterized):
q = param.CalendarDate(bounds=(dt.date(2017,2,1),
dt.date(2017,2,26)))
try:
Q.q = dt.date(2017,2,27)
except ValueError:
pass
else:
raise AssertionError("No exception raised on out-of-bounds date")

def test_set_exclusive_out_of_bounds(self):
class Q(param.Parameterized):
q = param.CalendarDate(bounds=(dt.date(2017,2,1),
dt.date(2017,2,26)),
inclusive_bounds=(True, False))
try:
Q.q = dt.date(2017,2,26)
except ValueError:
pass
else:
raise AssertionError("No exception raised on out-of-bounds date")

def test_get_soft_bounds(self):
q = param.CalendarDate(dt.date(2017,2,25),
bounds=(dt.date(2017,2,1),
dt.date(2017,2,26)),
softbounds=(dt.date(2017,2,1),
dt.date(2017,2,25)))
self.assertEqual(q.get_soft_bounds(), (dt.date(2017,2,1),
dt.date(2017,2,25)))
78 changes: 78 additions & 0 deletions tests/API1/testcalendardaterangeparam.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""
Unit tests for CalendarDateRange parameter.
"""

import datetime as dt
import param
from . import API1TestCase

# Assuming tests of range parameter cover most of what's needed to
# test date range.

class TestDateTimeRange(API1TestCase):

bad_range = (dt.date(2017,2,27),dt.date(2017,2,26))

def test_wrong_type_default(self):
try:
class Q(param.Parameterized):
a = param.CalendarDateRange(default=(1.0,2.0))
except ValueError:
pass
else:
raise AssertionError("Bad date type was accepted.")

def test_wrong_type_init(self):
class Q(param.Parameterized):
a = param.CalendarDateRange()

try:
Q(a=self.bad_range)
except ValueError:
pass
else:
raise AssertionError("Bad date type was accepted.")

def test_wrong_type_set(self):
class Q(param.Parameterized):
a = param.CalendarDateRange()
q = Q()

try:
q.a = self.bad_range
except ValueError:
pass
else:
raise AssertionError("Bad date type was accepted.")

def test_start_before_end_default(self):
try:
class Q(param.Parameterized):
a = param.CalendarDateRange(default=self.bad_range)
except ValueError:
pass
else:
raise AssertionError("Bad date range was accepted.")

def test_start_before_end_init(self):
class Q(param.Parameterized):
a = param.CalendarDateRange()

try:
Q(a=self.bad_range)
except ValueError:
pass
else:
raise AssertionError("Bad date range was accepted.")

def test_start_before_end_set(self):
class Q(param.Parameterized):
a = param.CalendarDateRange()

q = Q()
try:
q.a = self.bad_range
except ValueError:
pass
else:
raise AssertionError("Bad date range was accepted.")
9 changes: 7 additions & 2 deletions tests/API1/testnumberparameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ def test_step_invalid_type_integer_parameter(self):
with self.assertRaisesRegexp(ValueError, exception):
param.Integer(step=3.4)

def test_step_invalid_type_date_parameter(self):
exception = "Step parameter can only be None or a datetime type"
def test_step_invalid_type_datetime_parameter(self):
exception = "Step parameter can only be None, a datetime or datetime type"
with self.assertRaisesRegexp(ValueError, exception):
param.Date(dt.datetime(2017,2,27), step=3.2)

def test_step_invalid_type_date_parameter(self):
exception = "Step parameter can only be None or a date type"
with self.assertRaisesRegexp(ValueError, exception):
param.CalendarDate(dt.date(2017,2,27), step=3.2)

0 comments on commit 44a95ca

Please sign in to comment.