SQLite Dates
SQLite columns are flexibly-typed, you can put broadly anything in any column regardless of the declared type of that column. There also isn’t a specific type for dates or times. This means that when it comes to storing dates or times, we’ve got choices to make. I’ll go through the main options in this post.
To start with, we need to provide some context:
- SQLite lets you put values of any type you want in a column1
- SQLite doesn’t have date- or time-specific column types
- The date and time functions in SQLite work with
TEXT,REALandINTEGERvalues2
These three points might seem contradictory, since we’re saying on one hand that SQLite doesn’t care about types, but also that the date functions work only on certain types. That’s because the date functions have a few specific formats ("time values") that they can handle, so it’s less about the column types and more about the value formats.
The formats
| ISO8601 (TEXT) | Julian day number (REAL) | Unix timestamp (INTEGER) |
|---|---|---|
- |
- |
- |
Dates as TEXT
If you want to handle dates as text, then you’ll need to format dates as ISO 8601 strings. The ISO standard describes quite a lot of options for formatting dates, but SQLite uses only a few of them. Thankfully it’s the ones I expect most people think of when ISO 8601 gets mentioned.
The full list of accepted formats is available on the SQLite website, but a few examples are:
- Date and time:
2025-11-01T18:30:00.000 - Date only:
2025-11-01 - Time only:
18:30:00.000
These formats can also include a timezone specifier. Internally SQLite works with UTC, so when you use the date functions any dates with a timezone specified will be converted to UTC:
SELECT datetime('2025-11-01 18:30:00.000+09:00') AS utcValue;
-- Returns: '2025-11-01 09:30:00'
Dates as REAL
This one might sound odd to start with, but bear with it. You can store dates as Julian day numbers.
The Julian day number is the number of days since -4713-11-24 12:00:003 and can
include decimal places to cover units of less than a day. So it’s really just a timestamp, with the
reference date set a long way in the past.
A few examples of Julian dates:
- 1st January 2000 at 00:00 UTC:
2451544.5 - 1st November 2025 at 18:30 UTC:
2460981.2708333335 - The unix epoch date:
2440587.5
Dates as INTEGER
This one’s a bit more common: dates stored as unix time;
the number of seconds since 1970-01-01 00:00:00 UTC.
SQLite can’t tell just from looking at a number whether it’s a Julian day number or unix timestamp and by default it assumes numbers are Julian days4. If you use unix time with the date functions you’ll need to tell the function by passing in an extra parameter:
SELECT datetime(1762021800, 'unixepoch');
-- Returns '2025-11-01 18:30:00'
Other formats
You can also store Julian day numbers as INTEGER values, or unix timestamps as decimals if you
want. If you only need to store a specific day, not a time, an integer day number works just fine.
If you want fractional seconds for a unix timestamp, you can store it as a REAL.
It’s also worth pointing out that you’re not limited to these formats for storage. You can represent
dates and times in any way you want. You could choose to use the email date format
(Tue, 01 Nov 2025 18:30:00 UTC) or perhaps Cocoa’s TimeInterval (the number of seconds since
1st January 2001 as a decimal). These - and any others you might find - can be saved
perfectly well in your tables.
If you do choose a different format though, the built-in date and time functions in SQLite won’t work with your data. This may or may not be a problem for you depending on how you use the database, but it’s important to consider when choosing a format.
Which to choose?
As usual, it depends.
You’ll want to consider things like:
- How am I interacting with the database? Do I need people to be able to read dates by eye?
- What systems will be using the database? What is their preferred serialisation format?
- What precision do I need?
- Do I care about storage space?
Generally my preference is to store dates as TEXT unless there is a good reason not to.
I choose this because:
- They’re human readable
- They’re widely compatible with other tools
- There’s little to no risk of getting them confused with other formats
There are disadvantages though:
- Converting to/from strings may have performance costs
- You use more storage5
Most of the time these drawbacks are not very significant to me, so I don’t worry about them.
-
Unless you’ve chosen to declare a table to be strictly typed ↩︎
-
See the SQLite documentation on data types ↩︎
-
You might notice an inconsistency here. The SQLite documentation says that the reference date is “noon in Greenwich on November 24, 4714 B.C.”, but
datetime(0)returns-4713-11-24 12:00:00, which has a different year number. That’s because the proleptic Gregorian calendar doesn’t have a year zero, it jumps from 1BC to 1AD, while ISO8601 uses astronomical year numbering which does have a year zero. ↩︎ -
There is an
automodifier to date/time functions which will let SQLite try to auto-detect the date representation from an integer, based on its size. ↩︎ -
Sometimes quite a lot more. For the same date:
2025-11-01 18:30:00is 19 bytes;2460981.2708333335is 8 bytes;1762021800is 4 bytes ↩︎