Prior art#

The packages that already store partial dates in Django, why none of them is used, and what this package must not repeat. The storage decision itself is ADR 0001.

Existing packages#

Package

Last release

Storage

Why not use it

django-partial-date

January 2022

A DateTimeField column, with the precision held in the seconds

Unmaintained: four open issues have had no reply for years. Carries the defects listed below.

r-django-partial-date

February 2020

The same as django-partial-date, of which it is a fork

Unmaintained.

python-edtf

September 2026

The EDTF string, plus four float columns the model must declare for filtering and sorting

Healthy and complete, but its Django field needs four extra columns on every model, and its documentation warns that the float conversions can be inaccurate. It is the reference if uncertain or approximate dates are ever in scope.

What overlaps with python-edtf, and why this package exists anyway#

python-edtf covers every form this package stores, and far more. The overlap is real. This package exists because its users need three forms, not the whole of EDTF, and because they need them in a single database column with no companion fields to declare. A field that adds hidden columns, or asks every model to declare four more, is more machinery than the problem needs.

Defects in django-partial-date#

Every one of these is a defect this package must not repeat.

  • Comparisons across precisions are wrong. __ge__ is self.date >= other.date and self.precision >= other.precision, so 1990-06 is not “greater than or equal to” 1991. There is no __lt__ or __le__.

  • Values are unhashable. The class defines __eq__ without __hash__, so a value cannot go in a set or be a dictionary key.

  • The precision setter does nothing. It calls date.replace(day=1) and discards the result, so a value built from 17 May 1990 at year precision still holds 17 May.

  • Serialisation breaks on empty values. The field has no value_to_string, so dumpdata and django-reversion cannot handle a blank one.

  • It depends on six, a Python 2 compatibility library.

  • It invents a day. .date on a year-precision value returns 1 January, which callers cannot tell from a recorded 1 January.

  • Its database lookups depend on the time zone. The column is a timestamp. With USE_TZ, the __year, __month and __date lookups convert to the current time zone first, so on a site west of UTC a value stored as 1990 matches __year=1989.

Storage options considered#

Option

Outcome

A timestamp with the precision in its seconds, as django-partial-date does

Rejected. It stores a day and a month nobody recorded, and brings the time zone into date lookups.

A date column and a precision column

Rejected. Django has no field spanning two columns, so it needs a hidden companion field.

A start date and an end date, or a PostgreSQL daterange

Rejected. It presents an uncertain date as a certain interval, and a daterange works only on PostgreSQL.

The ISO 8601 string in one text column

Chosen. ADR 0001 records why.