No Good Solution
The question of how to handle empty date-time values in a database has no good solution.
Avoid NULL
As Dr. Chris Date explains in A Guide to the SQL standard and elsewhere, NULLs are a bloody nightmare. Generally they should be avoided if at all possible. In SQL databases, that means declaring each column as NOT NULL (I wish there were a way to make that the default). Where sensible we set some kind of default value.
No Good Default Value For Date-Time
But setting a default value for date-time values can be very confusing. Neither the SQL standard nor JDBC/java.sql.* defines any particular value as a flag for being “empty”.
The java.time framework built into Java 8 and later does define minimum and maximum values as constants in the Instant, LocalDateTime, LocalDate, and LocalTime classes. These values might work as flag values except for the critical issue of resolution. These java.time classes resolve to nanoseconds while many databases have a larger granularity such as microseconds in Postgres. The min & max values are so extreme that their meaning would change when truncated to a smaller granularity.
Zero-Value
You might intuitively think to consider zero-value date-times, 0000-00-00 00:00:00.0 / 0000-00-00 / 00:00:00.0. Not so simple.
No Zero Month / Day
First of all, there is no such thing as a month of 00, nor day-of-month of 00. So, okay, we could change it to 0000-01-01, the first of January.
No Zero Year
Next issue: No such year as 0000. I don't know about all databases, but at least in Postgres there is no such zero year. Trying to insert such a value generates an error.
INSERT INTO moment_ -- TIMESTAMP WITH TIME ZONE.
VALUES ( '0000-01-01 00:00:00.0Z' ) ;
…throws error:
ERROR: date/time field value out of range: "0000-01-01 00:00:00.0Z"
LINE 2: VALUES ( '0000-01-01 00:00:00.0Z' ) ;
^
********** Error **********
Changing 0000 to year one 0001 works.
INSERT INTO moment_ -- TIMESTAMP WITH TIME ZONE.
VALUES ( '0001-01-01 00:00:00.0Z' ) ;
SET TIME ZONE 'UTC' ;
TABLE moment_ ;
0001-01-01 00:00:00+00
That number is the pivot point for BC versus AD eras. Let’s subtract an hour to see the behavior of moving from AD to BC. By the way strings such as BC are locale-dependent so may vary for you. Also, note that we must set the time zone to UTC to get this date-time operation to complete correctly.
SET TIME ZONE 'UTC' ;
INSERT INTO moment_ -- TIMESTAMP WITH TIME ZONE.
VALUES ( TIMESTAMP '0001-01-01 00:00:00.0Z' - INTERVAL '1 hour' ) ;
SET TIME ZONE 'UTC' ;
TABLE moment_ ;
The result is the year 0001 BC, so we jump from 0001 to 0001 BC; no year zero 0000.
"0001-12-31 23:00:00+00 BC"
So if you do decide to use a certain flag value, you cannot use a true zero value but you could use a near-zero value, the first of January of year one AD: '0001-01-01 00:00:00.0Z'
Time Zone
You must be careful about time zones when working with this special flag value.
When passing strings the SQL session’s current default time zone is applied implicitly (at least in Postgres).
Sorting NULLs
In my own work I do indeed make nearly all columns NOT NULL and set a default. But date-time columns have been an exception as there is no workable flag value. And I have suffered from this. Specifically I learned the hard way how to use NULLS FIRST/NULLS LAST modifiers because the SQL standard does not explicitly define a default sort order for NULL.