Clean up the code for to_timestamp's conversion of year plus ISO day number
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 15 Mar 2009 20:31:19 +0000 (20:31 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 15 Mar 2009 20:31:19 +0000 (20:31 +0000)
to date, as per bug #4702 and subsequent discussion.  In particular, make it
work for years specified using AD/BC or CC fields, and fix the test for "no
year specified" so that it doesn't trigger inappropriately for 1 BC (which it
was doing even in code paths that had nothing to do with to_timestamp).  I
also did some minor code beautification in the non-ISO-day-number code path.

This area has been busted all along, but because the code has been rewritten
repeatedly, it would be considerable trouble to back-patch.  It's such a
corner case that it doesn't seem worth the effort.

src/backend/utils/adt/formatting.c
src/backend/utils/adt/timestamp.c

index 44187a1a9f89696e0a35fe014b48cff5d88cf7e5..1747caa9ae0bd486ff1c07b2807ffcb79a34fadb 100644 (file)
@@ -709,13 +709,13 @@ typedef enum
  */
 static const KeyWord DCH_keywords[] = {
 /*     name, len, id, is_digit, date_mode */
-       {"A.D.", 4, DCH_A_D, FALSE, FROM_CHAR_DATE_GREGORIAN},  /* A */
+       {"A.D.", 4, DCH_A_D, FALSE, FROM_CHAR_DATE_NONE},               /* A */
        {"A.M.", 4, DCH_A_M, FALSE, FROM_CHAR_DATE_NONE},
-       {"AD", 2, DCH_AD, FALSE, FROM_CHAR_DATE_GREGORIAN},
+       {"AD", 2, DCH_AD, FALSE, FROM_CHAR_DATE_NONE},
        {"AM", 2, DCH_AM, FALSE, FROM_CHAR_DATE_NONE},
-       {"B.C.", 4, DCH_B_C, FALSE, FROM_CHAR_DATE_GREGORIAN},  /* B */
-       {"BC", 2, DCH_BC, FALSE, FROM_CHAR_DATE_GREGORIAN},
-       {"CC", 2, DCH_CC, TRUE, FROM_CHAR_DATE_GREGORIAN},              /* C */
+       {"B.C.", 4, DCH_B_C, FALSE, FROM_CHAR_DATE_NONE},               /* B */
+       {"BC", 2, DCH_BC, FALSE, FROM_CHAR_DATE_NONE},
+       {"CC", 2, DCH_CC, TRUE, FROM_CHAR_DATE_NONE},                   /* C */
        {"DAY", 3, DCH_DAY, FALSE, FROM_CHAR_DATE_NONE},                /* D */
        {"DDD", 3, DCH_DDD, TRUE, FROM_CHAR_DATE_GREGORIAN},
        {"DD", 2, DCH_DD, TRUE, FROM_CHAR_DATE_GREGORIAN},
@@ -757,13 +757,13 @@ static const KeyWord DCH_keywords[] = {
        {"YYY", 3, DCH_YYY, TRUE, FROM_CHAR_DATE_GREGORIAN},
        {"YY", 2, DCH_YY, TRUE, FROM_CHAR_DATE_GREGORIAN},
        {"Y", 1, DCH_Y, TRUE, FROM_CHAR_DATE_GREGORIAN},
-       {"a.d.", 4, DCH_a_d, FALSE, FROM_CHAR_DATE_GREGORIAN},  /* a */
+       {"a.d.", 4, DCH_a_d, FALSE, FROM_CHAR_DATE_NONE},               /* a */
        {"a.m.", 4, DCH_a_m, FALSE, FROM_CHAR_DATE_NONE},
-       {"ad", 2, DCH_ad, FALSE, FROM_CHAR_DATE_GREGORIAN},
+       {"ad", 2, DCH_ad, FALSE, FROM_CHAR_DATE_NONE},
        {"am", 2, DCH_am, FALSE, FROM_CHAR_DATE_NONE},
-       {"b.c.", 4, DCH_b_c, FALSE, FROM_CHAR_DATE_GREGORIAN},  /* b */
-       {"bc", 2, DCH_bc, FALSE, FROM_CHAR_DATE_GREGORIAN},
-       {"cc", 2, DCH_CC, TRUE, FROM_CHAR_DATE_GREGORIAN},              /* c */
+       {"b.c.", 4, DCH_b_c, FALSE, FROM_CHAR_DATE_NONE},               /* b */
+       {"bc", 2, DCH_bc, FALSE, FROM_CHAR_DATE_NONE},
+       {"cc", 2, DCH_CC, TRUE, FROM_CHAR_DATE_NONE},                   /* c */
        {"day", 3, DCH_day, FALSE, FROM_CHAR_DATE_NONE},                /* d */
        {"ddd", 3, DCH_DDD, TRUE, FROM_CHAR_DATE_GREGORIAN},
        {"dd", 2, DCH_DD, TRUE, FROM_CHAR_DATE_GREGORIAN},
@@ -3281,41 +3281,41 @@ do_to_timestamp(text *date_txt, text *fmt,
                 * be interpreted as a Gregorian day-of-year, or an ISO week date
                 * day-of-year.
                 */
+
+               if (!tm->tm_year && !tmfc.bc)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+                                        errmsg("cannot calculate day of year without year information")));
+
                if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
                {
                        int                     j0;             /* zeroth day of the ISO year, in Julian */
 
-                       j0 = isoweek2j(tmfc.year, 1) - 1;
+                       j0 = isoweek2j(tm->tm_year, 1) - 1;
 
                        j2date(j0 + tmfc.ddd, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
                }
                else
                {
-                       int                *y,
-                                               i;
-
-                       int                     ysum[2][13] = {
-                               {31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365, 0},
-                               {31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366, 0}};
+                       const int  *y;
+                       int                     i;
 
-                       if (!tm->tm_year)
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
-                                                errmsg("cannot calculate day of year without year information")));
+                       static const int ysum[2][13] = {
+                               {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
+                               {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}};
 
                        y = ysum[isleap(tm->tm_year)];
 
-                       for (i = 0; i <= 11; i++)
+                       for (i = 1; i <= 12; i++)
                        {
-                               if (tm->tm_yday < y[i])
+                               if (tmfc.ddd < y[i])
                                        break;
                        }
                        if (tm->tm_mon <= 1)
-                               tm->tm_mon = i + 1;
+                               tm->tm_mon = i;
 
                        if (tm->tm_mday <= 1)
-                               tm->tm_mday = i == 0 ? tm->tm_yday :
-                                       tm->tm_yday - y[i - 1];
+                               tm->tm_mday = tmfc.ddd - y[i - 1];
                }
        }
 
index a36617d0153dceede43ba3feb75b414cb591d1f9..21c7beea625af2aed34d79e41abc2cc42afac6ef 100644 (file)
@@ -3668,11 +3668,6 @@ isoweek2j(int year, int week)
        int                     day0,
                                day4;
 
-       if (!year)
-               ereport(ERROR,
-                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                  errmsg("cannot calculate week number without year information")));
-
        /* fourth day of current year */
        day4 = date2j(year, 1, 4);