How do I tell postgres a timestamp within a column is UTC? - postgresql

We have an application that fetches data from a source and that source present the data with a timestamp in UTC. When our application saves that data to Postgres, it stores that timestamp in a timestamp column without time zone. The default on postgres in our shop is set to our local time, Mountain Time. So that means, I think, that postgres assumes that timestamp is mountain time. How can I query that column so that my result set thinks it's UTC and not the local time zone?
More cleary stated, I need to perform some offsets on that timestamp (moving it to, say EST) and so the math of doing that is different if the resultset thinks it's UTC than my local time

The Answer by Kouber Saparev is mostly correct, though incorrect about storing a time zone.
Wrong data type in Postgres
a timestamp in UTC. When our application saves that data to Postgres, it stores that timestamp in a timestamp column without time zone.
As noted in his Answer, you are using the wrong data type in your Postgres database. When tracking moments, you must use a column of type TIMESTAMP WITH TIME ZONE. When supplying an input during an insert or update, any accompanying info about time zone or offset-from-UTC is used to adjust into UTC. The accompanying zone/offset is then discarded. If you need to remember the original zone/offset, you will need to define a second column and store that info there yourself.
The other type in Postgres, and the SQL standard, is TIMESTAMP WITHOUT TIME ZONE. This type purposely lacks any concept of time zone or offset-from-UTC. So this type cannot represent moments, cannot store points on the timeline. It stores values that represent potential moments along a range of about 26-27 hours, the range of various time zones around the globe. Use this type only when you mean a date with time-of-day everywhere or anywhere, but not specifically somewhere. Also used when you mean appointments far enough out in the future that we run the risk of politicians changing the offset used in any of the time zones we care about.
Always specify time zone
default on postgres in our shop is set to our local time, Mountain Time
Never depend on the current default time zone of your host OS, the database server, or your tools such as the Java Virtual Machine. Always specify the desired/expected time zone in your code.
Tip: Generally best to work in UTC for data storage, data exchange, and most of your business logic. Adjust from UTC to a time zone only for presentation to the user or where business rules require.
As explained above, Postgres always stores date-time values either in UTC or with no zone/offset at all. Beware: Tools used between you and Postgres may apply a time zone to the UTC value retrieved from the database. While well-intentioned, this anti-feature creates the illusion that the time zone was stored when in fact only UTC was stored in TIMESTAMP WITH TIME ZONE or no zone/offset at all in TIMESTAMP WITHOUT TIME ZONE.
Be aware that any zone information accompanying input to a column of TIMESTAMP WITHOUT TIME ZONE is simply ignored, the date and time-of-day taken as-is and stored.
I need to perform some offsets on that timestamp (moving it to, say EST)
Generally best to use your database just for storage, query, and retrieval of data. For massaging the data like adjusting time zone, do such work in your application. For example, in Java use the industry-leading java.time classes, in .NET the Noda Time project (a port of the predecessor of java.time, the Joda-Time project).
Example code in Java using JDBC 4.2 or later.
LocalDateTime
For a value in a column of TIMESTAMP WITHOUT TIME ZONE we use the corresponding type in Java, LocalDateTime, lacking any concept of time zone or offset-from-UTC.
LocalDateTime ldt = myResultSet.getObject( … , LocalDateTime.class ) ; // Retrieve value from database.
String output = ldt.toString() ; // Generate text representing this date-with-time value in standard ISO 8601 format.
2018-01-23T01:23:45.123
If you know for certain that this date and time was meant for UTC but was incorrectly stored without any zone/offset info, you can apply a zone or offset to repair the damage.
OffsetDateTime odt = ldt.atOffset( ZoneOffset.UTC ); // Apply an offset-from-UTC to a `LocalDateTime` lacking such information. Determines a moment.
OffsetDateTime
For a value in a column of TIMESTAMP WITH TIME ZONE we use the corresponding type in Java, OffsetDateTime (or Instant), representing a moment in UTC.
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ; // Retrieve value from database.
String output = odt.toString() ; // Generate text representing this date-with-time value in standard ISO 8601 format. A `Z` on the end indicates UTC, pronounced “Zulu”.
2018-01-23T01:23:45.123Z
ZonedDateTime
To see that OffsetDateTime value set in UTC through the lens of the wall-clock time used by the people of regions within the mid-west of North America, specify a time zone such as America/Edmonton or America/Denver.
Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 2-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).
ZoneId z = ZoneId.of( "America/Denver" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant( z ) ;
See this code run live at IdeOne.com. We see the same moment but with a different wall-clock time.
2018-01-22T18:23:45.123-07:00[America/Denver]
Beware of tools & middleware injecting a time zone
Unfortunately, many tools and middleware will volunteer to apply some default time zone to a moment retrieved from the database. While well-intentioned, this creates the illusion of the zone having been a part of the stored data when in fact the time zone was added after storage, upon retrieval. This anti-feature creates much confusion. I wish all the tools were clear and truthful by reporting the moment in UTC, as it was stored.
If you use Java, with JDBC 4.2 and later, you can exchange java.time (JSR 310) (tutorial) objects with the database and avoid this time zone injection.

There are two data types handling timestamps in PostgreSQL - timestamp, and timestamptz (timestamp with time zone). The latter stores the time zone along with the timestamp itself.
If you are using just a timestamp without time zone, then there is no way for the result set to think whether the timestamp is UTC or not. It is just a timestamp. It is up to the client application to interpret it and give it some time zone meaning.
On the contrary, if you use timestamptz, then PostgreSQL knows the time zone of that timestamp, and then it can calculate time zone offsets properly for you.
db=# select now();
now
-------------------------------
2014-12-04 19:27:06.044703+02
(1 row)
db=# select timezone('est', now());
timezone
----------------------------
2014-12-04 12:27:06.044703
(1 row)
So, back on the problem posed. You need to make sure that first the data is imported properly and then - when needed, it is returned and displayed properly to the end user. You have two options:
Continue using timestamp
In that case both the writing app and the reading app need to know that all the timestamps in the database are UTC and calculate offsets accordingly.
Switch to timestamptz
Then the only thing that the apps need to know is their own time zone, they just have to declare it after connecting to PostgreSQL and leave the rest to the database.
For example, let's connect as a writing app and declare our time zone as UTC.
db=# create table x (data timestamptz);
CREATE TABLE
db=# set timezone='utc';
SET
db=# insert into x values (now());
INSERT 0 1
db=# select * from x;
data
-------------------------------
2014-12-04 20:02:08.692329+00
(1 row)
Now, let's say a reading app connects and is in the EST time zone.
db=# set timezone='est';
SET
db=# select * from x;
data
-------------------------------
2014-12-04 15:02:08.692329-05
(1 row)
Changing the client time zone setting changes the way all the timestamps are returned, but that's the case only if you use timestamptz - timestamp with time zone. If you cannot switch to this data type, then the application will have to take care of all this magic.

Related

How to specify time zone for the time stamp column when creating table in PostgreSql

I am creating a table with timestamp column,I am stuggling with time zone settings, I want to specify the time zone on the column,as follows:
create table t1(a date, b timestamp with time zone 'America/Los_Angeles', c timestamp without time zone)
But the grammar is wrong, I would ask how to specify the time zone on the column, than
👉 You do not specify a time zone when defining the column.
CREATE TABLE t1
(
a DATE ,
b TIMESTAMP WITH TIME ZONE ,
c TIMESTAMP WITHOUT TIME ZONE
)
;
You need to read the documentation carefully. Programming by intuition tends to end badly.
The TIMESTAMP WITH TIME ZONE type in Postgres does not save a time zone. The type uses any offset or time zone info supplied with an input to adjust to an offset of zero hours-minutes-seconds from UTC. 👉 Every value in that column is set to an offset of zero. After adjusting to zero offset, the supplied time zone or offset info is discarded by Postgres.
If you care about the original time zone, you need to write that value into a separate column yourself.
In contrast, the TIMESTAMP WITHOUT TIME ZONE type lacks any concept of a time zone or offset from UTC. A column of this type stores simply a date and a time-of-day. So values in this column cannot represent a moment, cannot refer to a specific point on the timeline. If you write noon on the 23rd of last January, we have no way of knowing if you meant noon in Tokyo Japan, noon in Toulouse France, or noon in Toledo Ohio US. Those would be three different moments, several hours apart.
Some other databases share the same behavior as Postgres. But not all. The SQL standard barely touches on the subject of date-time, just mentioning the types but without much detail regarding prescribed behavior. As a consequence, date-time behavior varies widely across database engines.
I should mention that some tools have an anti-feature where they inject a default time zone, used to adjust a value stored in UTC to that zone. pgAdmin is, unfortunately, one such tool. While well-intentioned as a convenience to the user, this behavior creates the illusion of a time zone having been stored and retrieved. I would rather all tools “tell the truth”, and report retrieved values with an offset of zero. A workaround is to set the current default time zone of your database session to UTC.
All this has been covered many times already here on Stack Overflow, and also on the sister site https://dba.stackexchange.com/. Search to learn more.

PostgreSQL/JDBC and TIMESTAMP vs. TIMESTAMPTZ

I've been going through a lot of pain dealing with Timestamps lately with JPA. I have found that a lot of my issues have been cleared up by using TIMESTAMPTZ for my fields instead of TIMESTAMP. My server is in UTC while my JVM is in PST. It seems almost impossible with JPA to normalize on UTC values in the database when using TIMESTAMP WITHOUT TIMEZONE.
For me I use these fields for stuff like "when was the user created", "when did they last use their device", "when was the last time they got an alert", etc. These are typically events so they are instance in time sorts of values. And because they will now by TIMESTAMPTZ I can always query them for a particular zone if I don't want them UTC.
So my question is, for a Java/JPA/PostgreSQL server, when WOULD I want to use TIMESTAMP over TIMESTAMPTZ? What are the use cases for it? Right now I have a hard time seeing why I'd ever want to use TIMESTAMP and because of that I'm concerned that I'm not grasping its value.
Generally use TIMESTAMPTZ
Here's advice from David E. Wheeler, a Postgres expert, in a blog post whose title says it all:Always Use TIMESTAMP WITH TIME ZONE (TIMESTAMPTZ)
If you are tracking actual moments, specific points on the timeline, use TIMESTAMP WITH TIME ZONE.
One Exception: Partitioning
Wheeler’s sole exception is when partitioning on timestamps, because of technical limitations. A rare exception for most of us.
For information about partitioning, see doc and see the Wiki.
Misnomer
The data types names timestamp with time zone and timestamp without time zone are misnomers. In both cases the date-time value is stored in UTC (no time zone offset). Read that previous sentence again. UTC, always. The "with time zone" phrase means "with attention paid to time zone", not "store the time zone alongside this value". The difference between the types is whether any time zone should be applied either during storage (INSERT or UPDATE) or retrieval (SELECT query). (This behavior is described for Postgres -- Other databases vary widely in this regard.)
More precisely, one should say that TIMESTAMP WITHOUT TIME ZONE stores date-time values with no time zone. But without any time frame reference, anyone looking at that data would have to assume (hope, pray?) that the values are UTC. But again, moot as you should almost never use this type.
Read the doc carefully, and experiment a bit to clarify your understanding.
Unzoned
If you want to store the general idea of a possible time rather than a specific moment, use the other type, TIMESTAMP WITHOUT TIME ZONE.
For example, Christmas starts this year at the first moment of December 25th, 2017. That would be 2017-12-25T
00:00:00 with no indicator of time zone nor offset-from-UTC. This value is only a vague idea about possible moments. It has no meaning until we apply a time zone (or offset). So we store this using TIMESTAMP WITHOUT TIME ZONE.
The elves staffing Santa’s Special Events Logistics Department apply the time zones as part of their planning process. The earliest time zone is currently Pacific/Kiribati, 14 hours ahead of UTC. The elves schedule Santa’s first arrival there. The elves schedule a flight plan taking the reindeer on to other time zones where midnight comes shortly after, such as Pacific/Auckland. They continue going westward as each zone’s midnight arrives. Hours later in Asia/Kolkata, still later in Europe/Paris, still more hours later in America/Montreal and so on.
Each of these specific delivery moments would be recorded by the elves using WITH TIME ZONE, while that general idea of Christmas would by stored as WITHOUT TIME ZONE.
Another use in business apps for WITHOUT TIME ZONE is scheduling appointments farther out than several weeks. Politicians around the world have an inexplicable predilection for messing with the clock and redefining time zone rules. They join Daylight Saving Time (DST), leave DST, start DST on a different date, or end DST on a different date, or shift their clocks by 15 minutes or half-hour. All of these have been done in last several years by Turkey, United States, Russia, Venezuela, and others.
The politicians often make these changes with little forewarning. So if you are scheduling a dental appointment for six months out at 13:00, that should probably be stored as TIMESTAMP WITHOUT TIME ZONE or otherwise the politicians may effectively be changing you appointment to noon, or 2 PM, or 13:30.
You could use it to represent what Joda-Time and the new Java 8 time APIs call a LocalDateTime. A LocalDateTime doesn't represent a precise point on the timeline. It's just a set of fields, from year to nanoseconds. It is "a description of the date, as used for birthdays, combined with the local time as seen on a wall clock".
You could use it to represent, for example, the fact that your precise birth date is 1975-07-19 at 6 PM. Or that, all across the world, the next new year is celebrated on 2015-01-01 at 00:00.
To represent precise moments, like the moment Armstrong walked on the moon, a timestamp with timezone is indeed more appropriate. Regardless of the timezone of the JVM and the timezone of the database, it should return you the correct moment.
Update for the answers above: partitioning is no longer an exceptional case in PG11 thanks to pruning.
https://www.postgresql.org/docs/11/ddl-partitioning.html#DDL-PARTITION-PRUNING
Personally successfully tested queries against PG11 AWS RDS. Also the official PG wiki states the use of timestamp without timezone is a bad idea:
https://wiki.postgresql.org/wiki/Don%27t_Do_This#Don.27t_use_timestamp_.28without_time_zone.29_to_store_UTC_times
With the Java 8 date & time API I wouldn't blindly jump into a timestamptz camp.
If you map timestamp <=> LocalDateTime you always get the same value regardless default Java application timezone. Regardless how many calls TimeZone.setDefault(TimeZone.getTimeZone("TZ")) mixing different TZ you put in between SELECT/INSERT you will get the same LocalDateTime in Java at any time and date/time components will be the same as in Postgresql TO_CHAR(ts, 'YYYY-MM-DD HH24:MI:SS').
If you map timestamptz <=> LocalDateTime Postgresql JDBC driver (supporting JDBC 4.2 spec) converts LocalDateTime to UTC using default Java timezone when saving value to DB. If you save it in one default TZ and read in another you get different "local" results.
Airplane departure time is local to an airport. If you don't need to compare departure time between different cities timestamptz & UTC doesn't make sense, you just print exact city local time in a ticket. With timestamp it is possible to keep date/time as is, avoiding double TZ correction due to Java app default TZ + city specific TZ (business logic).
timestamptz is useful when you heavily convert TZ in SQL. With only timestamp you write:
date_trunc('day', x.datecol AT TIME ZONE 'UTC' AT TIME ZONE x.timezone)
AT TIME ZONE x.timezone AT TIME ZONE 'UTC'
while with timestamptz there is no need to mention that time is in UTC (if you follow such convention, probably you should xD):
date_trunc('day', x.datecol AT TIME ZONE x.timezone)
AT TIME ZONE x.timezone
Operator AT TIME ZONE is overloaded:
timestamp AT TIME ZONE 'X' => timestamptz
timestamptz AT TIME ZONE 'X' => timestamp
Postgresql JDBC + Java 8 date&time API spec.

PostgreSQL and the mystery of timestamps

I think one of the two scourges of IT are timestamps and time zones (the other being character encoding) where one keeps stumbling upon again and again...
In that regard I currently have a problem related to different timestamps within a Java application storing into a PostgreSQL database.
For keeping things simple, assume having the following table:
CREATE TABLE ts_test
(
id integer NOT NULL,
utc timestamp without time zone,
local timestamp with time zone,
CONSTRAINT pk PRIMARY KEY (id)
)
So, I have to store a UTC time stamp and a local one, which in my case is central Europe summer time, so currently UTC+2.
Further assume having 2 entries in the table, which output on the psql console as follows (the database runs in UTC):
# select id, utc, local, local-utc as diff from ts_test;
id | utc | local | diff
----+---------------------+------------------------+----------
1 | 2012-06-27 12:00:00 | 2012-06-27 12:00:00+00 | 00:00:00
2 | 2012-06-27 12:00:00 | 2012-06-27 14:00:00+00 | 02:00:00
(2 rows)
Now, several questions arise:
What exactly does the output in the local column mean?
How does the system know the timezone, I inserted the value in?
How can I see the real raw value (e.g. milliseconds) stored?
I would have assumed, that the first line's local "12:00:00+00" means, that it's 12:00 in UTC, which again is 14:00 in CEST. But it seems (and so I was told by our database admin), that the 2nd line's local "14:00:00+00" is the correct value for 14:00 CEST - which is supported by the diff of 2 hours.
But to produce the 2nd line via sql insert, I have to write
insert into ts_test (id, utc, local) values (2, '2012-06-27 12:00:00', '2012-06-27 16:00:00+02');
which again does not support the predication.
So, to sum up this long question - can anyone enlighten me on how this whole thing works in detail, what the output is supposed to mean and how one should write local time stamps correctly into the database?
According to the output of the local column, the time zone of your SQL session is set to UTC or GMT and not the time zone where you live in. Presumably this is what you mean by: the database runs in UTC. This is the root of the problem, but let's try to elaborate.
The db itself, as a data repository, doesn't have a timezone, but each SQL session has its own timezone.
When they're requested by a SQL session, the values of timestamp without time zone are not rotated to the session's time zone and not presented with a time offset, whereas the values for timestamp with time zone are rotated to the session's timezone and presented with the time offset for this time zone. That's the difference between both.
The time zone is never stored in any of the datatypes, because when reading the value, all that matters is the time zone of the SQL session that is requesting this value.
Setting your SQL time zone to UTC is not a good idea because it contradicts this other part of your question:
So, I have to store a UTC time stamp and a local one, which in my case
is central Europe summer time, so currently UTC+2
Let the SQL session know your real time zone, and it will start to work as intended. If you don't, timestamp with time zone is essentially useless.
Also note that storing the same time in utc timestamp without time zone and local timestamp with time zone doesn't make sense, because you could always get the utc with:
SELECT local AT TIME ZONE 'UTC' FROM ts_test WHERE...
EDIT: answers to questions in the comments:
Q: you are saying that if my timezone is set to my local time in the
session, then I should see for example ...14:00:00+02 in local for a
utc value of ...12:00:00
Yes.
Q: And when writing something into the local field from my application,
it matters which timezone is set there?
Exactly.
Q: How does one set this in JDBS-session?
I don't know JDBC but at the SQL level, that would be for example:
SET timezone='Europe/Berlin';
Normally it's automatically set from the environment but it can be forced at various levels including postgresql.conf. Setting it explicitly in the session will override anything else.
Q: can I see the raw value of the timestamp somehow, to make sure it's
not just a representation problem when displaying
I'm not aware of how to do that except with pageinspect which operates at the lower level.

Specify a time zone to use as the reference time zone

Based on the first two answers, the question was unclear as originally posted, thus I am completely rewriting it:
The following question is concerned only with how and what data is stored, and is no way shape or form concerned with converting data upon retrieval. As such, converting at SELECT to the desired time zone is not an appropriate answer.
When inserting a value into a timestamp with time zone field, it is retrieved (and thus presumably stored) with the timestamp converted to the local time zone of the database at the time it was inserted.
That is so say, a timestamp inserted as 2012-01-01 00:00:00+00:00 is retrieved as 2011-12-31 19:00:00-05, where the local time zone of the database at the time of the insert was -05. Timestamps that were inserted during daylight savings time, when the database was at -04, are returned using the -04 time zone.
What I want is for all timestamps to use an arbitrary time zone when stored (and thus all be retrieved without any additional work as having that time zone). That is to say, were the server orbiting the planet, all times would be at +00:00 (arbitrary time zone), instead of -12:00 to +12:00.
Can I insert into a timestamp with time zone column such that all timestamps are stored relative to an arbitrary time zone? If so, how?
Original follows.
When inserting a value into a timestamp with time zone field, it is being converted to the server's current time zone.
Example: If I insert a value specifying a time zone of -1, retrieving it will give back the time at -5 (the time zone of the server at the time it was inserted).
Is it possible to specify that it should be stored using an arbitrary time zone?
Note: This question is not how to convert the returned time to another time zone, this is specific to how the time is stored.
You have to save the time zone offset in addition to the timestamp.
As #Milen already explained (and linked to the manual): a timestamp only saves a point in time (as abstract value). The time zone modifier is not saved, it only serves to adjust the timestamp relative to UTC.
Consider the following demo:
-- DROP TABLE tbl;
CREATE TEMP TABLE tbl (id int, myts timestamptz, mytz interval);
INSERT INTO tbl VALUES
(1, now() , EXTRACT (timezone from now()) * interval '1s')
,(2, '2012-01-01 00:00-05', interval '-5h')
,(3, '2012-01-01 00:00+04', interval '4h')
,(4, '2012-11-11 20:30+03', interval '3h');
SELECT *
,(myts AT TIME ZONE mytz)::text
|| CASE WHEN mytz > '0:0' THEN '+' ELSE '' END
|| to_char(mytz, 'FMHH24:mi') AS timestamp_at_origin
FROM tbl;
Run it locally to see. Pay special attention to the details of the AT TIME ZONE construct, and how I extract the time zone from the (local!) timestamp with time zone.
now() returns timestamp with time zone or timestamptz for short.
EXTRACT (timezone from now()) * interval '1s'
timestamp_at_origin displays the timestamp with time zone as seen at its origin. If I understood your question, then that is what you are looking for.
You could further improve formatting.
You may be interested in this related question which sheds some light on the ambiguities and pitfalls of time zones.
When inserting a value into a timestamp with time zone field what actually happens is the timestamp is converted to UTC. Another matter altogether is to what time zone that value is converted on output. There are a few ways to control that:
When a timestamp with time zone value is output, it is always
converted from UTC to the current timezone zone, and displayed as
local time in that zone. To see the time in another time zone, either
change timezone or use the AT TIME ZONE construct (see Section
9.9.3).
You can do your select with an at time zone operator:
select insertTs at time zone 'CST' from table
See more here.
I always store times in GMT so that the client can convert based on it's current GMT offset (the GMT offest is available in most language).
I write C# - so I can easily convert all DateTime objects to GMT using DateTime.ToUniversalTime() as I store data in the database.
I am not sure what language you are using or how to convert all times to GMT in postgressql, but from a logic standpoint - storing all times in GMT will allow a uniform time zone that all other time zones can easily relate to.
Hope this helps!
Greenwich Mean Time

Which timestamp type should I choose in a PostgreSQL database?

I would like to define a best practice for storing timestamps in my Postgres database in the context of a multi-timezone project.
I can
choose TIMESTAMP WITHOUT TIME ZONE and remember which timezone was used at insertion time for this field
choose TIMESTAMP WITHOUT TIME ZONE and add another field which will contain the name of the timezone that was used at insertion time
choose TIMESTAMP WITH TIME ZONE and insert the timestamps accordingly
I have a slight preference for option 3 (timestamp with time zone) but would like to have an educated opinion on the matter.
First off, PostgreSQL’s time handling and arithmetic is fantastic and Option 3 is fine in the general case. It is, however, an incomplete view of time and timezones and can be supplemented:
Store the name of a user’s time zone as a user preference (e.g. America/Los_Angeles, not -0700).
Have user events/time data submitted local to their frame of reference (most likely an offset from UTC, such as -0700).
In application, convert the time to UTC and stored using a TIMESTAMP WITH TIME ZONE column.
Return time requests local to a user's time zone (i.e. convert from UTC to America/Los_Angeles).
Set your database's timezone to UTC.
This option doesn’t always work because it can be hard to get a user’s time zone and hence the hedge advice to use TIMESTAMP WITH TIME ZONE for lightweight applications. That said, let me explain some background aspects of this this Option 4 in more detail.
Like Option 3, the reason for the WITH TIME ZONE is because the time at which something happened is an absolute moment in time. WITHOUT TIME ZONE yields a relative time zone. Don't ever, ever, ever mix absolute and relative TIMESTAMPs.
From a programmatic and consistency perspective, ensure all calculations are made using UTC as the time zone. This isn’t a PostgreSQL requirement, but it helps when integrating with other programming languages or environments. Setting a CHECK on the column to make sure the write to the time stamp column has a time zone offset of 0 is a defensive position that prevents a few classes of bugs (e.g. a script dumps data to a file and something else sorts the time data using a lexical sort). Again, PostgreSQL doesn’t need this to do date calculations correctly or to convert between time zones (i.e. PostgreSQL is very adept at converting times between any two arbitrary time zones). To ensure data going in to the database is stored with an offset of zero:
CREATE TABLE my_tbl (
my_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
CHECK(EXTRACT(TIMEZONE FROM my_timestamp) = '0')
);
test=> SET timezone = 'America/Los_Angeles';
SET
test=> INSERT INTO my_tbl (my_timestamp) VALUES (NOW());
ERROR: new row for relation "my_tbl" violates check constraint "my_tbl_my_timestamp_check"
test=> SET timezone = 'UTC';
SET
test=> INSERT INTO my_tbl (my_timestamp) VALUES (NOW());
INSERT 0 1
It's not 100% perfect, but it provides a strong enough anti-footshooting measure that makes sure the data is already converted to UTC. There are lots of opinions on how to do this, but this seems to be the best in practice from my experience.
Criticisms of database time zone handling is largely justified (there are plenty of databases that handle this with great incompetence), however PostgreSQL’s handling of timestamps and timezones is pretty awesome (despite a few "features" here and there). For example, one such feature:
-- Make sure we're all working off of the same local time zone
test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT NOW();
now
-------------------------------
2011-05-27 15:47:58.138995-07
(1 row)
test=> SELECT NOW() AT TIME ZONE 'UTC';
timezone
----------------------------
2011-05-27 22:48:02.235541
(1 row)
Note that AT TIME ZONE 'UTC' strips time zone info and creates a relative TIMESTAMP WITHOUT TIME ZONE using your target’s frame of reference (UTC).
When converting from an incomplete TIMESTAMP WITHOUT TIME ZONE to a TIMESTAMP WITH TIME ZONE, the missing time zone is inherited from your connection:
test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM NOW());
date_part
-----------
-7
(1 row)
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM TIMESTAMP WITH TIME ZONE '2011-05-27 22:48:02.235541');
date_part
-----------
-7
(1 row)
-- Now change to UTC
test=> SET timezone = 'UTC';
SET
-- Create an absolute time with timezone offset:
test=> SELECT NOW();
now
-------------------------------
2011-05-27 22:48:40.540119+00
(1 row)
-- Creates a relative time in a given frame of reference (i.e. no offset)
test=> SELECT NOW() AT TIME ZONE 'UTC';
timezone
----------------------------
2011-05-27 22:48:49.444446
(1 row)
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM NOW());
date_part
-----------
0
(1 row)
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM TIMESTAMP WITH TIME ZONE '2011-05-27 22:48:02.235541');
date_part
-----------
0
(1 row)
The bottom line:
store a user’s time zone as a named label (e.g. America/Los_Angeles) and not an offset from UTC (e.g. -0700)
use UTC for everything unless there is a compelling reason to store a non-zero offset
treat all non-zero UTC times as an input error
never mix and match relative and absolute timestamps
also use UTC as the timezone in the database if possible
Random programming language note: Python's datetime data type is very good at maintaining the distinction between absolute vs relative times (albeit frustrating at first until you supplement it with a library like PyTZ).
EDIT
Let me explain the difference between relative vs absolute a bit more.
Absolute time is used to record an event. Examples: "User 123 logged in" or "a graduation ceremonies start at 2011-05-28 2pm PST." Regardless of your local time zone, if you could teleport to where the event occurred, you could witness the event happening. Most time data in a database is absolute (and therefore should be TIMESTAMP WITH TIME ZONE, ideally with a +0 offset and a textual label representing the rules governing the particular timezone - not an offset).
A relative event would be to record or schedule the time of something from the perspective of a yet-to-be-determined time zone. Examples: "our business's doors open at 8am and close at 9pm", "let's meet every Monday at 7am for a weekly breakfast meeting," or "every Halloween at 8pm." In general, relative time is used in a template or factory for events, and absolute time is used for almost everything else. There is one rare exception that’s worth pointing out which should illustrate the value of relative times. For future events that are far enough in the future where there could be uncertainty about the absolute time at which something could occur, use a relative timestamp. Here’s a real world example:
Suppose it’s the year 2004 and you need to schedule a delivery on October 31st in 2008 at 1pm on the West Coast of the US (i.e. America/Los_Angeles/PST8PDT). If you stored that using absolute time using ’2008-10-31 21:00:00.000000+00’::TIMESTAMP WITH TIME ZONE , the delivery would have shown up at 2pm because the US Government passed the Energy Policy Act of 2005 that changed the rules governing daylight savings time. In 2004 when the delivery was scheduled, the date 10-31-2008 would have been Pacific Standard Time (+8000), but starting in year 2005+ timezone databases recognized that 10-31-2008 would have been Pacific Daylight Savings time (+0700). Storing a relative timestamp with the time zone would have resulted in a correct delivery schedule because a relative timestamp is immune to Congress’ ill-informed tampering. Where the cutoff between using relative vs absolute times for scheduling things is, is a fuzzy line, but my rule of thumb is that scheduling for anything in the future further than 3-6mo should make use of relative timestamps (scheduled = absolute vs planned = relative ???).
The other/last type of relative time is the INTERVAL. Example: "the session will time out 20 minutes after a user logs in". An INTERVAL can be used correctly with either absolute timestamps (TIMESTAMP WITH TIME ZONE) or relative timestamps (TIMESTAMP WITHOUT TIME ZONE). It is equally correct to say, "a user session expires 20min after a successful login (login_utc + session_duration)" or "our morning breakfast meeting can only last 60 minutes (recurring_start_time + meeting_length)".
Last bits of confusion: DATE, TIME, TIME WITHOUT TIME ZONE and TIME WITH TIME ZONE are all relative data types. For example: '2011-05-28'::DATE represents a relative date since you have no time zone information which could be used to identify midnight. Similarly, '23:23:59'::TIME is relative because you don't know either the time zone or the DATE represented by the time. Even with '23:59:59-07'::TIME WITH TIME ZONE, you don't know what the DATE would be. And lastly, DATE with a time zone is not in fact a DATE, it is a TIMESTAMP WITH TIME ZONE:
test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT '2011-05-11'::DATE AT TIME ZONE 'UTC';
timezone
---------------------
2011-05-11 07:00:00
(1 row)
test=> SET timezone = 'UTC';
SET
test=> SELECT '2011-05-11'::DATE AT TIME ZONE 'UTC';
timezone
---------------------
2011-05-11 00:00:00
(1 row)
Putting dates and time zones in databases is a good thing, but it is easy to get subtly incorrect results. Minimal additional effort is required to store time information correctly and completely, however that doesn’t mean the extra effort is always required.
Sean's answer is overly complex and misleading.
The fact is that both "WITH TIME ZONE" and "WITHOUT TIME ZONE" store the value as a unix-like absolute UTC timestamp. The difference is all in how the timestamp is displayed. When "WITH time zone" then the displayed value is the UTC stored value translated to the user's zone. When "WITHOUT time zone" the UTC stored value is twisted so as to show the same clock face no matter what zone the user has set".
The only situation where a "WITHOUT time zone" is usable is when a clock face value is applicable regardless of actual zone. For example, when a timestamp indicates when voting booths might close (ie. they close at 20:00 regardless of a person's timezone).
Use choice 3. Always use "WITH time zone" unless there is a very specific reason not to.
My preference is towards option 3, as Postgres can then do al ot of the work recalculating timestamps relative to timezone for you, whereas with the other two you'll have to do that yourself. The extra storage overhead of storing the timestamp with a timezone is really negligible unless you're talking millions of records, in which case you probably already have pretty meaty storage requirements anyway.