Package: psycopg-infdate
psycopg Infinite datesPostgresql has support for special dates, 'infinity' and '-infinity', which compare respectively above or below any other dates.
This module gives psycopg3 support for those dates.BackgroundSuppose you have a set of contracts with suppliers. The end date of the contract would be an SQL DATE, represented as a datetime.date in Python.Supplier NameStart DateEnd DateConcreteWorks2023-04-012024-03-31Bricks R Us2022-03-252023-09-24Lawn Maintenance2023-06-03NULLSome suppliers are on fixed contracts with a known termination date.
It is temping to represent the open-ended lawn maintenance contract as having a NULL end date, after all,
all contracts eventually reach an end, we just don't know when.This results in awkward SQL queries, such as:SELECTsupplier_nameFROMsuppliersWHEREend_date>=CURRENT_DATEORend_dateISNULLNULL is not a value and is therefore not indexed.
The fact that we had to explicitly include NULL, as if it were a normal value, is a giveaway that this
data is not normalized.While we do know not know what the end date will be, we do know that it is after today;
it isn't a completely unknown quantity.We could use a sentinel value, such as 1999-12-31. That's obviously a bad choice because it is in the past.
How about 2099-12-31? That could also represent real data, if the data pertains to future events.
There isn't any choice of sentinel value that could never be confused real data in some context.Postgres has two special values for dates and timestamps that can be used insteadhttps://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-DATETIME-SPECIAL-VALUES'infinity'::DATE compares later than all other dates.'-infinity'::DATE compares before than all other dates.Now we can use this for our lawn maintenance contract:Supplier NameStart DateEnd DateConcreteWorks2023-04-012024-03-31Bricks R Us2022-03-252023-09-24Lawn Maintenance2023-06-03infinityAnd our SQL query is simpler too:SELECTsupplier_nameFROMsuppliersWHEREend_date>=CURRENT_DATEThe problemSo far, so good. Let's try that SQL query in Python:withdbh.cursor()ascur:cur.execute("SELECT supplier_name FROM suppliers WHERE end_date >= CURRENT_DATE")suppliers=cur.fetchall()This yields the error:psycopg.DataError: date too large (after year 10K): 'infinity'. Not what we wanted!The problem is that the corresponding Python type, datetime.date, cannot represent this value.
It feels like we're back to square one.The solutionpsycopg3 has the ability to register new dumpers and loaders that get data from the database and
send data to the database. Their job is to convert between Postgresql representation and Python objects.This module adds support implements a Python type for each of these special values, and creates the corresponding dumpers and
loaders to enable the special values to be handled.The new types implement basic date arithmetic and comparisons in a similar way to Postgresql, such as:'infinity' == 'infinity''infinity' > datetime.date > '-infinity''infinity' + datetime.timedelta == 'infinity''infinity' - 'infinity' raises a ValueErrorUsing the moduleFor all database connectionsimportpsycopg_infdateimportpsycopgpsycopg_infdate.register_inf_date_handler(psycopg)dbh=psycopg.connect('...')withdbh.cursor()ascur:cur.execute("SELECT 'infinity'::DATE")answer=cur.fetchall()For one database connectionimportpsycopg_infdateimportpsycopgdbh=psycopg.connect('...')psycopg_infdate.register_inf_date_handler(dbh)withdbh.cursor()ascur:cur.execute("SELECT 'infinity'::DATE")answer=cur.fetchall()For just this cursorimportpsycopg_infdateimportpsycopgdbh=psycopg.connect('...')withdbh.cursor()ascur:psycopg_infdate.register_inf_date_handler(cur)cur.execute("SELECT 'infinity'::DATE")answer=cur.fetchall()