I have a table definition like the following:
CREATE TABLE what_happened (
id SERIAL PRIMARY KEY,
action VARCHAR(128),
created TIMESTAMP WITH TIME ZONE NOT NULL
);
I want to be able to store the original time offset given for the value that was inserted in the created
column. As I understand it, postgresql converts TIMESTAMP WITH TIME ZONE
to UTC internally, and then converts them again on fetch based on the value shown by SHOW TIMEZONE
.
Let’s say that we’re adding a time stamp that has offset +0900, and that the offset is of interest. When we’ve added the value 2023-06-30T12:34:56+0900
, it is converted to UTC and stored in the database.
When the same record is fetched, the date is converted to the current time zone. If we’re currently at CET +0100, it will display as 2023-06-30T04:34:56+0100
.
While this is correct, it’s not very logical, and it assumes that the original time zone was not of interest.
So how could I store the original time offset, besides storing the value as text and casting it to date as required on read? Can I store the original time offset together with the time stamp, and use that? If so I don’t see the right way, because changing the timestamp’s offset with AT TIME ZONE xyz
always returns a new value without time zone, which is not really helpful (as described in the docs).
So perhaps I need to:
- Add a second column to save the time offset together with the timestamp
- On display, convert the timestamp back to the original time offset, and build a valid ISO 8601 string append my original time offset information.
This seems a bit convoluted, and some of the steps aren’t very straight forward. I’m hoping to avoid doing it manually with string operations.
2
Answers
As noted by @Matt Johnson-Pint, offsets and time zones are different things. A time zone like
Europe/Berlin
has a different time offset during summer time and winter time, and postgres keeps track of this. So my example using different offsets across daylight savings time boundaries requires no further attention, as it is already handled correctly by postgres when we're working in within the same time zone.If there is a requirement that involved storing timestamps from completely different time offsets, and the original offset is of interest, the following might be useful.
It appears that storing both the
TIMESTAMP WITH TIME ZONE
and a string type storing the original value might be the way to go.So the table could look like this:
and this provides the behaviour I was looking for. The main drawback is that the timestamp is stored twice.
It would probably make sense to write a trigger that sets
created_str
fromcreated
, rather than supplying the same value twice all over the place. I can't store onlycreated_str
, because string sorting won't sort correctly across time offsets. If you don't require an index, then the following query orders thecreated_str
correctly:Creating an index on
(created_str::TIMESTAMP WITH TIME ZONE)
wasn't entirely straight forward either, but could probably be resolved with a wrapper function marked as IMMUTABLE.Still, the safest bet might be to keep both
created
andcreated_str
. Index and sort bycreated
, and usecreated_str
for display only, or for further processing when the original time zone is of consequence.The only way to preserve the time zone that was entered is to save the time zone string in a separate column. Extracting the time zone is best done on the client side.