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.
These allow you to create an unfixed date or datetime instance by providing a timedelta offset and/or factory method.
By default, RelativeDate uses date.today and RelativeDateTime uses datetime.now as the default factories and both have a default offset of timedelta(0):
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 date or datetime compatible object, everything will just work. By compatible, I mean implements the date or datetime interface.
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, timedelta and relativedelta are not interoperable.
from datetime import timedelta
rd = RelativeDate(offset=timedelta(days=6))
rd.as_date() # date(2016, 7, 30)RelativeDate and RelativeDateTime also allow comparing against regular date and 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 trueAdding and subtracting relative instances actually operate on their offsets, rather than underlying date or datetime values.
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 constructorRelativeDateTime.now: the default constructor, allows passing a tzinfo object to the factoryRelativeDateTime.utcnow: factory produces UTC-based datetimes (note: these are NAIVE as it relies on the underlyingdatetime.utcnow)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 contextRelativeDateTime.fromdatetime: hoists a regular datetime intoRelativeDateTime.fromdate: hoists a date into aRelativeDateTimecontext, allows passing a tzinfo object, factory looks likedatetime.combine(the_date, time(tzinfo=tzinfo))
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), alwaysFinally, any functionality not implemented directly in the relative instance is proxied to the underlying date or datetime instance.
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.
Instead, DateRange is modeled after the Python 3 range type, which has fast path lookup for membership, lazy iteration, indexing and slicing (slices return new DateRange objects)
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[1] == date(2016, 1, 8) # True
dr[1:-1:2] == DateRange(date(2016, 1, 8), date(2016, 12, 30), step=timedelta(days=14)) # TrueDateRange 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)
Currently, 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 date and datetime like objects and other timedelta like objects. Interestingly, this would apply to RelativeDate and RelativeDateTime as well.
Currently, the only util is within_delta which is useful for comparing two date or 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)) # trueIf simple boundary checking is needed, this tool is much more light weight than either DateRange or RelativeDate. Sadly, this is another tool that cannot interoperate with relativedelta as it and timedelta are unorderable (at least in Python 3).