[libxslt] Change internal representation of years



commit 624cb21fc267fe227ef96638af0d8944e2cc4fc9
Author: Nick Wellnhofer <wellnhofer aevum de>
Date:   Wed May 17 21:52:23 2017 +0200

    Change internal representation of years
    
    XML Schema Part 2 doesn't allow the year 0000 which seems to imply that
    year 0001 is preceded by -0001. The old code followed this convention but
    it represented the year -0001 as -1, requiring some adjustments when
    crossing the beginning of year 0001.
    
    Now the year -0001 is represented by 0 internally (astronomical year
    numbering). This simplifies some calculations.
    
    As a side effect, (XML Schema) years -0001, -0005, ... are now leap years.
    Previously, years -0004, -0008, ... were leap years. The new behavior
    seems more correct and better matches other implementations of the
    proleptic Gregorian calendar.
    
    Also fixes some bugs:
    
    - Previously, date:day-in-week() returned wrong values for dates before
      the year 3 BC. For example, it returned 6 (Friday) for both
      '-0004-12-31' and '-0003-01-01'. Now it returns 4 (Wednesday) for
      '-0004-12-31' and 5 (Thursday) for '-0003-01-01' (because of the leap
      year change).
    
    - date:add could return wrong results when crossing AD 1. For example,
      date:add('-0001-01-01', 'P2Y') would return '0001-01-01' instead of
      '0002-01-01'.
    
    - Likewise, date:difference produced wrong results when working on
      years or yearMonths.

 libexslt/date.c                   |   91 +++++++++++++++++++++----------------
 tests/exslt/date/add.1.out        |    2 +
 tests/exslt/date/add.1.xml        |    1 +
 tests/exslt/date/date.1.out       |   10 ++--
 tests/exslt/date/date.1.xml       |    2 +-
 tests/exslt/date/datetime.1.out   |   14 +++---
 tests/exslt/date/difference.1.out |    4 +-
 tests/exslt/date/difference.1.xml |    1 +
 tests/exslt/date/gyear.1.out      |    4 +-
 tests/exslt/date/gyear.1.xml      |    2 +-
 tests/exslt/date/gyearmonth.1.out |    4 +-
 tests/exslt/date/gyearmonth.1.xml |    2 +-
 tests/exslt/date/seconds.1.out    |    2 +-
 13 files changed, 78 insertions(+), 61 deletions(-)
---
diff --git a/libexslt/date.c b/libexslt/date.c
index 625dea6..81fddf9 100644
--- a/libexslt/date.c
+++ b/libexslt/date.c
@@ -139,7 +139,6 @@ struct _exsltDateVal {
        ((c == 0) || (c == 'Z') || (c == '+') || (c == '-'))
 
 #define VALID_ALWAYS(num)      (num >= 0)
-#define VALID_YEAR(yr)          (yr != 0)
 #define VALID_MONTH(mon)        ((mon >= 1) && (mon <= 12))
 /* VALID_DAY should only be used when month is unknown */
 #define VALID_DAY(day)          ((day >= 1) && (day <= 31))
@@ -164,7 +163,7 @@ static const unsigned long daysInMonthLeap[12] =
            (dt->day <= daysInMonth[dt->mon - 1]))
 
 #define VALID_DATE(dt)                                         \
-       (VALID_YEAR(dt->year) && VALID_MONTH(dt->mon) && VALID_MDAY(dt))
+       (VALID_MONTH(dt->mon) && VALID_MDAY(dt))
 
 /*
     hour and min structure vals are unsigned, so normal macros give
@@ -201,6 +200,10 @@ static const unsigned long dayInLeapYearByMonth[12] =
  * xs:gYear. It is supposed that @dt->year is big enough to contain
  * the year.
  *
+ * According to XML Schema Part 2, the year "0000" is an illegal year value
+ * which probably means that the year preceding AD 1 is BC 1. Internally,
+ * we allow a year 0 and adjust the value when parsing and formatting.
+ *
  * Returns 0 or the error code
  */
 static int
