Recently I stumbled on the same problem to solve (which, previously, I solved differently than I am going to describe here), but answers from Laurnez Albe and Lennart inspired me to this, one column, solution.
The main idea is to use timestamp
column to hold the date and use time fields as precision markers. Further, to prevent some unwanted values in time fields, it is recommended to create domain for it. (Domain name could be better, thou):
CREATE DOMAIN xdate AS timestamp
CHECK (extract(HOUR FROM VALUE) IN (0, 1))
CHECK (extract(MINUTE FROM VALUE) IN (0, 1))
;
Meaning: use date fields normally, but allow only zero or one both in hour and minute fields (probably we could also check if seconds and millis are zeros too). So we can store values like 2022-06-16 00:00:00
or 2022-06-16 00:01:00
, but not eg. 2022-06-16 11:55:00
.
Now: treat ones as disabling flags. Hour disables month and second disables day. So eg. 1850-01-01 01:01:00
would mean "year 1850", next 1850-01-01 00:01:00
would mean "January of 1850" and finally 1850-01-01 00:00:00
would mean "1st January of 1850".
With this approach, if you sort the table by this column, "more precise" dates (i.e. with enabled year, month and day) would appear higher and less precise lower (if month and day happen to both 1 for given dates). Besides you can safely insert into this column CURRENT_DATE
or any timestamp cast to date
.
You could also create few simple function if needed:
CREATE OR REPLACE FUNCTION make_xdate(IN year int)
RETURNS xdate
LANGUAGE SQL
RETURNS NULL ON NULL INPUT
AS $$ SELECT make_timestamp(year, 1, 1, 1, 1, 0)::xdate; $$;
CREATE OR REPLACE FUNCTION make_xdate(IN year int, IN month int)
RETURNS xdate
LANGUAGE SQL
RETURNS NULL ON NULL INPUT
AS $$ SELECT make_timestamp(year, month, 1, 0, 1, 0)::xdate; $$;
CREATE OR REPLACE FUNCTION make_xdate(IN year int, IN month int, IN day int)
RETURNS xdate
LANGUAGE SQL
RETURNS NULL ON NULL INPUT
AS $$ SELECT make_timestamp(year, month, day, 0, 0, 0)::xdate; $$;
Please also note, that the checks in the above defined domain is not perfect (it allows "illegal time sequence", eg. 01:00:00
and allows non-zeroed seconds or millis), but it's more to demonstrate the idea than to write 100% fool-proof code (it can be easily fixed, thou).
And if you have better name for the domain than ad hoc made up xdate
, then let me know! :-)