We all know the datetime module from the Python Standard Library. It provides lots of functions to operate on dates and times in a nice way. Still, it lacks some features here and there. There is a library that fills these gaps. It is dateutil, a library created by Gustavo Niemeyer and now maintained mainly by Paul Ganssle.
Installation
dateutil is available on PyPI: https://pypi.python.org/pypi/python-dateutil. The current version is 2.5.3. Let's install it to virtual env:
python3 -m venv pyve . pyve/bin/activate pip install python-dateutil
First, let's check if it works. Run the Python interpreter from virtual env:
python
And then import module
>>> import dateutil >>> dateutil.__version__ '2.5.3'
Now let's check what it can do for us.
How to parse a date with an unknown format?
Parsing date or time using the datetime module is straightforward. You are providing format and then your text is interpreted. It can go this way:
>>> import datetime >>> datetime.datetime.strptime('2032-04-03 13:15:54', '%Y-%m-%d %H:%M:%S') datetime.datetime(2032, 4, 3, 13, 15, 54)
Dates expressed using words could be decoded too:
>>> datetime.datetime.strptime('Sat Sep 17 23:26:08 CEST 2016', '%a %b %d %H:%M:%S %Z %Y') datetime.datetime(2016, 9, 17, 23, 26, 8)
Here, the first problem appears. Preparing format like '%Y-%m-%d %H:%M:%S' or '%a %b %d %H:%M:%S %Z %Y' can be tiresome and error-prone. For example, it is easy to use %m instead of %M. The first one means month while the second minutes. Unfortunately such mistakes can be missed because it works in some cases.
There is also another problem. What to do if we do not know the format of a date that we are reading from the input?
dateutil can solve these problems. It provides a function that tries to guess the format of input date and time. It does it with great success. Let's parse previous examples using dateutil:
>>> import dateutil.parser >>> dateutil.parser.parse('2032-04-03 13:15:54') datetime.datetime(2032, 4, 3, 13, 15, 54) >>> dateutil.parser.parse('Sat Sep 17 23:26:08 CEST 2016' datetime.datetime(2016, 9, 17, 23, 26, 8, tzinfo=tzlocal()))
This was easy. What about taking into account more complicated cases with time zones?
Dealing with time zones
datetime module does not provide much regarding time zones. It can return e.g. current local time, but it does not provide information about local time zone:
>>> datetime.datetime.now() datetime.datetime(2016, 9, 19, 6, 37, 10, 898752)
To make it time zone aware is not effortless. One can try to use pytz library but still it requires pretty much attention due to e.g. daylight saving time time zone changes. With dateutil this is quite straightforward:
>>> import dateutil.tz >>> tz = dateutil.tz.tzlocal() >>> datetime.datetime.now(tz) datetime.datetime(2016, 9, 19, 6, 41, 7, 352559, tzinfo=tzlocal()) >>> print(datetime.datetime.now(tz)) 2016-09-19 06:41:24.440190+02:00
or you can override arbitrarily time zone, e.g. "Asia/Taipei":
>>> now = datetime.datetime.now() >>> tz_taipei = dateutil.tz.gettz('Asia/Taipei') >>> now = now.replace(tzinfo=tz_taipei) >>> now datetime.datetime(2016, 9, 19, 6, 47, 8, 305221, tzinfo=tzfile('/usr/share/zoneinfo/Asia/Taipei')) >>> print(now) 2016-09-19 06:47:08.305221+08:00
And then let's try to convert it from Taipei time zone to UTC:
>>> tz_utc = dateutil.tz.tzutc() >>> now_utc = now.astimezone(tz_utc) >>> now_utc datetime.datetime(2016, 9, 18, 22, 47, 8, 305221, tzinfo=tzutc()) >>> print(now_utc) 2016-09-18 22:47:08.305221+00:00
As we can see dateutil deals with time zones pretty well. What can it do else?
Date difference in months?
Another great feature of dateutil is time or date differences. The class timedelta from datetime module is limited in this regard. It can express a difference only in days, seconds and microseconds.
While determining a difference is ok:
>>> d = datetime.datetime.now() - datetime.datetime(2016, 4, 1) >>> d datetime.timedelta(171, 71314, 791227) >>> print(d) 171 days, 19:48:38.903404
The problem appears when you would like to define a difference e.g. in minutes or months because you have to first convert it to days, seconds and microseconds. How to express a difference of 3 months? Is it 3 times 30 days?
Let's see how to cope with that using dateutil:
>>> dateutil.relativedelta.relativedelta(datetime.datetime.now(), datetime.datetime(2016, 4, 1)) relativedelta(months=+5, days=+18, hours=+19, minutes=+53, seconds=+27, microseconds=+253252)
or define a difference of 3 months and play with it:
>>> d = dateutil.relativedelta.relativedelta(months=3) >>> d relativedelta(months=+3) >>> datetime.datetime.now() + d datetime.datetime(2016, 12, 19, 19, 55, 32, 784841)
There is one more pretty interesting case for relativedelta. How to get the last day of the month. For January, this is 31, for February, this is sometimes 28, sometimes 29. So the rule, again, is not that straightforward. How can we deal with it using dateutil?
>>> datetime.datetime.now() + dateutil.relativedelta.relativedelta(day=31) datetime.datetime(2016, 9, 30, 6, 39, 46, 484167) >>> datetime.datetime(2016, 1, 1) + dateutil.relativedelta.relativedelta(day=31) datetime.datetime(2016, 1, 31, 0, 0) >>> datetime.datetime(2016, 2,1) + dateutil.relativedelta.relativedelta(day=31) datetime.datetime(2016, 2, 29, 0, 0) >>> datetime.datetime(2016, 3,1) + dateutil.relativedelta.relativedelta(day=31) datetime.datetime(2016, 3, 31, 0, 0)
The trick here is that we set 'day' not 'days' attribute in relativedelta. When such delta is being added to another date, then actually it is not adding to given field (e.g. day) but is replacing it. As there are no 31 days in February dateutil finds the closest day in this month, in our case the last day of a month.
Iterating dates
Sometimes we need to make a list of dates or times, e.g. the following 3 Thursdays or something more complicated: the first and the third Monday of the month in next 6 months. Generating such a list of dates using datetime only is not that easy. And again, dateutil has features that support this use case.
Start with "every Thursday":
>>> from dateutil.rrule import rrule, WEEKLY, TH >>> for d in rrule(WEEKLY, dtstart=datetime.datetime.now(), byweekday=TH, count=3): print(d) ... 2016-09-22 22:53:55 2016-09-29 22:53:55 2016-10-06 22:53:55
The other case, "first Monday of the month":
>>> from dateutil.rrule import MONTHLY, MO >>> for d in rrule(MONTHLY, dtstart=datetime.datetime.now(), bysetpos=(1, 3), byweekday=MO, count=3): print(d) ... 2016-09-19 22:59:59 2016-10-03 22:59:59 2016-10-17 22:59:59
And one more example, "all 3rd days of months between indicated dates":
>>> for d in rrule(MONTHLY, dtstart=datetime.datetime.now(), until=datetime.datetime(2017, 1, 1), bymonthday=3): print(d) ... 2016-10-03 23:03:56 2016-11-03 23:03:56 2016-12-03 23:03:56
That's all what I wanted to show you about dateutil. To find more about dateutil, please check references provided below.
References
- datetime -- https://docs.python.org/3/library/datetime.html
- dateutil docs -- https://dateutil.readthedocs.io/
- dateutil on github -- https://github.com/dateutil/dateutil/
- dateutil on PyPI -- https://pypi.python.org/pypi/python-dateutil