Commit 0171e72d authored by Tom Lane's avatar Tom Lane

Update timezone code to track the upstream changes since 2003. In particular

this adds support for 64-bit tzdata files, which is needed to support DST
calculations beyond 2038.  Add a regression test case to give some minimal
confidence that that really works.

Heikki Linnakangas
parent 2f67722d
......@@ -114,6 +114,31 @@ INSERT INTO TIMESTAMPTZ_TBL VALUES ('19970710 173201 America/Does_not_exist');
ERROR: time zone "america/does_not_exist" not recognized
SELECT '19970710 173201' AT TIME ZONE 'America/Does_not_exist';
ERROR: time zone "America/Does_not_exist" not recognized
-- Daylight saving time for timestamps beyond 32-bit time_t range.
SELECT '20500710 173201 Europe/Helsinki'::timestamptz; -- DST
timestamptz
------------------------------
Sun Jul 10 07:32:01 2050 PDT
(1 row)
SELECT '20500110 173201 Europe/Helsinki'::timestamptz; -- non-DST
timestamptz
------------------------------
Mon Jan 10 07:32:01 2050 PST
(1 row)
SELECT '205000-07-10 17:32:01 Europe/Helsinki'::timestamptz; -- DST
timestamptz
--------------------------------
Thu Jul 10 07:32:01 205000 PDT
(1 row)
SELECT '205000-01-10 17:32:01 Europe/Helsinki'::timestamptz; -- non-DST
timestamptz
--------------------------------
Fri Jan 10 07:32:01 205000 PST
(1 row)
-- Check date conversion and date arithmetic
INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997-06-10 18:32:01 PDT');
INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 10 17:32:01 1997');
......
......@@ -86,6 +86,13 @@ SELECT '19970710 173201' AT TIME ZONE 'America/New_York';
INSERT INTO TIMESTAMPTZ_TBL VALUES ('19970710 173201 America/Does_not_exist');
SELECT '19970710 173201' AT TIME ZONE 'America/Does_not_exist';
-- Daylight saving time for timestamps beyond 32-bit time_t range.
SELECT '20500710 173201 Europe/Helsinki'::timestamptz; -- DST
SELECT '20500110 173201 Europe/Helsinki'::timestamptz; -- non-DST
SELECT '205000-07-10 17:32:01 Europe/Helsinki'::timestamptz; -- DST
SELECT '205000-01-10 17:32:01 Europe/Helsinki'::timestamptz; -- non-DST
-- Check date conversion and date arithmetic
INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997-06-10 18:32:01 PDT');
......
This is a PostgreSQL adapted version of the timezone library
from:
This is a PostgreSQL adapted version of the timezone library from:
ftp://elsie.nci.nih.gov/pub/tzcode*.tar.gz
The data files under data/ are an exact copy of the latest data set
from
The code is currently synced with release 2007k. There are many cosmetic
(and not so cosmetic) differences from the original tzcode library, but
diffs in the upstream version should usually be propagated to our version.
The data files under data/ are an exact copy of the latest data set from:
ftp://elsie.nci.nih.gov/pub/tzdata*.tar.gz
......
/*
* This file is in the public domain, so clarified as of
* 1996-06-05 by Arthur David Olson (arthur_david_olson@nih.gov).
* 2006-07-17 by Arthur David Olson.
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/timezone/ialloc.c,v 1.9 2007/10/26 13:30:10 tgl Exp $
* $PostgreSQL: pgsql/src/timezone/ialloc.c,v 1.10 2008/02/16 21:16:04 tgl Exp $
*/
#include "postgres_fe.h"
......
This diff is collapsed.
......@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.58 2008/02/11 19:55:11 mha Exp $
* $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.59 2008/02/16 21:16:04 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -287,7 +287,7 @@ score_timezone(const char *tzname, struct tztry * tt)
* Load timezone directly. Don't use pg_tzset, because we don't want all
* timezones loaded in the cache at startup.
*/
if (tzload(tzname, NULL, &tz.state) != 0)
if (tzload(tzname, NULL, &tz.state, TRUE) != 0)
{
if (tzname[0] == ':' || tzparse(tzname, &tz.state, FALSE) != 0)
{
......@@ -1191,7 +1191,7 @@ pg_tzset(const char *name)
return &tzp->tz;
}
if (tzload(uppername, canonname, &tzstate) != 0)
if (tzload(uppername, canonname, &tzstate, TRUE) != 0)
{
if (uppername[0] == ':' || tzparse(uppername, &tzstate, FALSE) != 0)
{
......@@ -1463,7 +1463,8 @@ pg_tzenumerate_next(pg_tzenum *dir)
* Load this timezone using tzload() not pg_tzset(), so we don't fill
* the cache
*/
if (tzload(fullname + dir->baselen, dir->tz.TZname, &dir->tz.state) != 0)
if (tzload(fullname + dir->baselen, dir->tz.TZname, &dir->tz.state,
TRUE) != 0)
{
/* Zone could not be loaded, ignore it */
continue;
......
......@@ -9,7 +9,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/timezone/pgtz.h,v 1.21 2008/01/01 19:46:01 momjian Exp $
* $PostgreSQL: pgsql/src/timezone/pgtz.h,v 1.22 2008/02/16 21:16:04 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -43,6 +43,8 @@ struct state
int timecnt;
int typecnt;
int charcnt;
int goback;
int goahead;
pg_time_t ats[TZ_MAX_TIMES];
unsigned char types[TZ_MAX_TIMES];
struct ttinfo ttis[TZ_MAX_TYPES];
......@@ -64,7 +66,8 @@ struct pg_tz
extern int pg_open_tzfile(const char *name, char *canonname);
/* in localtime.c */
extern int tzload(const char *name, char *canonname, struct state * sp);
extern int tzload(const char *name, char *canonname, struct state * sp,
int doextend);
extern int tzparse(const char *name, struct state * sp, int lastditch);
#endif /* _PGTZ_H */
......@@ -3,10 +3,10 @@
/*
* This file is in the public domain, so clarified as of
* 1996-06-05 by Arthur David Olson (arthur_david_olson@nih.gov).
* 1996-06-05 by Arthur David Olson.
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/timezone/private.h,v 1.11 2005/02/23 04:34:21 momjian Exp $
* $PostgreSQL: pgsql/src/timezone/private.h,v 1.12 2008/02/16 21:16:04 tgl Exp $
*/
/*
......@@ -17,12 +17,13 @@
* Thank you!
*/
#include <limits.h> /* for CHAR_BIT */
#include <limits.h> /* for CHAR_BIT et al. */
#include <sys/wait.h> /* for WIFEXITED and WEXITSTATUS */
#include <unistd.h> /* for F_OK and R_OK */
#include "pgtime.h"
#define GRANDPARENTED "Local time zone must be set--see zic manual page"
#ifndef WIFEXITED
#define WIFEXITED(status) (((status) & 0xff) == 0)
......@@ -34,22 +35,6 @@
/* Unlike <ctype.h>'s isdigit, this also works if c < 0 | c > UCHAR_MAX. */
#define is_digit(c) ((unsigned)(c) - '0' <= 9)
/*
* SunOS 4.1.1 headers lack EXIT_SUCCESS.
*/
#ifndef EXIT_SUCCESS
#define EXIT_SUCCESS 0
#endif /* !defined EXIT_SUCCESS */
/*
* SunOS 4.1.1 headers lack EXIT_FAILURE.
*/
#ifndef EXIT_FAILURE
#define EXIT_FAILURE 1
#endif /* !defined EXIT_FAILURE */
/*
* SunOS 4.1.1 libraries lack remove.
*/
......@@ -70,7 +55,7 @@ extern char *imalloc(int n);
extern void *irealloc(void *pointer, int size);
extern void icfree(char *pointer);
extern void ifree(char *pointer);
extern char *scheck(const char *string, const char *format);
extern const char *scheck(const char *string, const char *format);
/*
......@@ -93,6 +78,15 @@ extern char *scheck(const char *string, const char *format);
#define TYPE_SIGNED(type) (((type) -1) < 0)
#endif /* !defined TYPE_SIGNED */
/*
* Since the definition of TYPE_INTEGRAL contains floating point numbers,
* it cannot be used in preprocessor directives.
*/
#ifndef TYPE_INTEGRAL
#define TYPE_INTEGRAL(type) (((type) 0.5) != 0.5)
#endif /* !defined TYPE_INTEGRAL */
#ifndef INT_STRLEN_MAXIMUM
/*
* 302 / 1000 is log10(2.0) rounded up.
......@@ -107,6 +101,26 @@ extern char *scheck(const char *string, const char *format);
#undef _
#define _(msgid) (msgid)
#ifndef YEARSPERREPEAT
#define YEARSPERREPEAT 400 /* years before a Gregorian repeat */
#endif /* !defined YEARSPERREPEAT */
/*
** The Gregorian year averages 365.2425 days, which is 31556952 seconds.
*/
#ifndef AVGSECSPERYEAR
#define AVGSECSPERYEAR 31556952L
#endif /* !defined AVGSECSPERYEAR */
#ifndef SECSPERREPEAT
#define SECSPERREPEAT ((int64) YEARSPERREPEAT * (int64) AVGSECSPERYEAR)
#endif /* !defined SECSPERREPEAT */
#ifndef SECSPERREPEAT_BITS
#define SECSPERREPEAT_BITS 34 /* ceil(log2(SECSPERREPEAT)) */
#endif /* !defined SECSPERREPEAT_BITS */
/*
* UNIX was a registered trademark of The Open Group in 2003.
*/
......
/*
* This file is in the public domain, so clarified as of
* 1996-06-05 by Arthur David Olson (arthur_david_olson@nih.gov).
* 2006-07-17 by Arthur David Olson.
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/timezone/scheck.c,v 1.8 2007/10/26 13:30:10 tgl Exp $
* $PostgreSQL: pgsql/src/timezone/scheck.c,v 1.9 2008/02/16 21:16:04 tgl Exp $
*/
#include "postgres_fe.h"
......@@ -11,18 +11,17 @@
#include "private.h"
char *
const char *
scheck(const char *string, const char *format)
{
char *fbuf;
const char *fp;
char *tp;
int c;
char *result;
const char *result;
char dummy;
static char nada;
result = &nada;
result = "";
if (string == NULL || format == NULL)
return result;
fbuf = imalloc((int) (2 * strlen(format) + 4));
......
......@@ -15,7 +15,7 @@
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/timezone/strftime.c,v 1.11 2006/07/14 14:52:27 momjian Exp $
* $PostgreSQL: pgsql/src/timezone/strftime.c,v 1.12 2008/02/16 21:16:04 tgl Exp $
*/
#include "postgres.h"
......@@ -92,6 +92,7 @@ static char *_add(const char *, char *, const char *);
static char *_conv(int, const char *, char *, const char *);
static char *_fmt(const char *, const struct pg_tm *, char *,
const char *, int *);
static char * _yconv(int, int, int, int, char *, const char *);
#define IN_NONE 0
#define IN_SOME 1
......@@ -160,8 +161,8 @@ _fmt(const char *format, const struct pg_tm * t, char *pt, const char *ptlim,
* ...whereas now POSIX 1003.2 calls for something
* completely different. (ado, 1993-05-24)
*/
pt = _conv((t->tm_year + TM_YEAR_BASE) / 100,
"%02d", pt, ptlim);
pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 0,
pt, ptlim);
continue;
case 'c':
{
......@@ -308,11 +309,13 @@ _fmt(const char *format, const struct pg_tm * t, char *pt, const char *ptlim,
*/
{
int year;
int base;
int yday;
int wday;
int w;
year = t->tm_year + TM_YEAR_BASE;
year = t->tm_year;
base = TM_YEAR_BASE;
yday = t->tm_yday;
wday = t->tm_wday;
for (;;)
......@@ -321,7 +324,7 @@ _fmt(const char *format, const struct pg_tm * t, char *pt, const char *ptlim,
int bot;
int top;
len = isleap(year) ?
len = isleap_sum(year, base) ?
DAYSPERLYEAR :
DAYSPERNYEAR;
......@@ -342,7 +345,7 @@ _fmt(const char *format, const struct pg_tm * t, char *pt, const char *ptlim,
top += len;
if (yday >= top)
{
++year;
++base;
w = 1;
break;
}
......@@ -352,8 +355,8 @@ _fmt(const char *format, const struct pg_tm * t, char *pt, const char *ptlim,
DAYSPERWEEK);
break;
}
--year;
yday += isleap(year) ?
--base;
yday += isleap_sum(year, base) ?
DAYSPERLYEAR :
DAYSPERNYEAR;
}
......@@ -363,11 +366,11 @@ _fmt(const char *format, const struct pg_tm * t, char *pt, const char *ptlim,
else if (*format == 'g')
{
*warnp = IN_ALL;
pt = _conv(year % 100, "%02d",
pt = _yconv(year, base, 0, 1,
pt, ptlim);
}
else
pt = _conv(year, "%04d",
pt = _yconv(year, base, 1, 1,
pt, ptlim);
}
continue;
......@@ -405,11 +408,11 @@ _fmt(const char *format, const struct pg_tm * t, char *pt, const char *ptlim,
continue;
case 'y':
*warnp = IN_ALL;
pt = _conv((t->tm_year + TM_YEAR_BASE) % 100,
"%02d", pt, ptlim);
pt = _yconv(t->tm_year, TM_YEAR_BASE, 0, 1,
pt, ptlim);
continue;
case 'Y':
pt = _conv(t->tm_year + TM_YEAR_BASE, "%04d",
pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 1,
pt, ptlim);
continue;
case 'Z':
......@@ -480,3 +483,43 @@ _add(const char *str, char *pt, const char *ptlim)
++pt;
return pt;
}
/*
* POSIX and the C Standard are unclear or inconsistent about
* what %C and %y do if the year is negative or exceeds 9999.
* Use the convention that %C concatenated with %y yields the
* same output as %Y, and that %Y contains at least 4 bytes,
* with more only if necessary.
*/
static char *
_yconv(const int a, const int b, const int convert_top,
const int convert_yy, char *pt, const char * const ptlim)
{
int lead;
int trail;
#define DIVISOR 100
trail = a % DIVISOR + b % DIVISOR;
lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
trail %= DIVISOR;
if (trail < 0 && lead > 0)
{
trail += DIVISOR;
--lead;
}
else if (lead < 0 && trail > 0)
{
trail -= DIVISOR;
++lead;
}
if (convert_top)
{
if (lead == 0 && trail < 0)
pt = _add("-0", pt, ptlim);
else pt = _conv(lead, "%02d", pt, ptlim);
}
if (convert_yy)
pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim);
return pt;
}
......@@ -3,10 +3,10 @@
/*
* This file is in the public domain, so clarified as of
* 1996-06-05 by Arthur David Olson (arthur_david_olson@nih.gov).
* 1996-06-05 by Arthur David Olson.
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/timezone/tzfile.h,v 1.6 2005/10/15 02:49:51 momjian Exp $
* $PostgreSQL: pgsql/src/timezone/tzfile.h,v 1.7 2008/02/16 21:16:04 tgl Exp $
*/
/*
......@@ -33,7 +33,8 @@
struct tzhead
{
char tzh_magic[4]; /* TZ_MAGIC */
char tzh_reserved[16]; /* reserved for future use */
char tzh_version[1]; /* '\0' or '2' as of 2005 */
char tzh_reserved[15]; /* reserved--must be zero */
char tzh_ttisgmtcnt[4]; /* coded number of trans. time flags */
char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */
char tzh_leapcnt[4]; /* coded number of leap seconds */
......@@ -69,17 +70,21 @@ struct tzhead
*/
/*
* In the current implementation, "tzset()" refuses to deal with files that
* exceed any of the limits below.
* If tzh_version is '2' or greater, the above is followed by a second instance
* of tzhead and a second instance of the data in which each coded transition
* time uses 8 rather than 4 chars,
* then a POSIX-TZ-environment-variable-style string for use in handling
* instants after the last transition time stored in the file
* (with nothing between the newlines if there is no POSIX representation for
* such instants).
*/
/*
* The TZ_MAX_TIMES value below is enough to handle a bit more than a
* year's worth of solar time (corrected daily to the nearest second) or
* 138 years of Pacific Presidential Election time
* (where there are three time zone transitions every fourth year).
* In the current implementation, "tzset()" refuses to deal with files that
* exceed any of the limits below.
*/
#define TZ_MAX_TIMES 370
#define TZ_MAX_TIMES 1200
#define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */
......@@ -124,11 +129,20 @@ struct tzhead
#define EPOCH_YEAR 1970
#define EPOCH_WDAY TM_THURSDAY
#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
/*
* Accurate only for the past couple of centuries;
* that will probably do.
* Since everything in isleap is modulo 400 (or a factor of 400), we know that
* isleap(y) == isleap(y % 400)
* and so
* isleap(a + b) == isleap((a + b) % 400)
* or
* isleap(a + b) == isleap(a % 400 + b % 400)
* This is true even if % means modulo rather than Fortran remainder
* (which is allowed by C89 but not C99).
* We use this to avoid addition overflow problems.
*/
#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400)
#endif /* !defined TZFILE_H */
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment