diff options
Diffstat (limited to 'libexslt/date.c')
-rw-r--r-- | libexslt/date.c | 432 |
1 files changed, 246 insertions, 186 deletions
diff --git a/libexslt/date.c b/libexslt/date.c index 60d353d5..bb4a8ec7 100644 --- a/libexslt/date.c +++ b/libexslt/date.c @@ -38,6 +38,7 @@ #include "exslt.h" +#include <stdlib.h> #include <string.h> #include <limits.h> #include <errno.h> @@ -52,6 +53,12 @@ #include <time.h> +#if defined(_MSC_VER) && _MSC_VER >= 1400 || \ + defined(_WIN32) && \ + defined(__MINGW64_VERSION_MAJOR) && __MINGW64_VERSION_MAJOR >= 4 + #define HAVE_MSVCRT +#endif + /* * types of date and/or time (from schema datatypes) * somewhat ordered from least specific to most specific (i.e. @@ -227,37 +234,41 @@ _exsltDateParseGYear (exsltDateValPtr dt, const xmlChar **str) } /** - * FORMAT_GYEAR: + * exsltFormatGYear: + * @cur: a pointer to a pointer to an allocated buffer + * @end: a pointer to the end of @cur buffer * @yr: the year to format - * @cur: a pointer to an allocated buffer * * Formats @yr in xsl:gYear format. Result is appended to @cur and * @cur is updated to point after the xsl:gYear. */ -#define FORMAT_GYEAR(yr, cur) \ - if (yr <= 0) { \ - *cur = '-'; \ - cur++; \ - } \ - { \ - long year = (yr <= 0) ? -yr + 1 : yr; \ - xmlChar tmp_buf[100], *tmp = tmp_buf; \ - /* result is in reverse-order */ \ - while (year > 0) { \ - *tmp = '0' + (xmlChar)(year % 10); \ - year /= 10; \ - tmp++; \ - } \ - /* virtually adds leading zeros */ \ - while ((tmp - tmp_buf) < 4) \ - *tmp++ = '0'; \ - /* restore the correct order */ \ - while (tmp > tmp_buf) { \ - tmp--; \ - *cur = *tmp; \ - cur++; \ - } \ - } +static void +exsltFormatGYear(xmlChar **cur, xmlChar *end, long yr) +{ + long year; + xmlChar tmp_buf[100], *tmp = tmp_buf, *tmp_end = tmp_buf + 99; + + if (yr <= 0 && *cur < end) { + *(*cur)++ = '-'; + } + + year = (yr <= 0) ? -yr + 1 : yr; + /* result is in reverse-order */ + while (year > 0 && tmp < tmp_end) { + *tmp++ = '0' + (xmlChar)(year % 10); + year /= 10; + } + + /* virtually adds leading zeros */ + while ((tmp - tmp_buf) < 4) + *tmp++ = '0'; + + /* restore the correct order */ + while (tmp > tmp_buf && *cur < end) { + tmp--; + *(*cur)++ = *tmp; + } +} /** * PARSE_2_DIGITS: @@ -286,18 +297,22 @@ _exsltDateParseGYear (exsltDateValPtr dt, const xmlChar **str) cur += 2; /** - * FORMAT_2_DIGITS: - * @num: the integer to format - * @cur: a pointer to an allocated buffer + * exsltFormat2Digits: + * @cur: a pointer to a pointer to an allocated buffer + * @end: a pointer to the end of @cur buffer + * @num: the integer to format * * Formats a 2-digits integer. Result is appended to @cur and * @cur is updated to point after the integer. */ -#define FORMAT_2_DIGITS(num, cur) \ - *cur = '0' + ((num / 10) % 10); \ - cur++; \ - *cur = '0' + (num % 10); \ - cur++; +static void +exsltFormat2Digits(xmlChar **cur, xmlChar *end, unsigned int num) +{ + if (*cur < end) + *(*cur)++ = '0' + ((num / 10) % 10); + if (*cur < end) + *(*cur)++ = '0' + (num % 10); +} /** * PARSE_FLOAT: @@ -326,29 +341,6 @@ _exsltDateParseGYear (exsltDateValPtr dt, const xmlChar **str) } /** - * FORMAT_FLOAT: - * @num: the double to format - * @cur: a pointer to an allocated buffer - * @pad: a flag for padding to 2 integer digits - * - * Formats a float. Result is appended to @cur and @cur is updated to - * point after the integer. If the @pad flag is non-zero, then the - * float representation has a minimum 2-digits integer part. The - * fractional part is formatted if @num has a fractional value. - */ -#define FORMAT_FLOAT(num, cur, pad) \ - { \ - xmlChar *sav, *str; \ - if ((pad) && (num < 10.0)) \ - *cur++ = '0'; \ - str = xmlXPathCastNumberToString(num); \ - sav = str; \ - while (*str != 0) \ - *cur++ = *str++; \ - xmlFree(sav); \ - } - -/** * _exsltDateParseGMonth: * @dt: pointer to a date structure * @str: pointer to the string to analyze @@ -380,17 +372,6 @@ _exsltDateParseGMonth (exsltDateValPtr dt, const xmlChar **str) } /** - * FORMAT_GMONTH: - * @mon: the month to format - * @cur: a pointer to an allocated buffer - * - * Formats @mon in xsl:gMonth format. Result is appended to @cur and - * @cur is updated to point after the xsl:gMonth. - */ -#define FORMAT_GMONTH(mon, cur) \ - FORMAT_2_DIGITS(mon, cur) - -/** * _exsltDateParseGDay: * @dt: pointer to a date structure * @str: pointer to the string to analyze @@ -422,32 +403,25 @@ _exsltDateParseGDay (exsltDateValPtr dt, const xmlChar **str) } /** - * FORMAT_GDAY: - * @dt: the #exsltDateVal to format - * @cur: a pointer to an allocated buffer - * - * Formats @dt in xsl:gDay format. Result is appended to @cur and - * @cur is updated to point after the xsl:gDay. - */ -#define FORMAT_GDAY(dt, cur) \ - FORMAT_2_DIGITS(dt->day, cur) - -/** - * FORMAT_DATE: + * exsltFormatYearMonthDay: + * @cur: a pointer to a pointer to an allocated buffer + * @end: a pointer to the end of @cur buffer * @dt: the #exsltDateVal to format - * @cur: a pointer to an allocated buffer * * Formats @dt in xsl:date format. Result is appended to @cur and * @cur is updated to point after the xsl:date. */ -#define FORMAT_DATE(dt, cur) \ - FORMAT_GYEAR(dt->year, cur); \ - *cur = '-'; \ - cur++; \ - FORMAT_GMONTH(dt->mon, cur); \ - *cur = '-'; \ - cur++; \ - FORMAT_GDAY(dt, cur); +static void +exsltFormatYearMonthDay(xmlChar **cur, xmlChar *end, const exsltDateValPtr dt) +{ + exsltFormatGYear(cur, end, dt->year); + if (*cur < end) + *(*cur)++ = '-'; + exsltFormat2Digits(cur, end, dt->mon); + if (*cur < end) + *(*cur)++ = '-'; + exsltFormat2Digits(cur, end, dt->day); +} /** * _exsltDateParseTime: @@ -506,23 +480,6 @@ _exsltDateParseTime (exsltDateValPtr dt, const xmlChar **str) } /** - * FORMAT_TIME: - * @dt: the #exsltDateVal to format - * @cur: a pointer to an allocated buffer - * - * Formats @dt in xsl:time format. Result is appended to @cur and - * @cur is updated to point after the xsl:time. - */ -#define FORMAT_TIME(dt, cur) \ - FORMAT_2_DIGITS(dt->hour, cur); \ - *cur = ':'; \ - cur++; \ - FORMAT_2_DIGITS(dt->min, cur); \ - *cur = ':'; \ - cur++; \ - FORMAT_FLOAT(dt->sec, cur, 1); - -/** * _exsltDateParseTimeZone: * @dt: pointer to a date structure * @str: pointer to the string to analyze @@ -600,27 +557,31 @@ _exsltDateParseTimeZone (exsltDateValPtr dt, const xmlChar **str) } /** - * FORMAT_TZ: - * @tzo: the timezone offset to format - * @cur: a pointer to an allocated buffer + * exsltFormatTimeZone: + * @cur: a pointer to a pointer to an allocated buffer + * @end: a pointer to the end of @cur buffer + * @tzo: the timezone offset to format * * Formats @tzo timezone. Result is appended to @cur and * @cur is updated to point after the timezone. */ -#define FORMAT_TZ(tzo, cur) \ - if (tzo == 0) { \ - *cur = 'Z'; \ - cur++; \ - } else { \ - int aTzo = (tzo < 0) ? - tzo : tzo; \ - int tzHh = aTzo / 60, tzMm = aTzo % 60; \ - *cur = (tzo < 0) ? '-' : '+' ; \ - cur++; \ - FORMAT_2_DIGITS(tzHh, cur); \ - *cur = ':'; \ - cur++; \ - FORMAT_2_DIGITS(tzMm, cur); \ - } +static void +exsltFormatTimeZone(xmlChar **cur, xmlChar *end, int tzo) +{ + if (tzo == 0) { + if (*cur < end) + *(*cur)++ = 'Z'; + } else { + unsigned int aTzo = (tzo < 0) ? -tzo : tzo; + unsigned int tzHh = aTzo / 60, tzMm = aTzo % 60; + if (*cur < end) + *(*cur)++ = (tzo < 0) ? '-' : '+'; + exsltFormat2Digits(cur, end, tzHh); + if (*cur < end) + *(*cur)++ = ':'; + exsltFormat2Digits(cur, end, tzMm); + } +} /**************************************************************** * * @@ -718,7 +679,7 @@ static exsltDateValPtr exsltDateCurrent (void) { struct tm localTm, gmTm; -#ifndef HAVE_GMTIME_R +#if !defined(HAVE_GMTIME_R) && !defined(HAVE_MSVCRT) struct tm *tb = NULL; #endif time_t secs; @@ -740,7 +701,11 @@ exsltDateCurrent (void) errno = 0; secs = (time_t) strtol (source_date_epoch, NULL, 10); if (errno == 0) { -#if HAVE_GMTIME_R +#ifdef HAVE_MSVCRT + struct tm *gm = gmtime_s(&localTm, &secs) ? NULL : &localTm; + if (gm != NULL) + override = 1; +#elif HAVE_GMTIME_R if (gmtime_r(&secs, &localTm) != NULL) override = 1; #else @@ -757,7 +722,9 @@ exsltDateCurrent (void) /* get current time */ secs = time(NULL); -#if HAVE_LOCALTIME_R +#ifdef HAVE_MSVCRT + localtime_s(&localTm, &secs); +#elif HAVE_LOCALTIME_R localtime_r(&secs, &localTm); #else localTm = *localtime(&secs); @@ -776,7 +743,9 @@ exsltDateCurrent (void) ret->sec = (double) localTm.tm_sec; /* determine the time zone offset from local to gm time */ -#if HAVE_GMTIME_R +#ifdef HAVE_MSVCRT + gmtime_s(&gmTm, &secs); +#elif HAVE_GMTIME_R gmtime_r(&secs, &gmTm); #else tb = gmtime(&secs); @@ -1138,21 +1107,41 @@ error: return NULL; } -/** - * FORMAT_ITEM: - * @num: number to format - * @cur: current location to convert number - * @limit: max value - * @item: char designator - * - */ -#define FORMAT_ITEM(num, cur, limit, item) \ - if (num >= limit) { \ - double comp = floor(num / limit); \ - FORMAT_FLOAT(comp, cur, 0); \ - *cur++ = item; \ - num -= comp * limit; \ +static void +exsltFormatLong(xmlChar **cur, xmlChar *end, long num) { + xmlChar buf[20]; + int i = 0; + + while (i < 20) { + buf[i++] = '0' + num % 10; + num /= 10; + if (num == 0) + break; + } + + while (i > 0) { + if (*cur < end) + *(*cur)++ = buf[--i]; + } +} + +static void +exsltFormatNanoseconds(xmlChar **cur, xmlChar *end, long nsecs) { + long p10, digit; + + if (nsecs > 0) { + if (*cur < end) + *(*cur)++ = '.'; + p10 = 100000000; + while (nsecs > 0) { + digit = nsecs / p10; + if (*cur < end) + *(*cur)++ = '0' + digit; + nsecs -= digit * p10; + p10 /= 10; } + } +} /** * exsltDateFormatDuration: @@ -1165,9 +1154,9 @@ error: static xmlChar * exsltDateFormatDuration (const exsltDateDurValPtr dur) { - xmlChar buf[100], *cur = buf; - double secs, days; - double years, months; + xmlChar buf[100], *cur = buf, *end = buf + 99; + double secs, tmp; + long days, months, intSecs, nsecs; if (dur == NULL) return NULL; @@ -1177,9 +1166,8 @@ exsltDateFormatDuration (const exsltDateDurValPtr dur) return xmlStrdup((xmlChar*)"P0D"); secs = dur->sec; - days = (double)dur->day; - years = (double)(dur->mon / 12); - months = (double)(dur->mon % 12); + days = dur->day; + months = dur->mon; *cur = '\0'; if (days < 0) { @@ -1190,10 +1178,6 @@ exsltDateFormatDuration (const exsltDateDurValPtr dur) days = -days; *cur = '-'; } - if (years < 0) { - years = -years; - *cur = '-'; - } if (months < 0) { months = -months; *cur = '-'; @@ -1203,23 +1187,64 @@ exsltDateFormatDuration (const exsltDateDurValPtr dur) *cur++ = 'P'; - if (years != 0.0) { - FORMAT_ITEM(years, cur, 1, 'Y'); + if (months >= 12) { + long years = months / 12; + + months -= years * 12; + exsltFormatLong(&cur, end, years); + if (cur < end) + *cur++ = 'Y'; } - if (months != 0.0) { - FORMAT_ITEM(months, cur, 1, 'M'); + if (months != 0) { + exsltFormatLong(&cur, end, months); + if (cur < end) + *cur++ = 'M'; } - FORMAT_ITEM(days, cur, 1, 'D'); - if (secs > 0.0) { - *cur++ = 'T'; + if (days != 0) { + exsltFormatLong(&cur, end, days); + if (cur < end) + *cur++ = 'D'; } - FORMAT_ITEM(secs, cur, SECS_PER_HOUR, 'H'); - FORMAT_ITEM(secs, cur, SECS_PER_MIN, 'M'); - if (secs > 0.0) { - FORMAT_FLOAT(secs, cur, 0); - *cur++ = 'S'; + + tmp = floor(secs); + intSecs = (long) tmp; + /* Round to nearest to avoid issues with floating point precision */ + nsecs = (long) floor((secs - tmp) * 1000000000 + 0.5); + if (nsecs >= 1000000000) { + nsecs -= 1000000000; + intSecs += 1; + } + + if ((intSecs > 0) || (nsecs > 0)) { + if (cur < end) + *cur++ = 'T'; + + if (intSecs >= SECS_PER_HOUR) { + long hours = intSecs / SECS_PER_HOUR; + + intSecs -= hours * SECS_PER_HOUR; + exsltFormatLong(&cur, end, hours); + if (cur < end) + *cur++ = 'H'; + } + + if (intSecs >= SECS_PER_MIN) { + long mins = intSecs / SECS_PER_MIN; + + intSecs -= mins * SECS_PER_MIN; + exsltFormatLong(&cur, end, mins); + if (cur < end) + *cur++ = 'M'; + } + + if ((intSecs > 0) || (nsecs > 0)) { + exsltFormatLong(&cur, end, intSecs); + exsltFormatNanoseconds(&cur, end, nsecs); + if (cur < end) + *cur++ = 'S'; + } } *cur = 0; @@ -1227,6 +1252,42 @@ exsltDateFormatDuration (const exsltDateDurValPtr dur) return xmlStrdup(buf); } +static void +exsltFormatTwoDigits(xmlChar **cur, xmlChar *end, int num) { + if (num < 0 || num >= 100) + return; + if (*cur < end) + *(*cur)++ = '0' + num / 10; + if (*cur < end) + *(*cur)++ = '0' + num % 10; +} + +static void +exsltFormatTime(xmlChar **cur, xmlChar *end, exsltDateValPtr dt) { + double tmp; + long intSecs, nsecs; + + exsltFormatTwoDigits(cur, end, dt->hour); + if (*cur < end) + *(*cur)++ = ':'; + + exsltFormatTwoDigits(cur, end, dt->min); + if (*cur < end) + *(*cur)++ = ':'; + + tmp = floor(dt->sec); + intSecs = (long) tmp; + /* + * Round to nearest to avoid issues with floating point precision, + * but don't carry over so seconds stay below 60. + */ + nsecs = (long) floor((dt->sec - tmp) * 1000000000 + 0.5); + if (nsecs > 999999999) + nsecs = 999999999; + exsltFormatTwoDigits(cur, end, intSecs); + exsltFormatNanoseconds(cur, end, nsecs); +} + /** * exsltDateFormatDateTime: * @dt: an #exsltDateValPtr @@ -1238,16 +1299,16 @@ exsltDateFormatDuration (const exsltDateDurValPtr dur) static xmlChar * exsltDateFormatDateTime (const exsltDateValPtr dt) { - xmlChar buf[100], *cur = buf; + xmlChar buf[100], *cur = buf, *end = buf + 99; if ((dt == NULL) || !VALID_DATETIME(dt)) return NULL; - FORMAT_DATE(dt, cur); - *cur = 'T'; - cur++; - FORMAT_TIME(dt, cur); - FORMAT_TZ(dt->tzo, cur); + exsltFormatYearMonthDay(&cur, end, dt); + if (cur < end) + *cur++ = 'T'; + exsltFormatTime(&cur, end, dt); + exsltFormatTimeZone(&cur, end, dt->tzo); *cur = 0; return xmlStrdup(buf); @@ -1264,14 +1325,14 @@ exsltDateFormatDateTime (const exsltDateValPtr dt) static xmlChar * exsltDateFormatDate (const exsltDateValPtr dt) { - xmlChar buf[100], *cur = buf; + xmlChar buf[100], *cur = buf, *end = buf + 99; if ((dt == NULL) || !VALID_DATETIME(dt)) return NULL; - FORMAT_DATE(dt, cur); + exsltFormatYearMonthDay(&cur, end, dt); if (dt->tz_flag || (dt->tzo != 0)) { - FORMAT_TZ(dt->tzo, cur); + exsltFormatTimeZone(&cur, end, dt->tzo); } *cur = 0; @@ -1289,14 +1350,14 @@ exsltDateFormatDate (const exsltDateValPtr dt) static xmlChar * exsltDateFormatTime (const exsltDateValPtr dt) { - xmlChar buf[100], *cur = buf; + xmlChar buf[100], *cur = buf, *end = buf + 99; if ((dt == NULL) || !VALID_TIME(dt)) return NULL; - FORMAT_TIME(dt, cur); + exsltFormatTime(&cur, end, dt); if (dt->tz_flag || (dt->tzo != 0)) { - FORMAT_TZ(dt->tzo, cur); + exsltFormatTimeZone(&cur, end, dt->tzo); } *cur = 0; @@ -1316,7 +1377,6 @@ exsltDateFormatTime (const exsltDateValPtr dt) static xmlChar * exsltDateFormat (const exsltDateValPtr dt) { - if (dt == NULL) return NULL; @@ -1332,17 +1392,17 @@ exsltDateFormat (const exsltDateValPtr dt) } if (dt->type & XS_GYEAR) { - xmlChar buf[100], *cur = buf; + xmlChar buf[100], *cur = buf, *end = buf + 99; - FORMAT_GYEAR(dt->year, cur); + exsltFormatGYear(&cur, end, dt->year); if (dt->type == XS_GYEARMONTH) { - *cur = '-'; - cur++; - FORMAT_GMONTH(dt->mon, cur); + if (cur < end) + *cur++ = '-'; + exsltFormat2Digits(&cur, end, dt->mon); } if (dt->tz_flag || (dt->tzo != 0)) { - FORMAT_TZ(dt->tzo, cur); + exsltFormatTimeZone(&cur, end, dt->tzo); } *cur = 0; return xmlStrdup(buf); @@ -1689,16 +1749,16 @@ _exsltDateAddDurCalc (exsltDateDurValPtr ret, exsltDateDurValPtr x, exsltDateDurValPtr y) { /* months */ - if ((x->mon > 0 && y->mon > LONG_MAX - x->mon) || - (x->mon < 0 && y->mon < LONG_MIN - x->mon)) { + if ((x->mon > 0 && y->mon > LONG_MAX - x->mon) || + (x->mon < 0 && y->mon <= LONG_MIN - x->mon)) { /* Overflow */ return 0; } ret->mon = x->mon + y->mon; /* days */ - if ((x->day > 0 && y->day > LONG_MAX - x->day) || - (x->day < 0 && y->day < LONG_MIN - x->day)) { + if ((x->day > 0 && y->day > LONG_MAX - x->day) || + (x->day < 0 && y->day <= LONG_MIN - x->day)) { /* Overflow */ return 0; } |