aboutsummaryrefslogtreecommitdiff
path: root/src/timezone/pgtz.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/timezone/pgtz.c')
-rw-r--r--src/timezone/pgtz.c263
1 files changed, 262 insertions, 1 deletions
diff --git a/src/timezone/pgtz.c b/src/timezone/pgtz.c
index c51e3b0382b..5356f40e3ca 100644
--- a/src/timezone/pgtz.c
+++ b/src/timezone/pgtz.c
@@ -6,13 +6,22 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.9 2004/05/18 03:36:45 momjian Exp $
+ * $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.10 2004/05/21 05:08:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
+#define NO_REDEFINE_TIMEFUNCS
+#include "postgres.h"
+
+#include <ctype.h>
+
+#include "miscadmin.h"
+#include "pgtime.h"
#include "pgtz.h"
#include "tzfile.h"
+#include "utils/elog.h"
+#include "utils/guc.h"
static char tzdir[MAXPGPATH];
@@ -30,3 +39,255 @@ pg_TZDIR(void)
done_tzdir = 1;
return tzdir;
}
+
+/*
+ * Try to determine the system timezone (as opposed to the timezone
+ * set in our own library).
+ */
+#define T_YEAR (60*60*24*365)
+#define T_MONTH (60*60*24*30)
+
+struct tztry {
+ time_t std_t,dst_t;
+ char std_time[TZ_STRLEN_MAX+1],dst_time[TZ_STRLEN_MAX+1];
+ int std_ofs,dst_ofs;
+ struct tm std_tm, dst_tm;
+};
+
+
+static bool compare_tm(struct tm *s, struct pg_tm *p) {
+ if (s->tm_sec != p->tm_sec ||
+ s->tm_min != p->tm_min ||
+ s->tm_hour != p->tm_hour ||
+ s->tm_mday != p->tm_mday ||
+ s->tm_mon != p->tm_mon ||
+ s->tm_year != p->tm_year ||
+ s->tm_wday != p->tm_wday ||
+ s->tm_yday != p->tm_yday ||
+ s->tm_isdst != p->tm_isdst)
+ return false;
+ return true;
+}
+
+static bool try_timezone(char *tzname, struct tztry *tt, bool checkdst) {
+ struct pg_tm *pgtm;
+
+ if (!pg_tzset(tzname))
+ return false; /* If this timezone couldn't be picked at all */
+
+ /* Verify standard time */
+ pgtm = pg_localtime(&(tt->std_t));
+ if (!pgtm)
+ return false;
+ if (!compare_tm(&(tt->std_tm), pgtm))
+ return false;
+
+ if (!checkdst)
+ return true;
+
+ /* Now check daylight time */
+ pgtm = pg_localtime(&(tt->dst_t));
+ if (!pgtm)
+ return false;
+ if (!compare_tm(&(tt->dst_tm), pgtm))
+ return false;
+
+ return true;
+}
+
+static int get_timezone_offset(struct tm *tm) {
+#if defined(HAVE_STRUCT_TM_TM_ZONE)
+ return tm->tm_gmtoff;
+#elif defined(HAVE_INT_TIMEZONE)
+#ifdef HAVE_UNDERSCORE_TIMEZONE
+ return -_timezone;
+#else
+ return -timezone;
+#endif
+#else
+#error No way to determine TZ? Can this happen?
+#endif
+}
+
+
+#ifdef WIN32
+#define TZABBREV(tz) win32_get_timezone_abbrev(tz)
+
+static char *win32_get_timezone_abbrev(char *tz) {
+ static char w32tzabbr[TZ_STRLEN_MAX+1];
+ int l = 0;
+ char *c;
+
+ for (c = tz; *c; c++) {
+ if (isupper(*c))
+ w32tzabbr[l++] = *c;
+ }
+ w32tzabbr[l] = '\0';
+ return w32tzabbr;
+}
+
+#else
+#define TZABBREV(tz) tz
+#endif
+
+
+/*
+ * Try to identify a timezone name (in our terminology) that matches the
+ * observed behavior of the system timezone library. We cannot assume that
+ * the system TZ environment setting (if indeed there is one) matches our
+ * terminology, so ignore it and just look at what localtime() returns.
+ */
+static char *
+identify_system_timezone(void)
+{
+ static char __tzbuf[TZ_STRLEN_MAX+1];
+ bool std_found=false,
+ dst_found=false;
+ time_t tnow = time(NULL);
+ time_t t;
+ struct tztry tt;
+ char cbuf[TZ_STRLEN_MAX+1];
+
+ /* Initialize OS timezone library */
+ tzset();
+
+ memset(&tt, 0, sizeof(tt));
+
+ for (t = tnow; t < tnow+T_YEAR; t += T_MONTH) {
+ struct tm *tm = localtime(&t);
+
+ if (tm->tm_isdst == 0 && !std_found) {
+ /* Standard time */
+ memcpy(&tt.std_tm, tm, sizeof(struct tm));
+ memset(cbuf,0,sizeof(cbuf));
+ strftime(cbuf, sizeof(cbuf)-1, "%Z", tm); /* zone abbr */
+ strcpy(tt.std_time, TZABBREV(cbuf));
+ tt.std_ofs = get_timezone_offset(tm);
+ tt.std_t = t;
+ std_found = true;
+ }
+ else if (tm->tm_isdst == 1 && !dst_found) {
+ /* Daylight time */
+ memcpy(&tt.dst_tm, tm, sizeof(struct tm));
+ memset(cbuf,0,sizeof(cbuf));
+ strftime(cbuf, sizeof(cbuf)-1, "%Z", tm); /* zone abbr */
+ strcpy(tt.dst_time, TZABBREV(cbuf));
+ tt.dst_ofs = get_timezone_offset(tm);
+ tt.dst_t = t;
+ dst_found = true;
+ }
+ if (std_found && dst_found)
+ break; /* Got both standard and daylight */
+ }
+
+ if (!std_found)
+ {
+ /* Failed to determine TZ! */
+ ereport(LOG,
+ (errmsg("unable to determine system timezone, defaulting to \"%s\"", "GMT"),
+ errhint("You can specify the correct timezone in postgresql.conf.")));
+ return NULL; /* go to GMT */
+ }
+
+ if (dst_found) {
+ /* Try STD<ofs>DST */
+ sprintf(__tzbuf,"%s%d%s",tt.std_time,-tt.std_ofs/3600,tt.dst_time);
+ if (try_timezone(__tzbuf, &tt, dst_found))
+ return __tzbuf;
+ }
+ /* Try just the STD timezone */
+ strcpy(__tzbuf,tt.std_time);
+ if (try_timezone(__tzbuf, &tt, dst_found))
+ return __tzbuf;
+
+ /* Did not find the timezone. Fallback to try a GMT zone. */
+ sprintf(__tzbuf,"Etc/GMT%s%d",
+ (-tt.std_ofs<0)?"+":"",tt.std_ofs/3600);
+ ereport(LOG,
+ (errmsg("could not recognize system timezone, defaulting to \"%s\"",
+ __tzbuf),
+ errhint("You can specify the correct timezone in postgresql.conf.")));
+ return __tzbuf;
+}
+
+/*
+ * Check whether timezone is acceptable.
+ *
+ * What we are doing here is checking for leap-second-aware timekeeping.
+ * We need to reject such TZ settings because they'll wreak havoc with our
+ * date/time arithmetic.
+ *
+ * NB: this must NOT ereport(ERROR). The caller must get control back so that
+ * it can restore the old value of TZ if we don't like the new one.
+ */
+bool
+tz_acceptable(void)
+{
+ struct pg_tm tt;
+ time_t time2000;
+
+ /*
+ * To detect leap-second timekeeping, compute the time_t value for
+ * local midnight, 2000-01-01. Insist that this be a multiple of 60;
+ * any partial-minute offset has to be due to leap seconds.
+ */
+ MemSet(&tt, 0, sizeof(tt));
+ tt.tm_year = 100;
+ tt.tm_mon = 0;
+ tt.tm_mday = 1;
+ tt.tm_isdst = -1;
+ time2000 = pg_mktime(&tt);
+ if ((time2000 % 60) != 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * Identify a suitable default timezone setting based on the environment,
+ * and make it active.
+ *
+ * We first look to the TZ environment variable. If not found or not
+ * recognized by our own code, we see if we can identify the timezone
+ * from the behavior of the system timezone library. When all else fails,
+ * fall back to GMT.
+ */
+const char *
+select_default_timezone(void)
+{
+ char *def_tz;
+
+ def_tz = getenv("TZ");
+ if (def_tz && pg_tzset(def_tz) && tz_acceptable())
+ return def_tz;
+
+ def_tz = identify_system_timezone();
+ if (def_tz && pg_tzset(def_tz) && tz_acceptable())
+ return def_tz;
+
+ if (pg_tzset("GMT") && tz_acceptable())
+ return "GMT";
+
+ ereport(FATAL,
+ (errmsg("could not select a suitable default timezone"),
+ errdetail("It appears that your GMT time zone uses leap seconds. PostgreSQL does not support leap seconds.")));
+ return NULL; /* keep compiler quiet */
+}
+
+/*
+ * Initialize timezone library
+ *
+ * This is called after initial loading of postgresql.conf. If no TimeZone
+ * setting was found therein, we try to derive one from the environment.
+ */
+void pg_timezone_initialize(void) {
+ /* Do we need to try to figure the timezone? */
+ if (strcmp(GetConfigOption("timezone"), "UNKNOWN") == 0) {
+ const char *def_tz;
+
+ /* Select setting */
+ def_tz = select_default_timezone();
+ /* Tell GUC about the value. Will redundantly call pg_tzset() */
+ SetConfigOption("timezone", def_tz, PGC_POSTMASTER, PGC_S_ENV_VAR);
+ }
+}