From 3dbbbbf8e98329e1eea9920436defc64af3594d3 Mon Sep 17 00:00:00 2001 From: Bruce Momjian Date: Sat, 23 Jul 2005 14:25:34 +0000 Subject: [PATCH] Andrew pointed out that the current fix didn't handle dates that were near daylight savings time boudaries. This handles it properly, e.g. test=> select '2005-04-03 04:00:00'::timestamp at time zone 'America/Los_Angeles'; timezone ------------------------ 2005-04-03 07:00:00-04 (1 row) --- src/backend/utils/adt/date.c | 6 ++--- src/backend/utils/adt/datetime.c | 16 ++++++------ src/backend/utils/adt/formatting.c | 4 +-- src/backend/utils/adt/nabstime.c | 4 +-- src/backend/utils/adt/timestamp.c | 39 ++++++++++++++++-------------- src/include/utils/datetime.h | 4 +-- 6 files changed, 38 insertions(+), 35 deletions(-) diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index fdb3942f4f..477d7993e6 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/date.c,v 1.118 2005/07/22 05:03:09 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/date.c,v 1.119 2005/07/23 14:25:33 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -301,7 +301,7 @@ date2timestamptz(DateADT dateVal) tm->tm_hour = 0; tm->tm_min = 0; tm->tm_sec = 0; - tz = DetermineLocalTimeZone(tm); + tz = DetermineTimeZoneOffset(tm, global_timezone); #ifdef HAVE_INT64_TIMESTAMP result = dateVal * USECS_PER_DAY + tz * USECS_PER_SEC; @@ -2231,7 +2231,7 @@ time_timetz(PG_FUNCTION_ARGS) GetCurrentDateTime(tm); time2tm(time, tm, &fsec); - tz = DetermineLocalTimeZone(tm); + tz = DetermineTimeZoneOffset(tm, global_timezone); result = (TimeTzADT *) palloc(sizeof(TimeTzADT)); diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index 04ec7cb216..9e5be7d4cf 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.156 2005/07/22 03:46:33 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.157 2005/07/23 14:25:33 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -1612,7 +1612,7 @@ DecodeDateTime(char **field, int *ftype, int nf, if (fmask & DTK_M(DTZMOD)) return DTERR_BAD_FORMAT; - *tzp = DetermineLocalTimeZone(tm); + *tzp = DetermineTimeZoneOffset(tm, global_timezone); } } @@ -1620,10 +1620,10 @@ DecodeDateTime(char **field, int *ftype, int nf, } -/* DetermineLocalTimeZone() +/* DetermineTimeZoneOffset() * * Given a struct pg_tm in which tm_year, tm_mon, tm_mday, tm_hour, tm_min, and - * tm_sec fields are set, attempt to determine the applicable local zone + * tm_sec fields are set, attempt to determine the applicable time zone * (ie, regular or daylight-savings time) at that time. Set the struct pg_tm's * tm_isdst field accordingly, and return the actual timezone offset. * @@ -1632,7 +1632,7 @@ DecodeDateTime(char **field, int *ftype, int nf, * of mktime(), anyway. */ int -DetermineLocalTimeZone(struct pg_tm *tm) +DetermineTimeZoneOffset(struct pg_tm *tm, pg_tz *tzp) { int date, sec; @@ -1648,7 +1648,7 @@ DetermineLocalTimeZone(struct pg_tm *tm) after_isdst; int res; - if (HasCTZSet) + if (tzp == global_timezone && HasCTZSet) { tm->tm_isdst = 0; /* for lack of a better idea */ return CTimeZone; @@ -1687,7 +1687,7 @@ DetermineLocalTimeZone(struct pg_tm *tm) &before_gmtoff, &before_isdst, &boundary, &after_gmtoff, &after_isdst, - global_timezone); + tzp); if (res < 0) goto overflow; /* failure? */ @@ -2282,7 +2282,7 @@ DecodeTimeOnly(char **field, int *ftype, int nf, tmp->tm_hour = tm->tm_hour; tmp->tm_min = tm->tm_min; tmp->tm_sec = tm->tm_sec; - *tzp = DetermineLocalTimeZone(tmp); + *tzp = DetermineTimeZoneOffset(tmp, global_timezone); tm->tm_isdst = tmp->tm_isdst; } diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index 9f1cc1a476..b473b9b13b 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -1,7 +1,7 @@ /* ----------------------------------------------------------------------- * formatting.c * - * $PostgreSQL: pgsql/src/backend/utils/adt/formatting.c,v 1.92 2005/07/21 03:56:16 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/formatting.c,v 1.93 2005/07/23 14:25:33 momjian Exp $ * * * Portions Copyright (c) 1999-2005, PostgreSQL Global Development Group @@ -2989,7 +2989,7 @@ to_timestamp(PG_FUNCTION_ARGS) do_to_timestamp(date_txt, fmt, &tm, &fsec); - tz = DetermineLocalTimeZone(&tm); + tz = DetermineTimeZoneOffset(&tm, global_timezone); if (tm2timestamp(&tm, fsec, &tz, &result) != 0) ereport(ERROR, diff --git a/src/backend/utils/adt/nabstime.c b/src/backend/utils/adt/nabstime.c index d4d9d25e48..739345b7ba 100644 --- a/src/backend/utils/adt/nabstime.c +++ b/src/backend/utils/adt/nabstime.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/nabstime.c,v 1.141 2005/07/22 19:55:50 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/nabstime.c,v 1.142 2005/07/23 14:25:33 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -474,7 +474,7 @@ timestamp_abstime(PG_FUNCTION_ARGS) result = NOEND_ABSTIME; else if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) == 0) { - tz = DetermineLocalTimeZone(tm); + tz = DetermineTimeZoneOffset(tm, global_timezone); result = tm2abstime(tm, tz); } else diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 999f8fbf52..da7a87a1bb 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.143 2005/07/23 02:02:27 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.144 2005/07/23 14:25:34 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -2100,7 +2100,7 @@ timestamptz_pl_interval(PG_FUNCTION_ARGS) if (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1]) tm->tm_mday = (day_tab[isleap(tm->tm_year)][tm->tm_mon - 1]); - tz = DetermineLocalTimeZone(tm); + tz = DetermineTimeZoneOffset(tm, global_timezone); if (tm2timestamp(tm, fsec, &tz, ×tamp) != 0) ereport(ERROR, @@ -2124,7 +2124,7 @@ timestamptz_pl_interval(PG_FUNCTION_ARGS) julian = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) + span->day; j2date(julian, &tm->tm_year, &tm->tm_mon, &tm->tm_mday); - tz = DetermineLocalTimeZone(tm); + tz = DetermineTimeZoneOffset(tm, global_timezone); if (tm2timestamp(tm, fsec, &tz, ×tamp) != 0) ereport(ERROR, @@ -3104,7 +3104,7 @@ timestamptz_trunc(PG_FUNCTION_ARGS) } if (redotz) - tz = DetermineLocalTimeZone(tm); + tz = DetermineTimeZoneOffset(tm, global_timezone); if (tm2timestamp(tm, fsec, &tz, &result) != 0) ereport(ERROR, @@ -3529,7 +3529,7 @@ timestamp_part(PG_FUNCTION_ARGS) (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); - tz = DetermineLocalTimeZone(tm); + tz = DetermineTimeZoneOffset(tm, global_timezone); if (tm2timestamp(tm, fsec, &tz, ×tamptz) != 0) ereport(ERROR, @@ -3924,12 +3924,11 @@ interval_part(PG_FUNCTION_ARGS) /* timestamp_zone() * Encode timestamp type with specified time zone. - * Returns timestamp with time zone, with the input - * rotated from local time to the specified zone. - * This function is tricky because instead of shifting - * the time _to_ a new time zone, it sets the time to _be_ - * the specified timezone. This requires trickery - * of double-subtracting the requested timezone offset. + * This function is just timestamp2timestamptz() except instead of + * shifting to the global timezone, we shift to the specified timezone. + * This is different from the other AT TIME ZONE cases because instead + * of shifting to a _to_ a new time zone, it sets the time to _be_ the + * specified timezone. */ Datum timestamp_zone(PG_FUNCTION_ARGS) @@ -3943,11 +3942,12 @@ timestamp_zone(PG_FUNCTION_ARGS) int len; struct pg_tm tm; fsec_t fsec; - + bool fail; + if (TIMESTAMP_NOT_FINITE(timestamp)) PG_RETURN_TIMESTAMPTZ(timestamp); - /* Find the specified timezone? */ + /* Find the specified timezone */ len = (VARSIZE(zone) - VARHDRSZ>TZ_STRLEN_MAX) ? TZ_STRLEN_MAX : VARSIZE(zone) - VARHDRSZ; memcpy(tzname, VARDATA(zone), len); @@ -3963,8 +3963,13 @@ timestamp_zone(PG_FUNCTION_ARGS) } /* Apply the timezone change */ - if (timestamp2tm(timestamp, &tz, &tm, &fsec, NULL, tzp) != 0 || - tm2timestamp(&tm, fsec, &tz, &result) != 0) + fail = (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, tzp) != 0); + if (!fail) + { + tz = DetermineTimeZoneOffset(&tm, tzp); + fail = (tm2timestamp(&tm, fsec, &tz, &result) != 0); + } + if (fail) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -3972,8 +3977,6 @@ timestamp_zone(PG_FUNCTION_ARGS) tzname))); PG_RETURN_NULL(); } - /* Must double-adjust for timezone */ - result = dt2local(result, -tz); PG_RETURN_TIMESTAMPTZ(result); } @@ -4039,7 +4042,7 @@ timestamp2timestamptz(Timestamp timestamp) (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); - tz = DetermineLocalTimeZone(tm); + tz = DetermineTimeZoneOffset(tm, global_timezone); if (tm2timestamp(tm, fsec, &tz, &result) != 0) ereport(ERROR, diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h index 2e7155d803..572a9852f7 100644 --- a/src/include/utils/datetime.h +++ b/src/include/utils/datetime.h @@ -9,7 +9,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/datetime.h,v 1.55 2005/07/22 03:46:34 momjian Exp $ + * $PostgreSQL: pgsql/src/include/utils/datetime.h,v 1.56 2005/07/23 14:25:34 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -291,7 +291,7 @@ extern int DecodeInterval(char **field, int *ftype, extern void DateTimeParseError(int dterr, const char *str, const char *datatype); -extern int DetermineLocalTimeZone(struct pg_tm *tm); +extern int DetermineTimeZoneOffset(struct pg_tm *tm, pg_tz *tzp); extern int EncodeDateOnly(struct pg_tm *tm, int style, char *str); extern int EncodeTimeOnly(struct pg_tm *tm, fsec_t fsec, int *tzp, int style, char *str);