Stuff for dates
For when you need some
datetime helpers but not a complete replacement for the modules.
Frankly, I love the built in
datetime module. Almost everything I need to do, I can just do with it.
However, a few things tend to creep up datetime and datetime again. Things like:
- Creating a range of dates
- Creating an unfixed date
- Checking if two datetimes are within a certain delta of one another
Here’s a short look at what’s included.
RelativeDate and RelativeDateTime
These allow you to create an unfixed
datetime instance by providing a
timedelta offset and/or factory method.
datetime.now as the default factories and both have a default offset of
rd = RelativeDate() rd.as_date() # date(2016, 7, 24) rdt = RelativeDateTime() rd.as_datetime() # datetime(2016, 7, 24, 12, 29)
However, it is also possible to provide other factories as well:
import arrow rdt = RelativeDateTime(clock=arrow.utcnow) rdt.as_datetime() # <Arrow [2016-07-24T17:34:58.970460+00:00]>
And as long as the underlying factory produces a
datetime compatible object, everything will just work. By compatible, I mean implements the
Additionally, if only a static offset from today or now is desired, you can simply provide the offset argument with a
timedelta or dateutil
relativedelta. Note that currently,
relativedelta are not interoperable.
from datetime import timedelta rd = RelativeDate(offset=timedelta(days=6)) rd.as_date() # date(2016, 7, 30)
RelativeDateTime also allow comparing against regular
datetime instances with the standard operators (==, !=, >, etc). Making these incredibly useful for quickly defining date boundaries that are defined statically (such as in a serializer or ORM model):
from datetime import timedelta, date rd = RelativeDate(offset=timedelta(days=7)) assert rd > date.today() # always true
Adding and subtracting relative instances actually operate on their offsets, rather than underlying
from datetime import timedelta rd = RelativeDate(offset=timedelta(days=1)) rd + rd == RelativeDate(offset(timedelta(days=2))) rd - rd == RelativeDate()
Some alternate constructors are provided where it makes sense, each allows passing an offset but defaults to
timedelta(), provided are:
RelativeDate.today: the default constructor
RelativeDateTime.now: the default constructor, allows passing a tzinfo object to the factory
RelativeDateTime.utcnow: factory produces UTC-based datetimes (note: these are NAIVE as it relies on the underlying
RelativeDateTime.today: the default constructor, does not allow passing a tzinfo object
For convenience sake there are also truly static constructors:
RelativeDate.fromdate: hoists a regular date into relative context
RelativeDateTime.fromdatetime: hoists a regular datetime into
RelativeDateTime.fromdate: hoists a date into a
RelativeDateTimecontext, allows passing a tzinfo object, factory looks like
Any additional static constructors, such as
datetime.strptime, can be derived from these if truly needed.
from datetime import date, time, timedelta rd = RelativeDate.fromdate(date(2016, 7, 24), offset=timedelta(days=7)) rd.as_date() # date(2016, 7, 31), always
Finally, any functionality not implemented directly in the relative instance is proxied to the underlying
A range of dates is another tool I find myself needing from time to time, however eager creation can sometimes be very expensive for a large range.
DateRange is modeled after the Python 3
range type, which has fast path lookup for membership, lazy iteration, indexing and slicing (slices return new
from datestuff import DateRange from datetime import date, timedelta dr = DateRange(start=date(2016, 1, 1), stop=date(2016, 12, 31), step=timedelta(days=7)) date(2016, 1, 8) in dr # true len(dr) # 53, yes this is correct list(dr) # [date(2016, 1, 1), date(2016, 1, 8), ...] dr == date(2016, 1, 8) # True dr[1:-1:2] == DateRange(date(2016, 1, 8), date(2016, 12, 30), step=timedelta(days=14)) # True
DateRange also allows creating an open ended range by simply omitting the stop argument. In this case, the only functionality that will not work is using
len and negative indexing/slicing (as there is no end)
DateRange does not support
relativedelta as under the hood it uses
timedelta.total_seconds for Python 2 and 3 compatiblity. This could be resolved in the future, but is unlikely.
DateRange is, however, compatible with
datetime like objects and other
timedelta like objects. Interestingly, this would apply to
RelativeDateTime as well.
Currently, the only util is
within_delta which is useful for comparing two
datetime (or like) instances within a certain delta.
from datetime import datetime, timedelta from datestuff import within_delta d1 = datetime.now() d2 = datetime.now() d1 == d2 # false within_delta(d1, d2, timedelta(seconds=1)) # true
If simple boundary checking is needed, this tool is much more light weight than either
RelativeDate. Sadly, this is another tool that cannot interoperate with
relativedelta as it and
timedelta are unorderable (at least in Python 3).