4

I have a table with a timestamp without time zone. YYYY-MM-DD HH:MM:SS

and a field "timezone" that is either "P" for Pacific or "M" for Mountain.

I need to create a field of type "timestamp with time zone"

Given the two fields I have, is there a way to do this that correctly accounts for Daylight Saving Time?

Specifically: timestamp: 2013-11-03 01:00:00 timezone: "P" would become: 2013-11-03 01:00:00-07

and timestamp: 2013-11-03 03:00:00 timezone: "P" would become: 2013-11-03 03:00:00-08

skihansen
  • 83
  • 1
  • 2
  • 8
  • Your question makes more sense following your edit, but the fundamental problem remains. "2013-11-03 01:00:00" is still ambiguous: it can be the minute following "2013-11-03 00:59:00-07" or the minute following "2013-11-03 01:59:00-07". – Bruno Sep 29 '13 at 11:32

3 Answers3

1

First, when saying that the result would become for example 2013-11-03 01:00:00-07, it should be added that this actually depends on the time zone setting of the SQL client. For instance a session in european time will never read 2013-11-03 01:00:00-07 as the value of a timestamp with time zone because no european country is ever at GMT-07.

That said, the conversion can be done with the AT TIME ZONE construct applied to a timestamp without time zone.

Assuming we run this from the US/Pacific time zone:

SET time zone 'US/Pacific';

SELECT t AT TIME ZONE 
     case z when 'P' then 'US/Pacific' when 'M' then 'US/Mountain' end  
  from (values
    ('2013-11-03 01:00:00'::timestamp, 'P'),
    ('2013-11-03 03:00:00'::timestamp, 'P')
  ) as v(t,z);

The result is:

        timezone        
------------------------
 2013-11-03 01:00:00-08
 2013-11-03 03:00:00-08

2013-11-03 01:00:00 AT time zone 'US/Pacific' has an ambiguity because it belongs to the hour span which happens first in the -07 timezone, and then a second time in the -08 timezone after the DST switch. The interpretation of postgres is to see it in the -08 timezone. If we consider the minute before, it falls into the -07 timezone.

Daniel Vérité
  • 58,074
  • 15
  • 129
  • 156
1

The difference between TIMESTAMP WITHOUT TIME ZONE and TIMESTAMP WITH TIME ZONE (TIMESTAMPTZ) can be quite tricky to understand if you consider their names. (In fact, the specifications seem to be sufficiently confusing so that various RDBMS implement it in a different way.)

In PostgreSQL, neither types store the time zone of the value when it was stored, but TIMESTAMPTZ stores the value as a precise instant in time based on the UTC reference, whereas TIMESTAMP WITHOUT TIME ZONE is always relative.

  • When queried, a TIMESTAMPTZ will be adjusted to represent the same instant in time as initially stored (in whichever part of the world this was) as the instant it would be in the current time zone as configured by the client.
  • A TIMESTAMP WITHOUT TIME ZONE will always be the same value relative to the time zone configured by the client, even if the time zone where you query it from differs: the instant represented by 2013-11-03 03:00:00 will be ambiguous and depend on the client settings.

Presumably, you used your "timezone" column (P or M) with your TIMESTAMP WITHOUT TIME ZONE to compensate for the ambiguity in the input value.

In principle, if you are in the same relative time zone as the one where you stored the timestamp, you should get the same value back, so if you've set your client in the US/Pacific time zone and if you've stored 2013-11-03 03:00:00 in your P time zone, you should get 2013-11-03 03:00:00 back. However, this is only valid when there is no ambiguity in the relative value.

The problem here in your first example is that there already is some ambiguity:

timestamp: 2013-11-03 01:00:00 timezone: "P" would become: 2013-11-03 01:00:00-07

2013-11-03 01:00:00 can represent two distinct instants in time in the US/Pacific time zone, so with just 2013-11-03 01:00:00 and "P", you've already lost information that you won't be able to recover.

If you just wanted it to change between '-08' and '-07' depending on the DST setting at that instant in time, this would have been done for you automatically, but you should have use a TIMESTAMPTZ in the first place, to be precise on which instant in time you were representing.

Here is an example where the initial time zone is kept, so you can see the change between '-08' and '-07':

SET time zone 'US/Pacific';

SELECT t AS "Date/Time for US/Pacific",
       t AT time zone 'UTC' "Date/Time in UTC"
FROM (VALUES
    ('2013-11-03 00:00:00-07'::timestamptz),
    ('2013-11-03 01:00:00-07'::timestamptz),
    ('2013-11-03 02:00:00-07'::timestamptz),
    ('2013-11-03 03:00:00-07'::timestamptz)) AS v(t);

Results:

| DATE/TIME FOR US/PACIFIC | DATE/TIME IN UTC    |
|--------------------------|---------------------|
| 2013-11-03 00:00:00-07   | 2013-11-03 07:00:00 |
| 2013-11-03 01:00:00-07   | 2013-11-03 08:00:00 |
| 2013-11-03 01:00:00-08   | 2013-11-03 09:00:00 |
| 2013-11-03 02:00:00-08   | 2013-11-03 10:00:00 |

Unfortunately, there is no way to handle DST change with just your two fields.

It's certainly worth reading the Date/Time types section of the PostgreSQL manual, as well as paying attention to the "return types" column of the table in the AT TIME ZONE documentation for a better understanding of these problems.

Bruno
  • 119,590
  • 31
  • 270
  • 376
0

Check if this makes any sense to you

set timezone to 'PST8PDT';

select now();
              now              
-------------------------------
 2013-09-28 03:24:20.169189-07

select ts,
    ts at time zone 'PST' as "PST",
    ts at time zone 'PDT' as "PDT"
from (values
    ('2013-11-03 01:00:00'::timestamp),
    ('2013-11-03 02:00:00'),
    ('2013-11-03 03:00:00')
) s (ts)
;
         ts          |          PST           |          PDT           
---------------------+------------------------+------------------------
 2013-11-03 01:00:00 | 2013-11-03 01:00:00-08 | 2013-11-03 01:00:00-07
 2013-11-03 02:00:00 | 2013-11-03 02:00:00-08 | 2013-11-03 01:00:00-08
 2013-11-03 03:00:00 | 2013-11-03 03:00:00-08 | 2013-11-03 02:00:00-08
Clodoaldo Neto
  • 118,695
  • 26
  • 233
  • 260