@@ -231,17 +234,18 @@ _exsltDateParseGYear (exsltDateValDatePtr dt, const xmlChar **str)
     if ((digcnt < 4) || ((digcnt > 4) && (*firstChar == '0')))
        return 1;
 
-    if (isneg)
-       dt->year = - dt->year;
-
-    if (!VALID_YEAR(dt->year))
+    if (dt->year == 0)
        return 2;
 
+    /* The internal representation of negative years is continuous. */
+    if (isneg)
+       dt->year = -dt->year + 1;
+
     *str = cur;
 
 #ifdef DEBUG_EXSLT_DATE
     xsltGenericDebug(xsltGenericDebugContext,
-                    "Parsed year %04i\n", dt->year);
+                    "Parsed year %04ld\n", dt->year);
 #endif
 
     return 0;
@@ -256,12 +260,12 @@ _exsltDateParseGYear (exsltDateValDatePtr dt, const xmlChar **str)
  * @cur is updated to point after the xsl:gYear.
  */
 #define FORMAT_GYEAR(yr, cur)                                  \
-       if (yr < 0) {                                           \
+       if (yr <= 0) {                                          \
            *cur = '-';                                         \
            cur++;                                              \
        }                                                       \
        {                                                       \
-           long year = (yr < 0) ? - yr : yr;                   \
+           long year = (yr <= 0) ? -yr + 1 : yr;               \
            xmlChar tmp_buf[100], *tmp = tmp_buf;               \
            /* result is in reverse-order */                    \
            while (year > 0) {                                  \
@@ -1348,7 +1352,7 @@ exsltDateFormat (const exsltDateValPtr dt)
  * Convert mon and year of @dt to total number of days. Take the
  * number of years since (or before) 1 AD and add the number of leap
  * years. This is a function  because negative
- * years must be handled a little differently and there is no zero year.
+ * years must be handled a little differently.
  *
  * Returns number of days.
  */
@@ -1357,11 +1361,11 @@ _exsltDateCastYMToDays (const exsltDateValPtr dt)
 {
     long ret;
 
-    if (dt->value.date.year < 0)
-        ret = (dt->value.date.year * 365) +
-              (((dt->value.date.year+1)/4)-((dt->value.date.year+1)/100)+
-               ((dt->value.date.year+1)/400)) +
-              DAY_IN_YEAR(0, dt->value.date.mon, dt->value.date.year);
+    if (dt->value.date.year <= 0)
+        ret = ((dt->value.date.year-1) * 365) +
+              (((dt->value.date.year)/4)-((dt->value.date.year)/100)+
+               ((dt->value.date.year)/400)) +
+              DAY_IN_YEAR(0, dt->value.date.mon, dt->value.date.year) - 1;
     else
         ret = ((dt->value.date.year-1) * 365) +
               (((dt->value.date.year-1)/4)-((dt->value.date.year-1)/100)+
@@ -1387,7 +1391,7 @@ _exsltDateCastYMToDays (const exsltDateValPtr dt)
  * exsltDateCastDateToNumber:
  * @dt:  an #exsltDateValPtr
  *
- * Calculates the number of seconds from year zero.
+ * Calculates the number of seconds from year 1 AD.
  *
  * Returns seconds from zero year.
  */
@@ -1461,7 +1465,7 @@ _exsltDateTruncateDate (exsltDateValPtr dt, exsltDateType type)
  * a Monday so all other days are calculated from there. Take the
  * number of years since (or before) add the number of leap years and
  * the day-in-year and mod by 7. This is a function  because negative
- * years must be handled a little differently and there is no zero year.
+ * years must be handled a little differently.
  *
  * Returns day in week (Sunday = 0).
  */
@@ -1470,8 +1474,8 @@ _exsltDateDayInWeek(long yday, long yr)
 {
     long ret;
 
-    if (yr < 0) {
-        ret = ((yr + (((yr+1)/4)-((yr+1)/100)+((yr+1)/400)) + yday) % 7);
+    if (yr <= 0) {
+        ret = ((yr-2 + ((yr/4)-(yr/100)+(yr/400)) + yday) % 7);
         if (ret < 0)
             ret += 7;
     } else
@@ -1524,12 +1528,6 @@ _exsltDateAdd (exsltDateValPtr dt, exsltDateValPtr dur)
 
     /* year (may be modified later) */
     r->year = d->year + carry;
-    if (r->year == 0) {
-        if (d->year > 0)
-            r->year--;
-        else
-            r->year++;
-    }
 
     /* time zone */
     r->tzo     = d->tzo;
@@ -1557,7 +1555,7 @@ _exsltDateAdd (exsltDateValPtr dt, exsltDateValPtr dur)
      * Note we use tempdays because the temporary values may need more
      * than 5 bits
      */
-    if ((VALID_YEAR(r->year)) && (VALID_MONTH(r->mon)) &&
+    if ((VALID_MONTH(r->mon)) &&
                   (d->day > MAX_DAYINMONTH(r->year, r->mon)))
         tempdays = MAX_DAYINMONTH(r->year, r->mon);
     else if (d->day < 1)
@@ -1592,12 +1590,6 @@ _exsltDateAdd (exsltDateValPtr dt, exsltDateValPtr dur)
         temp = r->mon + carry;
         r->mon = (unsigned int)MODULO_RANGE(temp, 1, 13);
         r->year = r->year + (long)FQUOTIENT_RANGE(temp, 1, 13);
-        if (r->year == 0) {
-            if (temp < 1)
-                r->year--;
-            else
-                r->year++;
-       }
     }
 
     r->day = tempdays;
@@ -1908,6 +1900,7 @@ static double
 exsltDateYear (const xmlChar *dateTime)
 {
     exsltDateValPtr dt;
+    long year;
     double ret;
 
     if (dateTime == NULL) {
@@ -1927,7 +1920,9 @@ exsltDateYear (const xmlChar *dateTime)
        }
     }
 
-    ret = (double) dt->value.date.year;
+    year = dt->value.date.year;
+    if (year <= 0) year -= 1; /* Adjust for missing year 0. */
+    ret = (double) year;
     exsltDateFreeDate(dt);
 
     return ret;
@@ -1956,16 +1951,32 @@ exsltDateYear (const xmlChar *dateTime)
 static xmlXPathObjectPtr
 exsltDateLeapYear (const xmlChar *dateTime)
 {
-    double year;
+    exsltDateValPtr dt = NULL;
+    xmlXPathObjectPtr ret;
 
-    year = exsltDateYear(dateTime);
-    if (xmlXPathIsNaN(year))
-       return xmlXPathNewFloat(xmlXPathNAN);
+    if (dateTime == NULL) {
+#ifdef WITH_TIME
+       dt = exsltDateCurrent();
+#endif
+    } else {
+       dt = exsltDateParse(dateTime);
+       if ((dt != NULL) &&
+            (dt->type != XS_DATETIME) && (dt->type != XS_DATE) &&
+           (dt->type != XS_GYEARMONTH) && (dt->type != XS_GYEAR)) {
+           exsltDateFreeDate(dt);
+           dt = NULL;
+       }
+    }
 
-    if (IS_LEAP((long)year))
-       return xmlXPathNewBoolean(1);
+    if (dt == NULL) {
+        ret = xmlXPathNewFloat(xmlXPathNAN);
+    }
+    else {
+        ret = xmlXPathNewBoolean(IS_LEAP(dt->value.date.year));
+        exsltDateFreeDate(dt);
+    }
 
-    return xmlXPathNewBoolean(0);
+    return ret;
 }
 
 /**
diff --git a/tests/exslt/date/add.1.out b/tests/exslt/date/add.1.out
index 47394ae..76c1865 100644
--- a/tests/exslt/date/add.1.out
+++ b/tests/exslt/date/add.1.out
@@ -21,6 +21,8 @@ add    : -0001 + -PT59S
 result : -0002-12-31T23:59:01Z
 add    : -0001 + P1Y
 result : 0001
+add    : -0001-01-01 + P2Y
+result : 0002-01-01
 add    : 2000-01 + -PT86400S
 result : 1999-12-31
 add    : 2000-01 + -P1D
diff --git a/tests/exslt/date/add.1.xml b/tests/exslt/date/add.1.xml
index 5555747..94cf1ad 100644
--- a/tests/exslt/date/add.1.xml
+++ b/tests/exslt/date/add.1.xml
@@ -11,6 +11,7 @@
   <date date='2000-01-01T00:00:00Z' dur='-PT59S'/>
   <date date='-0001'                dur='-PT59S'/>
   <date date='-0001'                dur='P1Y'/>
+  <date date='-0001-01-01'          dur='P2Y'/>
   <date date='2000-01'              dur='-PT86400S'/>
   <date date='2000-01'              dur='-P1D'/>
   <date date='1970-01-01T00:00:00-00:30' dur='-PT30S'/>
diff --git a/tests/exslt/date/date.1.out b/tests/exslt/date/date.1.out
index f22546c..8296de5 100644
--- a/tests/exslt/date/date.1.out
+++ b/tests/exslt/date/date.1.out
@@ -73,8 +73,8 @@
     minute-in-hour       : NaN
     second-in-minute     : NaN
   
-  Test Date : -0004-02-29
-    year                 : -4
+  Test Date : -0005-02-29
+    year                 : -5
     leap-year            : true
     month-in-year        : 2
     month-name           : February
@@ -83,9 +83,9 @@
     day-in-year          : 60
     day-in-month         : 29
     day-of-week-in-month : 5
-    day-in-week          : 1
-    day-name             : Sunday
-    day-abbreviation     : Sun
+    day-in-week          : 5
+    day-name             : Thursday
+    day-abbreviation     : Thu
     time                 : 
     hour-in-day          : NaN
     minute-in-hour       : NaN
diff --git a/tests/exslt/date/date.1.xml b/tests/exslt/date/date.1.xml
index 562e08e..7f3e9f5 100644
--- a/tests/exslt/date/date.1.xml
+++ b/tests/exslt/date/date.1.xml
@@ -5,7 +5,7 @@
   <date date="3000-01-31"/>
   <date date="2000-02-29"/>
   <date date="9990001-12-31Z"/>
-  <date date="-0004-02-29"/>
+  <date date="-0005-02-29"/>
   <date date="1999-01-02"/>
   <date date="1999-01-03"/>
   <date date="2004-01-01"/>
diff --git a/tests/exslt/date/datetime.1.out b/tests/exslt/date/datetime.1.out
index c0b4c53..273130d 100644
--- a/tests/exslt/date/datetime.1.out
+++ b/tests/exslt/date/datetime.1.out
@@ -39,12 +39,12 @@
   
   Test Date : -0001-12-31T23:59:59-05:00
     year                 : -1
-    leap-year            : false
+    leap-year            : true
     month-in-year        : 12
     month-name           : December
     month-abbreviation   : Dec
     week-in-year         : 52
-    day-in-year          : 365
+    day-in-year          : 366
     day-in-month         : 31
     day-of-week-in-month : 5
     day-in-week          : 1
@@ -75,17 +75,17 @@
   
   Test Date : -10000-12-31T23:59:59Z
     year                 : -10000
-    leap-year            : true
+    leap-year            : false
     month-in-year        : 12
     month-name           : December
     month-abbreviation   : Dec
     week-in-year         : 1
-    day-in-year          : 366
+    day-in-year          : 365
     day-in-month         : 31
     day-of-week-in-month : 5
-    day-in-week          : 4
-    day-name             : Wednesday
-    day-abbreviation     : Wed
+    day-in-week          : 2
+    day-name             : Monday
+    day-abbreviation     : Mon
     time                 : 23:59:59Z
     hour-in-day          : 23
     minute-in-hour       : 59
diff --git a/tests/exslt/date/difference.1.out b/tests/exslt/date/difference.1.out
index bcafa2b..aac2e14 100644
--- a/tests/exslt/date/difference.1.out
+++ b/tests/exslt/date/difference.1.out
@@ -14,7 +14,9 @@ result     : -P366D
 difference : 0002-05-05 - 0001-01
 result     : -P1Y4M
 difference : -0002-01-05 - 0001-01-04
-result     : P729D
+result     : P730D
+difference : 0002 - -0001
+result     : -P2Y
 difference : 1970-01-01T05:04:03 - 1970-01-01T04:03:02
 result     : -PT1H1M1S
 difference : 2000-01-01T05:00:03 - 2000-01-01T04:03:02
diff --git a/tests/exslt/date/difference.1.xml b/tests/exslt/date/difference.1.xml
index ca897c0..0e361f0 100644
--- a/tests/exslt/date/difference.1.xml
+++ b/tests/exslt/date/difference.1.xml
@@ -9,6 +9,7 @@
   <date date1='0002-01-05' date2='0001-01-04'/>
   <date date1='0002-05-05' date2='0001-01'/>
   <date date1='-0002-01-05' date2='0001-01-04'/>
+  <date date1='0002' date2='-0001'/>
   <date date1='1970-01-01T05:04:03' date2='1970-01-01T04:03:02'/>
   <date date1='2000-01-01T05:00:03' date2='2000-01-01T04:03:02'/>
   <date date1='2000-01-01T05:00:03' date2='1980-01-01T04:03:02'/>
diff --git a/tests/exslt/date/gyear.1.out b/tests/exslt/date/gyear.1.out
index 9a7b291..c5c517c 100644
--- a/tests/exslt/date/gyear.1.out
+++ b/tests/exslt/date/gyear.1.out
@@ -73,8 +73,8 @@
     minute-in-hour       : NaN
     second-in-minute     : NaN
   
-  Test Date : -0004
-    year                 : -4
+  Test Date : -0005
+    year                 : -5
     leap-year            : true
     month-in-year        : NaN
     month-name           : 
diff --git a/tests/exslt/date/gyear.1.xml b/tests/exslt/date/gyear.1.xml
index 95d776b..fb1e577 100644
--- a/tests/exslt/date/gyear.1.xml
+++ b/tests/exslt/date/gyear.1.xml
@@ -5,6 +5,6 @@
   <date date="3000"/>
   <date date="2000"/>
   <date date="9990001"/>
-  <date date="-0004"/>
+  <date date="-0005"/>
 </page>
 
diff --git a/tests/exslt/date/gyearmonth.1.out b/tests/exslt/date/gyearmonth.1.out
index 57580c7..6ffef03 100644
--- a/tests/exslt/date/gyearmonth.1.out
+++ b/tests/exslt/date/gyearmonth.1.out
@@ -73,8 +73,8 @@
     minute-in-hour       : NaN
     second-in-minute     : NaN
   
-  Test Date : -0004-02
-    year                 : -4
+  Test Date : -0005-02
+    year                 : -5
     leap-year            : true
     month-in-year        : 2
     month-name           : February
diff --git a/tests/exslt/date/gyearmonth.1.xml b/tests/exslt/date/gyearmonth.1.xml
index 08a7d29..3e3bdaf 100644
--- a/tests/exslt/date/gyearmonth.1.xml
+++ b/tests/exslt/date/gyearmonth.1.xml
@@ -5,6 +5,6 @@
   <date date="3000-01"/>
   <date date="2000-02"/>
   <date date="9990001-12"/>
-  <date date="-0004-02"/>
+  <date date="-0005-02"/>
 </page>
 
diff --git a/tests/exslt/date/seconds.1.out b/tests/exslt/date/seconds.1.out
index c0eb72c..5d5e5da 100644
--- a/tests/exslt/date/seconds.1.out
+++ b/tests/exslt/date/seconds.1.out
@@ -34,7 +34,7 @@ result  : 31536000
 seconds : 0001-01-01T00:00:00
 result  : -6.21355968e+10
 seconds : -0001-01-01T00:00:00
-result  : -6.21671328e+10
+result  : -6.21672192e+10
 seconds : 1970-01-01
 result  : 0
 seconds : 1970-01-01Z


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]