Manipulating dates and time

Import and setup execution context for demonstration purposes.

>>> from lino import startup
>>> startup('lino.projects.std.settings_test')
>>> from lino.utils import *

Covered modules

The following modules define classes related to dates and date ranges. Also miscellaneous date formatting functions.

Setting an offset

The following are some usage examples of date_offset() used on a reference date given a number of days to offset it:

>>> r = i2d(20140222)

In 10 days: >>> date_offset(r, 10) datetime.date(2014, 3, 4)

Four hundred days ago: >>> date_offset(r, -400) datetime.date(2013, 1, 18)

Last day of the month

Given a date as the only argument to the last_day_of_month() returns the last day of the month of the given date. Examples:

>>> last_day_of_month(i2d(20160212))
datetime.date(2016, 2, 29)
>>> last_day_of_month(i2d(20161201))
datetime.date(2016, 12, 31)
>>> last_day_of_month(i2d(20160123))
datetime.date(2016, 1, 31)
>>> last_day_of_month(i2d(20161123))
datetime.date(2016, 11, 30)

Incomplete Date

Examples of IncompleteDate instances and their behaviour:

In the case where we have to say something like: "Once upon a time in the year 2011":

>>> print(IncompleteDate(2011, 0, 0).strftime("%d.%m.%Y"))
00.00.2011

Unlike datetime.date objects an incomplete date can hold years before 1970.

>>> print(IncompleteDate(1532, 0, 0))
1532-00-00

On June 1st (but we don't say the year):

>>> print(IncompleteDate(0, 6, 1))
0000-06-01

On the first day of the month in 1990:

>>> print(IncompleteDate(1990, 0, 1))
1990-00-01

W.A. Mozart's birth date:

>>> print(IncompleteDate(1756, 1, 27))
1756-01-27

Christ's birth date:

>>> print(IncompleteDate(-7, 12, 25))
-7-12-25
>>> print(IncompleteDate(-7, 12, 25).strftime("%d.%m.%Y"))
25.12.-7

Note that you cannot convert all incomplete dates to real datetime.date objects:

>>> IncompleteDate(-7, 12, 25).as_date()
... 
Traceback (most recent call last):
...
ValueError: year...is out of range
>>> IncompleteDate(1756, 1, 27).as_date()
datetime.date(1756, 1, 27)

An IncompleteDate is allowed to be complete:

>>> d = IncompleteDate.parse('2011-11-19')
>>> print(d)
2011-11-19
>>> d.is_complete()
True
>>> print(repr(d.as_date()))
datetime.date(2011, 11, 19)
>>> d = IncompleteDate.parse('2008-03-24')
>>> d.get_age(i2d(20131224))
5
>>> d.get_age(i2d(20140323))
5
>>> d.get_age(i2d(20140324))
6
>>> d.get_age(i2d(20140325))
6
>>> d.get_age(i2d(20141025))
6

Note that IncompleteDate can store invalid dates:

>>> d = IncompleteDate(2009, 2, 30)
>>> d.get_age(i2d(20160202))
6
>>> IncompleteDate(2009, 2, 32)
IncompleteDate('2009-02-32')
>>> IncompleteDate(2009, 32, 123)
IncompleteDate('2009-32-123')

Some usage example of the method IncompleteDate.parse() which return an IncompleteDate instance:

>>> IncompleteDate.parse('2008-03-24')
IncompleteDate('2008-03-24')

Belgian eID cards gave us some special challenges:

>>> IncompleteDate.parse('01.JUN. 1968')
IncompleteDate('1968-06-01')
>>> IncompleteDate.parse('JUN. 1968')
IncompleteDate('1968-06-00')
>>> IncompleteDate.parse('13 maart 1953')
IncompleteDate('1953-03-13')
>>> IncompleteDate.parse('13 januari 1953')
IncompleteDate('1953-01-13')

Return None when we could not understand the string.

>>> IncompleteDate.parse('foo bar')

dateparser unfortunately does not understand that "MAAR" is the abbreviation for dutch "MAART" (which means "March"). So here it returns None:

>>> IncompleteDate.parse('13 MAAR 1953')

Calculating workdays

The function workdays() calculates and returns the number of working days, given a start date and an end date.

Examples:

>>> examples = [
...   (20121130,20121201,1),
...   (20121130,20121224,17),
...   (20121130,20121130,1),
...   (20121201,20121201,0),
...   (20121201,20121202,0),
...   (20121201,20121203,1),
...   (20121130,20121207,6),
... ]
>>> for start,end,expected in examples:
...     a = i2d(start)
...     b = i2d(end)
...     if workdays(a,b) != expected:
...        print("Got %d instead of %d for (%s,%s)" % (workdays(a,b),expected,a,b))

Like workdays(), dates.weekdays() also works in a similar fashion. Examples:

>>> from lino.utils.dates import weekdays
>>> weekdays(i2d(20151201), i2d(20151231))
23
>>> weekdays(i2d(20160701), i2d(20160717))
11
>>> weekdays(i2d(20160717), i2d(20160717))
0
>>> weekdays(i2d(20160718), i2d(20160717))
0

Date formatting

There are different date formatting syntaxes:

Note that if you ask just for English, then we change Babel's default localization (US) to UK because US date format is just silly for non-american people (no further comment).

The function format_date() is a thin wrapper to the corresponding function in babel.dates, filling the locale parameter according to Django's current language (and doing the conversion).

The major advantage over using date_format from django.utils.formats is that Babel offers a "full" format.

Examples of formatting date using the functions format_date.fds(), format_date.fdm(), format_date.fdl() & format_date.fdf() which ultimately calls the function format_date.format_date() with the related arguments:

>>> from lino.utils.format_date import *
>>> import datetime
>>> d = datetime.date(2013, 8, 26)
>>> print(fds(d)) # short
26/08/2013
>>> print(fdm(d)) # medium
26 Aug 2013
>>> print(fdl(d)) # long
26 August 2013
>>> print(fdf(d)) # full
Monday, 26 August 2013
>>> print(fdmy(d)) # full
August 2013

Some localized examples of date formatting:

>>> today = datetime.date(2013,1,18)
>>> print(format_date(today,'full'))
Friday, 18 January 2013
>>> with translation.override('fr'):
...    print(format_date(today,'full'))
vendredi 18 janvier 2013
>>> with translation.override('de'):
...    print(format_date(today,'full'))
Freitag, 18. Januar 2013

You can use this also for languages that aren't on your site:

>>> with translation.override('et'):
...    print(format_date(today,'full'))
reede, 18. jaanuar 2013
>>> with translation.override('nl'):
...    print(format_date(today,'full'))
vrijdag 18 januari 2013
>>> with translation.override('de'):
...    print(fds(today))
18.01.13
>>> with translation.override('fr'):
...    print(fds(today))
18/01/2013
>>> with translation.override('en_US'):
...    print(fds(today))
1/18/13
>>> with translation.override('en'):
...    print(fds(today))
18/01/2013
>>> with translation.override('en_UK'):
...    print(fds(today))
18/01/2013
>>> print(fds('')) # empty string is tolerated

>>> print(fds('2014-10-12')) # not tolerated
Traceback (most recent call last):
  ...
Exception: Not a date: '2014-10-12'