OSDN Git Service

Still another try at automatically detecting the best match in the zic
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 22 Jul 2004 05:28:30 +0000 (05:28 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 22 Jul 2004 05:28:30 +0000 (05:28 +0000)
timezone database for the system behavior we find ourselves in.  Scan
backwards from current time and choose the zone that matches furthest
back.  As per discussion a week or so back.

src/timezone/pgtz.c

index 19379d6..c3b8811 100644 (file)
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.18 2004/07/10 23:06:50 tgl Exp $
+ *       $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.19 2004/07/22 05:28:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -32,7 +32,7 @@
 #define T_WEEK  ((time_t) (60*60*24*7))
 #define T_MONTH ((time_t) (60*60*24*31))
 
-#define MAX_TEST_TIMES (52*35) /* 35 years, or 1970..2004 */
+#define MAX_TEST_TIMES (52*40) /* 40 years, or 1964..2004 */
 
 struct tztry
 {
@@ -43,8 +43,9 @@ struct tztry
 static char tzdir[MAXPGPATH];
 static int     done_tzdir = 0;
 
-static bool scan_available_timezones(char *tzdir, char *tzdirsub,
-                                                                        struct tztry *tt);
+static void scan_available_timezones(char *tzdir, char *tzdirsub,
+                                                                        struct tztry *tt,
+                                                                        int *bestscore, char *bestzonename);
 
 
 /*
@@ -144,10 +145,18 @@ compare_tm(struct tm *s, struct pg_tm *p)
 }
 
 /*
- * See if a specific timezone setting matches the system behavior
+ * See how well a specific timezone setting matches the system behavior
+ *
+ * We score a timezone setting according to the number of test times it
+ * matches.  (The test times are ordered later-to-earlier, but this routine
+ * doesn't actually know that; it just scans until the first non-match.)
+ *
+ * We return -1 for a completely unusable setting; this is worse than the
+ * score of zero for a setting that works but matches not even the first
+ * test time.
  */
-static bool
-try_timezone(const char *tzname, struct tztry *tt)
+static int
+score_timezone(const char *tzname, struct tztry *tt)
 {
        int                     i;
        pg_time_t       pgtt;
@@ -156,7 +165,14 @@ try_timezone(const char *tzname, struct tztry *tt)
        char            cbuf[TZ_STRLEN_MAX + 1];
 
        if (!pg_tzset(tzname))
-               return false;                   /* can't handle the TZ name at all */
+               return -1;                              /* can't handle the TZ name at all */
+
+       /* Reject if leap seconds involved */
+       if (!tz_acceptable())
+       {
+               elog(DEBUG4, "Reject TZ \"%s\": uses leap seconds", tzname);
+               return -1;
+       }
 
        /* Check for match at all the test times */
        for (i = 0; i < tt->n_test_times; i++)
@@ -164,51 +180,44 @@ try_timezone(const char *tzname, struct tztry *tt)
                pgtt = (pg_time_t) (tt->test_times[i]);
                pgtm = pg_localtime(&pgtt);
                if (!pgtm)
-                       return false;           /* probably shouldn't happen */
+                       return -1;              /* probably shouldn't happen */
                systm = localtime(&(tt->test_times[i]));
                if (!compare_tm(systm, pgtm))
                {
-                       elog(DEBUG4, "Reject TZ \"%s\": at %ld %04d-%02d-%02d %02d:%02d:%02d %s versus %04d-%02d-%02d %02d:%02d:%02d %s",
-                                tzname, (long) pgtt,
+                       elog(DEBUG4, "TZ \"%s\" scores %d: at %ld %04d-%02d-%02d %02d:%02d:%02d %s versus %04d-%02d-%02d %02d:%02d:%02d %s",
+                                tzname, i, (long) pgtt,
                                 pgtm->tm_year + 1900, pgtm->tm_mon + 1, pgtm->tm_mday,
                                 pgtm->tm_hour, pgtm->tm_min, pgtm->tm_sec,
                                 pgtm->tm_isdst ? "dst" : "std",
                                 systm->tm_year + 1900, systm->tm_mon + 1, systm->tm_mday,
                                 systm->tm_hour, systm->tm_min, systm->tm_sec,
                                 systm->tm_isdst ? "dst" : "std");
-                       return false;
+                       return i;
                }
                if (systm->tm_isdst >= 0)
                {
                        /* Check match of zone names, too */
                        if (pgtm->tm_zone == NULL)
-                               return false;
+                               return -1;              /* probably shouldn't happen */
                        memset(cbuf, 0, sizeof(cbuf));
                        strftime(cbuf, sizeof(cbuf) - 1, "%Z", systm); /* zone abbr */
                        if (strcmp(TZABBREV(cbuf), pgtm->tm_zone) != 0)
                        {
-                               elog(DEBUG4, "Reject TZ \"%s\": at %ld \"%s\" versus \"%s\"",
-                                        tzname, (long) pgtt,
+                               elog(DEBUG4, "TZ \"%s\" scores %d: at %ld \"%s\" versus \"%s\"",
+                                        tzname, i, (long) pgtt,
                                         pgtm->tm_zone, cbuf);
-                               return false;
+                               return i;
                        }
                }
        }
 
-       /* Reject if leap seconds involved */
-       if (!tz_acceptable())
-       {
-               elog(DEBUG4, "Reject TZ \"%s\": uses leap seconds", tzname);
-               return false;
-       }
-
-       elog(DEBUG4, "Accept TZ \"%s\"", tzname);
-       return true;
+       elog(DEBUG4, "TZ \"%s\" gets max score %d", tzname, i);
+       return i;
 }
 
 
 /*
- * Try to identify a timezone name (in our terminology) that matches the
+ * Try to identify a timezone name (in our terminology) that best 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 we ignore it and just look at what localtime() returns.
@@ -221,6 +230,7 @@ identify_system_timezone(void)
        time_t          t;
        struct tztry tt;
        struct tm  *tm;
+       int                     bestscore;
        char            tmptzdir[MAXPGPATH];
        int                     std_ofs;
        char            std_zone_name[TZ_STRLEN_MAX + 1],
@@ -231,36 +241,38 @@ identify_system_timezone(void)
        tzset();
 
        /*
-        * Set up the list of dates to be probed to verify that our timezone
-        * matches the system zone.  We first probe January and July of 1970;
+        * Set up the list of dates to be probed to see how well our timezone
+        * matches the system zone.  We first probe January and July of 2004;
         * this serves to quickly eliminate the vast majority of the TZ database
-        * entries.  If those dates match, we probe every week from 1970 to
-        * late 2004.  This exhaustive test is intended to ensure that we have
-        * the same DST transition rules as the system timezone.  (Note: we
-        * probe Thursdays, not Sundays, to avoid triggering DST-transition
-        * bugs in localtime itself.)
-        *
-        * Ideally we'd probe some dates before 1970 too, but that is guaranteed
-        * to fail if the system TZ library doesn't cope with DST before 1970.
+        * entries.  If those dates match, we probe every week from 2004 backwards
+        * to late 1964.  (Weekly resolution is good enough to identify DST
+        * transition rules, since everybody switches on Sundays.)  The further
+        * back the zone matches, the better we score it.  This may seem like
+        * a rather random way of doing things, but experience has shown that
+        * system-supplied timezone definitions are likely to have DST behavior
+        * that is right for the recent past and not so accurate further back.
+        * Scoring in this way allows us to recognize zones that have some
+        * commonality with the zic database, without insisting on exact match.
+        * (Note: we probe Thursdays, not Sundays, to avoid triggering
+        * DST-transition bugs in localtime itself.)
         */
        tt.n_test_times = 0;
-       tt.test_times[tt.n_test_times++] = t = build_time_t(1970, 1, 15);
-       tt.test_times[tt.n_test_times++] = build_time_t(1970, 7, 15);
+       tt.test_times[tt.n_test_times++] = build_time_t(2004, 1, 15);
+       tt.test_times[tt.n_test_times++] = t = build_time_t(2004, 7, 15);
        while (tt.n_test_times < MAX_TEST_TIMES)
        {
-               t += T_WEEK;
+               t -= T_WEEK;
                tt.test_times[tt.n_test_times++] = t;
        }
 
-       /* Search for matching timezone file */
+       /* Search for the best-matching timezone file */
        strcpy(tmptzdir, pg_TZDIR());
-       if (scan_available_timezones(tmptzdir,
-                                                                tmptzdir + strlen(tmptzdir) + 1,
-                                                                &tt))
-       {
-               StrNCpy(resultbuf, pg_get_current_timezone(), sizeof(resultbuf));
+       bestscore = 0;
+       scan_available_timezones(tmptzdir, tmptzdir + strlen(tmptzdir) + 1,
+                                                        &tt,
+                                                        &bestscore, resultbuf);
+       if (bestscore > 0)
                return resultbuf;
-       }
 
        /*
         * Couldn't find a match in the database, so next we try constructed zone
@@ -326,19 +338,19 @@ identify_system_timezone(void)
        {
                snprintf(resultbuf, sizeof(resultbuf), "%s%d%s",
                                 std_zone_name, -std_ofs / 3600, dst_zone_name);
-               if (try_timezone(resultbuf, &tt))
+               if (score_timezone(resultbuf, &tt) > 0)
                        return resultbuf;
        }
 
        /* Try just the STD timezone (works for GMT at least) */
        strcpy(resultbuf, std_zone_name);
-       if (try_timezone(resultbuf, &tt))
+       if (score_timezone(resultbuf, &tt) > 0)
                return resultbuf;
 
        /* Try STD<ofs> */
        snprintf(resultbuf, sizeof(resultbuf), "%s%d",
                         std_zone_name, -std_ofs / 3600);
-       if (try_timezone(resultbuf, &tt))
+       if (score_timezone(resultbuf, &tt) > 0)
                return resultbuf;
 
        /*
@@ -358,7 +370,7 @@ identify_system_timezone(void)
 }
 
 /*
- * Recursively scan the timezone database looking for a usable match to
+ * Recursively scan the timezone database looking for the best match to
  * the system timezone behavior.
  *
  * tzdir points to a buffer of size MAXPGPATH.  On entry, it holds the
@@ -372,14 +384,15 @@ identify_system_timezone(void)
  *
  * tt tells about the system timezone behavior we need to match.
  *
- * On success, returns TRUE leaving the proper timezone selected.
- * On failure, returns FALSE with a random timezone selected.
+ * *bestscore and *bestzonename on entry hold the best score found so far
+ * and the name of the best zone.  We overwrite them if we find a better
+ * score.  bestzonename must be a buffer of length TZ_STRLEN_MAX + 1.
  */
-static bool
-scan_available_timezones(char *tzdir, char *tzdirsub, struct tztry *tt)
+static void
+scan_available_timezones(char *tzdir, char *tzdirsub, struct tztry *tt,
+                                                int *bestscore, char *bestzonename)
 {
        int                     tzdir_orig_len = strlen(tzdir);
-       bool            found = false;
        DIR                *dirdesc;
 
        dirdesc = AllocateDir(tzdir);
@@ -388,7 +401,7 @@ scan_available_timezones(char *tzdir, char *tzdirsub, struct tztry *tt)
                ereport(LOG,
                                (errcode_for_file_access(),
                                 errmsg("could not open directory \"%s\": %m", tzdir)));
-               return false;
+               return;
        }
 
        for (;;)
@@ -432,16 +445,19 @@ scan_available_timezones(char *tzdir, char *tzdirsub, struct tztry *tt)
                if (S_ISDIR(statbuf.st_mode))
                {
                        /* Recurse into subdirectory */
-                       found = scan_available_timezones(tzdir, tzdirsub, tt);
-                       if (found)
-                               break;
+                       scan_available_timezones(tzdir, tzdirsub, tt,
+                                                                        bestscore, bestzonename);
                }
                else
                {
                        /* Load and test this file */
-                       found = try_timezone(tzdirsub, tt);
-                       if (found)
-                               break;
+                       int score = score_timezone(tzdirsub, tt);
+
+                       if (score > *bestscore)
+                       {
+                               *bestscore = score;
+                               StrNCpy(bestzonename, tzdirsub, TZ_STRLEN_MAX + 1);
+                       }
                }
        }
 
@@ -449,8 +465,6 @@ scan_available_timezones(char *tzdir, char *tzdirsub, struct tztry *tt)
 
        /* Restore tzdir */
        tzdir[tzdir_orig_len] = '\0';
-
-       return found;
 }