[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

[RESUBMIT] [PATCH] svn_parse_date rewrite (issue #408)

From: C. Scott Ananian <cananian_at_lesser-magoo.lcs.mit.edu>
Date: 2001-09-05 23:09:27 CEST

Reminder:
 To try out:
  a) apply patch
  b) remove subversion/libsvn_subr/getdate.c
  c) ./autogen.sh ; `sed -ne '7,7s/^#//p' config.status`
 To commit:
  a) svn remove subversion/libsvn_subr/getdate.*
  b) svn add subversion/libsvn_subr/svn_date.c
  c) svn add subversion/tests/libsvn_subr/date-test.c
  d) cvs add apr/time/unix/strtime.c
  e) mirror changes to www/project_tasks.html in CVS
  f) commit, oh boy!
  g) close issue #408

Enjoy!
 --s

2001-09-05 C. Scott Ananian <cananian@alumni.princeton.edu>

        * apr/include/apr_time.h
        * apr/time/unix/strtime.c (apr_strptime)
        Add the apr_strptime function, which mimics the X/OPEN standard
        strptime function. This is the "reverse" function of apr_strftime.
        If the platform doesn't have a working strptime(3), we use our
        own stripped-down implementation, which doesn't handle localization.
        This function is needed to implement flexible date parsers in
        a portable manner.
        * apr/configure.in
        Add check for correctly-functioning strptime(3) function on
        build platform. This function is including in various UNIX
        standards, but it's rather hard to find one that works completely
        correctly. We identify bugs in glibc versions up to and including
        2.2.4.
        * apr/time/unix/Makefile.in
        Add time/unix/strtime.c to the unix makefile.
        * apr/libapr.dsp
        Add time/unix/strtime.c to the "time" group in the win32 project.
        (not tested; I don't have a windows development machine.)
        * apr/test/testtime.c (main)
        Add tests for apr_strptime to the test suite.

        * apr/time/unix/timestr.c (apr_strftime)
        Added a comment about irregular handling of '%z' and '%Z' time
        format specifiers.
        
        * subversion/include/svn_time.h (svn_parse_date)
        Remove unnecessary 'struct getdate_time' declaration.
        Rewrite prototype for svn_parse_date.
        * subversion/libsvn_subr/svn_date.c (svn_parse_date)
        The svn_parse_date routine uses apr_strptime to very flexibly
        parse date/time strings. ISO 8601 format dates are accepted (and
        preferred); "natural language" date/time strings may also be
        specified. The code is fully localized; locale-specific date
        strings can be input if your system's strptime supports them;
        translations can also be added using gettext (or a similar
        mechanism) to support local versions of phrases just as "2 days
        ago" (any number of variant phrases may be added during
        translation, and we should handle noun/adjective declension issues
        well). Some of this support is currently '#if 0'ed out at the
        top of the file, in accordance with SVN's post-1.0 timeline for
        adding internationalization support.
        * subversion/clients/cmdline/main.c (main)
        The '-D' option now uses the new svn_parse_date function.
        Add comment questioning non-fatal error status on misparsing
        -D and --locale options.
        * autogen.sh
        Remove 'getdate.c' rule, which is now unnecessary.

        * subversion/tests/libsvn_subr/date-test.c (test_date_good,
        test_date_bad, main)
        Added test suite for svn_parse_date function.
        * build.conf
        Add rule to create date-test, which tests the svn_parse_date
        function.

        * www/project_tasks.html
        Removed the date parsing issues from the tasks list.

        
diff -ruHpN -x SVN svn.orig/trunk/apr/configure.in svn/trunk/apr/configure.in
--- svn.orig/trunk/apr/configure.in Wed Sep 5 14:50:52 2001
+++ svn/trunk/apr/configure.in Wed Sep 5 14:54:09 2001
@@ -617,6 +617,33 @@ AC_SUBST(have_memmove)
 
 APR_CHECK_SIGWAIT_ONE_ARG
 
+dnl glibc's version of strptime is historically buggy.
+dnl versions of glibc before 1997-05-21 contain a bug parsing %I
+dnl versions of glibc before 2001-08-10 (v2.2.4) contain a bug parsing %OI;
+dnl see cvs log for revision 1.34 of libc/time/strptime.c.
+dnl versions of glibc before 2001-09-05 contain a bug parsing %O prefixes,
+dnl due to a bug in how alternate digit sets are parsed.
+dnl Dynamic linking makes this configure-time test fallible, but better
+dnl something than nothing. Be careful when distributing dynamically
+dnl linked binaries; you may want to #undef HAVE_WORKING_STRPTIME
+dnl during your build (even though this gives up localization support).
+dnl - Assume cross-compiled builds don't have working strptime -
+AC_CHECK_FUNC(strptime, AC_TRY_RUN([#define _XOPEN_SOURCE
+ #include <time.h>
+ void exit(int status);
+ int main(int argc, char **argv) {
+ struct tm test; char *cp;
+ test.tm_hour=0; test.tm_min=0;
+ cp = strptime ("8", "%I", &test);
+ if (cp==NULL || test.tm_hour==7) exit(1); /* glibc < 2.0.4 */
+ cp = strptime ("8", "%OI", &test);
+ if (cp==NULL || test.tm_hour==7) exit(2); /* glibc <= 2.2.3 */
+ cp = strptime ("11:02", "%OH:%OM", &test);
+ if (cp==NULL || test.tm_min!=2) exit(3); /* glibc <= 2.2.4 */
+ exit(0);
+ }],AC_DEFINE(HAVE_WORKING_STRPTIME,1,[Define if you have a *working* strptime function.]),true,true))
+
+
 dnl #----------------------------- Checks for Any required Headers
 AC_HEADER_STDC
 
diff -ruHpN -x SVN svn.orig/trunk/apr/include/apr_time.h svn/trunk/apr/include/apr_time.h
--- svn.orig/trunk/apr/include/apr_time.h Wed Sep 5 14:50:52 2001
+++ svn/trunk/apr/include/apr_time.h Wed Sep 5 14:54:09 2001
@@ -228,6 +228,27 @@ APR_DECLARE(apr_status_t) apr_strftime(c
                                        apr_size_t max, const char *format,
                                        apr_exploded_time_t *tm);
 
+/**
+ * converts a string representation of time to an exploded time
+ * similar to X/OPEN standard strptime function. retptr gets
+ * NULL if apr_strptime fails to match all of the format string,
+ * or a pointer to the first character not processed in the format
+ * string otherwise (points to '\0' if the format string matches the
+ * complete input string). This function does not initialize exploded
+ * time but only stores the values specified by the format string,
+ * although implementations on some platforms will recompute fields
+ * such as tm_wday and tm_yday if any of the year, month, or day
+ * elements are changed.
+ * @param s string to parse
+ * @param retptr returns the end of parsed section of s
+ * @param format The format for the time string
+ * @param tm The parsed time
+ * @deffunc apr_status_t apr_strptime(const char *s, char **retptr, const char *format, apr_exploded_time_t *tm)
+ */
+APR_DECLARE(apr_status_t) apr_strptime(const char *s, char **retptr,
+ const char *format,
+ apr_exploded_time_t *tm);
+
 #ifdef __cplusplus
 }
 #endif
diff -ruHpN -x SVN svn.orig/trunk/apr/libapr.dsp svn/trunk/apr/libapr.dsp
--- svn.orig/trunk/apr/libapr.dsp Wed Sep 5 14:50:52 2001
+++ svn/trunk/apr/libapr.dsp Wed Sep 5 14:54:09 2001
@@ -370,6 +370,10 @@ SOURCE=.\threadproc\win32\threadpriv.c
 # PROP Default_Filter ""
 # Begin Source File
 
+SOURCE=.\time\unix\strtime.c
+# End Source File
+# Begin Source File
+
 SOURCE=.\time\win32\access.c
 # End Source File
 # Begin Source File
diff -ruHpN -x SVN svn.orig/trunk/apr/test/testtime.c svn/trunk/apr/test/testtime.c
--- svn.orig/trunk/apr/test/testtime.c Wed Sep 5 14:50:52 2001
+++ svn/trunk/apr/test/testtime.c Wed Sep 5 14:54:09 2001
@@ -58,6 +58,8 @@
 #include "apr_lib.h"
 #include <errno.h>
 #include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
 #include "test_apr.h"
 
 #define STR_SIZE 45
@@ -68,7 +70,7 @@ int main(void)
     apr_exploded_time_t xt, xt2;
     apr_time_t imp;
     apr_pool_t *p;
- char *str, *str2;
+ char *str, *str2, *cp;
     apr_size_t sz;
     apr_int32_t hr_off = -5 * 3600; /* 5 hours in seconds */
     apr_int64_t hr_off_64;
@@ -180,6 +182,199 @@ int main(void)
     printf("OK\n");
     printf(" ( %lld - %lld = %lld )\n", imp, now, imp - now);
  
+ /* A better test for apr_explode_time; we're more picky here
+ * on what exactly we accept. */
+ now = ((apr_time_t)998751752) * APR_USEC_PER_SEC;
+ STD_TEST_NEQ(" apr_explode_time (GMT -4 hours) for given time",
+ apr_explode_time(&xt, now, -4*3600))
+ if (/* 11:02:32.0 */
+ xt.tm_usec != 0 ||
+ xt.tm_sec != 32 ||
+ xt.tm_min != 2 ||
+ xt.tm_hour != 11 ||
+ /* 2001-08-25 */
+ xt.tm_mday != 25 ||
+ xt.tm_mon != 7 ||
+ xt.tm_year != 101 ||
+ /* saturday, 237th day of the year, no dst, GMT -0400 */
+ xt.tm_wday != 6 ||
+ xt.tm_yday != 236 ||
+ xt.tm_isdst != 0 ||
+ xt.tm_gmtoff != -14400) {
+ apr_strftime(str, &sz, STR_SIZE, "%T %A %d %B %Y", &xt);
+ printf("apr_explode_time returned wrong result:\n"
+ "\tshould have returned 11:02:32 Saturday 25 August 2001\n"
+ "\tactually returned %s\n",
+ str);
+ exit(-1);
+ }
+
+ /* Tests for strptime; we test strftime indirectly here too. */
+
+ /* -- test 24H day date month year format -- */
+ STD_TEST_NEQ(" apr_strftime (24H day date month year)",
+ apr_strftime(str, &sz, STR_SIZE, "%T %A %e %B %Y", &xt));
+ printf(" ( %s )\n", str);
+ TEST_NEQ(" Checking strftime result for correctness",
+ strcmp(str, "11:02:32 Saturday 25 August 2001"), 0,
+ "OK", "Failed");
+ /* ----------------------- */
+ xt2.tm_usec = xt2.tm_sec = xt2.tm_min = xt2.tm_hour =
+ xt2.tm_mday = xt2.tm_mon = xt2.tm_year = xt2.tm_wday = xt2.tm_yday = 1;
+ STD_TEST_NEQ(" apr_strptime (24H day date month year)",
+ apr_strptime(str, &cp, "%T %A %d %B %Y", &xt2));
+ if (cp==NULL) {
+ MSG_AND_EXIT("apr_strptime failed to parse string.\n");
+ }
+ if (*cp!='\0') {
+ MSG_AND_EXIT("apr_strptime failed to parse string completely.\n");
+ }
+ if (/* 11:02:32 */
+ xt2.tm_sec != 32 ||
+ xt2.tm_min != 2 ||
+ xt2.tm_hour != 11) {
+ MSG_AND_EXIT("apr_strptime parsed time incorrectly\n");
+ }
+ if (/* 2001-08-25 */
+ xt2.tm_mday != 25 ||
+ xt2.tm_mon != 7 ||
+ xt2.tm_year != 101) {
+ MSG_AND_EXIT("apr_strptime parsed date incorrectly\n");
+ }
+ if (/* saturday, 237th day of the year */
+ xt2.tm_wday != 6 ||
+ xt2.tm_yday != 236) {
+ MSG_AND_EXIT("apr_strptime failed to set the weekday and year-day "
+ "fields\n");
+ }
+ /* -- test 24H format with locale specifiers -- */
+ xt2.tm_usec = xt2.tm_sec = xt2.tm_min = xt2.tm_hour =
+ xt2.tm_mday = xt2.tm_mon = xt2.tm_year = xt2.tm_wday = xt2.tm_yday = 1;
+ STD_TEST_NEQ(" apr_strptime (locale specifiers)",
+ apr_strptime(str, &cp, "%OH:%OM:%OS %A %Od %B %EY", &xt2));
+ if (cp==NULL) {
+ MSG_AND_EXIT("apr_strptime failed to parse string.\n");
+ }
+ if (*cp!='\0') {
+ MSG_AND_EXIT("apr_strptime failed to parse string completely.\n");
+ }
+ if (/* 11:02:32 */
+ xt2.tm_sec != 32 ||
+ xt2.tm_min != 2 ||
+ xt2.tm_hour != 11) {
+ MSG_AND_EXIT("apr_strptime parsed time incorrectly\n");
+ }
+ if (/* 2001-08-25 */
+ xt2.tm_mday != 25 ||
+ xt2.tm_mon != 7 ||
+ xt2.tm_year != 101) {
+ MSG_AND_EXIT("apr_strptime parsed date incorrectly\n");
+ }
+ if (/* saturday, 237th day of the year */
+ xt2.tm_wday != 6 ||
+ xt2.tm_yday != 236) {
+ MSG_AND_EXIT("apr_strptime failed to set the weekday and year-day "
+ "fields\n");
+ }
+
+ /* -- test parsing of month/day-of-week abbreviations -- */
+ STD_TEST_NEQ(" apr_strftime (abbreviated month/day-of-week)",
+ apr_strftime(str, &sz, STR_SIZE, "%a %b", &xt));
+ printf(" ( %s )\n", str);
+ /* ----------------------- */
+ xt2.tm_usec = xt2.tm_sec = xt2.tm_min = xt2.tm_hour =
+ xt2.tm_mday = xt2.tm_mon = xt2.tm_year = xt2.tm_wday = xt2.tm_yday = 1;
+ STD_TEST_NEQ(" apr_strptime (abbreviated month/day-of-week)",
+ apr_strptime(str, &cp, "%a %b", &xt2));
+ if (cp==NULL) {
+ MSG_AND_EXIT("apr_strptime failed to parse string.\n");
+ }
+ if (*cp!='\0') {
+ MSG_AND_EXIT("apr_strptime failed to parse string completely.\n");
+ }
+ if (/* saturday in august */
+ xt2.tm_mon != 7 ||
+ xt2.tm_wday != 6) {
+ MSG_AND_EXIT("apr_strptime failed to parse string correctly.\n");
+ }
+
+ /* -- test 12-hour time and weekday number -- */
+ STD_TEST_NEQ(" apr_strftime (12-hour time, weekday number)",
+ apr_strftime(str, &sz, STR_SIZE, "%I %p %w", &xt));
+ printf(" ( %s )\n", str);
+ /* ----------------------- */
+ xt2.tm_usec = xt2.tm_sec = xt2.tm_min = xt2.tm_hour =
+ xt2.tm_mday = xt2.tm_mon = xt2.tm_year = xt2.tm_wday = xt2.tm_yday = 1;
+ STD_TEST_NEQ(" apr_strptime (12-hour time, weekday number)",
+ apr_strptime(str, &cp, "%I %p %w", &xt2));
+ if (cp==NULL) {
+ MSG_AND_EXIT("apr_strptime failed to parse string.\n");
+ }
+ if (*cp!='\0') {
+ MSG_AND_EXIT("apr_strptime failed to parse string completely.\n");
+ }
+ if (/* 11am, saturday */
+ xt2.tm_hour != 11 ||
+ xt2.tm_wday != 6) {
+ MSG_AND_EXIT("apr_strptime failed to parse string correctly.\n");
+ }
+
+ /* -- test 12-hour time, in the afternoon and the percent character */
+ xt.tm_hour = 15;
+ STD_TEST_NEQ(" apr_strftime (12-hour time, percent character)",
+ apr_strftime(str, &sz, STR_SIZE, "%% %l %p %%", &xt));
+ printf(" ( %s )\n", str);
+ /* ----------------------- */
+ xt2.tm_usec = xt2.tm_sec = xt2.tm_min = xt2.tm_hour =
+ xt2.tm_mday = xt2.tm_mon = xt2.tm_year = xt2.tm_wday = xt2.tm_yday = 1;
+ STD_TEST_NEQ(" apr_strptime (12-hour time, percent character)",
+ apr_strptime(str, &cp, "%% %I %p %%", &xt2));
+ if (cp==NULL) {
+ MSG_AND_EXIT("apr_strptime failed to parse string.\n");
+ }
+ if (*cp!='\0') {
+ MSG_AND_EXIT("apr_strptime failed to parse string completely.\n");
+ }
+ if (/* 3pm */
+ xt2.tm_hour != 15) {
+ MSG_AND_EXIT("apr_strptime failed to parse string correctly.\n");
+ }
+
+ /* -- test parsing of tricky squashed-together no-space formats -- */
+ xt.tm_hour = 15;
+ STD_TEST_NEQ(" apr_strftime (squashed)",
+ apr_strftime(str, &sz, STR_SIZE, "%Y%m%dT%H%M%S.0Z", &xt));
+ printf(" ( %s )\n", str);
+ /* ----------------------- */
+ xt2.tm_usec = xt2.tm_sec = xt2.tm_min = xt2.tm_hour =
+ xt2.tm_mday = xt2.tm_mon = xt2.tm_year = xt2.tm_wday = xt2.tm_yday = 1;
+ STD_TEST_NEQ(" apr_strptime (squashed)",
+ apr_strptime(str, &cp, "%Y%m%dT%H%M%S.0Z", &xt2));
+ if (cp==NULL) {
+ MSG_AND_EXIT("apr_strptime failed to parse string.\n");
+ }
+ if (*cp!='\0') {
+ MSG_AND_EXIT("apr_strptime failed to parse string completely.\n");
+ }
+ if (/* 11:02:32 */
+ xt2.tm_sec != 32 ||
+ xt2.tm_min != 2 ||
+ xt2.tm_hour != 15) {
+ MSG_AND_EXIT("apr_strptime parsed time incorrectly\n");
+ }
+ if (/* 2001-08-25 */
+ xt2.tm_mday != 25 ||
+ xt2.tm_mon != 7 ||
+ xt2.tm_year != 101) {
+ MSG_AND_EXIT("apr_strptime parsed date incorrectly\n");
+ }
+ if (/* saturday, 237th day of the year */
+ xt2.tm_wday != 6 ||
+ xt2.tm_yday != 236) {
+ MSG_AND_EXIT("apr_strptime failed to set the weekday and year-day "
+ "fields\n");
+ }
+
     printf("\nTest Complete.\n");
     return 0;
 }
diff -ruHpN -x SVN svn.orig/trunk/apr/time/unix/Makefile.in svn/trunk/apr/time/unix/Makefile.in
--- svn.orig/trunk/apr/time/unix/Makefile.in Wed Sep 5 14:50:52 2001
+++ svn/trunk/apr/time/unix/Makefile.in Wed Sep 5 14:54:09 2001
@@ -1,5 +1,5 @@
 
-TARGETS = time.lo timestr.lo
+TARGETS = time.lo timestr.lo strtime.lo
 
 # bring in rules.mk for standard functionality
 @INCLUDE_RULES@
diff -ruHpN -x SVN svn.orig/trunk/apr/time/unix/strtime.c svn/trunk/apr/time/unix/strtime.c
--- svn.orig/trunk/apr/time/unix/strtime.c Wed Dec 31 19:00:00 1969
+++ svn/trunk/apr/time/unix/strtime.c Wed Sep 5 14:54:09 2001
@@ -0,0 +1,454 @@
+/* ====================================================================
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 2000-2001 The Apache Software Foundation. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ * if any, must include the following acknowledgment:
+ * "This product includes software developed by the
+ * Apache Software Foundation (http://www.apache.org/)."
+ * Alternately, this acknowledgment may appear in the software itself,
+ * if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Apache" and "Apache Software Foundation" must
+ * not be used to endorse or promote products derived from this
+ * software without prior written permission. For written
+ * permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache",
+ * nor may "Apache" appear in their name, without prior written
+ * permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+#define APR_WANT_STRFUNC /* for strncmp, strlen, etc */
+#define APR_WANT_MEMFUNC /* for memset, etc */
+#define APR_WANT_STDIO /* for sscanf */
+#include <apr_want.h>
+
+#include "apr_portable.h"
+#include "apr_time.h"
+#include "apr_lib.h"
+#include "apr_private.h"
+
+/* an implementation using strptime(3) for platforms which have it */
+#if defined(HAVE_WORKING_STRPTIME) \
+ && (APR_HAVE_SYS_TIME_H || defined(HAVE_TIME_H))
+
+/* System Headers required for time library */
+#if APR_HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+#ifdef HAVE_TIME_H
+# include <time.h>
+#endif
+
+/* this works iff:
+ * a) system has (working) strptime function
+ * (glibc 2.2.3 and 2.2.4 are buggy)
+ * b) apr_os_exp_time is same as struct tm
+ * c) apr_os_exp_time_put ignores pool argument. */
+/* XXX: how to enforce the above? */
+apr_status_t apr_strptime(const char *s, char **retptr,
+ const char *format, apr_exploded_time_t *tm)
+{
+ struct tm os_tm, *os_tmp = &os_tm;
+ apr_status_t status;
+ status = apr_os_exp_time_get(&os_tmp, tm);
+ if (status != APR_SUCCESS) goto bail;
+ status = APR_EBADDATE;
+ (*retptr) = strptime(s, format, os_tmp);
+ if (NULL == (*retptr)) goto bail;
+ status = apr_os_exp_time_put(tm, &os_tmp, NULL/*craaazy*/);
+ bail:
+ return status;
+}
+
+#else /* use the non-strptime implementation. limited features, no l18n */
+
+/* table of 'first day of month' for normal and leap years */
+const int mon_yday[2][13] = {
+ /* Normal years. */
+ { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
+ /* Leap years. */
+ { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
+};
+
+/* date routines */
+static int is_leap_year(int year) {
+ return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
+}
+static void month_and_day(apr_exploded_time_t *tm) {
+ int mon = 0, isleap = is_leap_year(1900+tm->tm_year);
+ while (mon<12 && mon_yday[isleap][mon+1] < tm->tm_yday)
+ mon++;
+ tm->tm_mon = mon;
+ tm->tm_mday = (tm->tm_yday - mon_yday[isleap][mon]) + 1;
+}
+static void day_of_the_week(apr_exploded_time_t *tm) {
+ /* deep magic, borrowed from glibc strptime */
+ /* We know that January 1st 1970 was a Thursday (= 4). Compute the
+ the difference between this data in the one on TM and so determine
+ the weekday. */
+ int corr_year = 1900 + tm->tm_year - (tm->tm_mon < 2);
+ int wday = (-473
+ + (365 * (tm->tm_year - 70))
+ + (corr_year / 4)
+ - ((corr_year / 4) / 25) + ((corr_year / 4) % 25 < 0)
+ + (((corr_year / 4) / 25) / 4)
+ + mon_yday[0][tm->tm_mon]
+ + tm->tm_mday - 1);
+ tm->tm_wday = ((wday % 7) + 7) % 7;
+}
+static void day_of_the_year(apr_exploded_time_t *tm) {
+ tm->tm_yday = (mon_yday[is_leap_year (1900 + tm->tm_year)][tm->tm_mon]
+ + (tm->tm_mday - 1));
+}
+
+/* Lists of day/month/am/pm names, with associated integer values */
+typedef struct {
+ char *phrase; int val;
+} string_list_t;
+const static string_list_t weekday_list[] = {
+ { "Sunday", 0 }, { "Sun", 0 },
+ { "Monday", 1 }, { "Mon", 1 },
+ { "Tuesday", 2 }, { "Tue", 2 },
+ { "Wednesday", 3 }, { "Wed", 3 },
+ { "Thursday", 4 }, { "Thu", 4 },
+ { "Friday", 5 }, { "Fri", 5 },
+ { "Saturday", 6 }, { "Sat", 6 },
+ { NULL, 0 }
+};
+const static string_list_t month_list[] = {
+ { "January", 0 }, { "Jan", 0 },
+ { "February", 1 }, { "Feb", 1 },
+ { "March", 2 }, { "Mar", 2 },
+ { "April", 3 }, { "Apr", 3 },
+ { "May", 4 }, { "May", 4 },
+ { "June", 5 }, { "Jun", 5 },
+ { "July", 6 }, { "Jul", 6 },
+ { "August", 7 }, { "Aug", 7 },
+ { "September", 8 }, { "Sep", 8 },
+ { "October", 9 }, { "Oct", 9 },
+ { "November", 10 }, { "Nov", 10 },
+ { "December", 11 }, { "Dec", 11 },
+ { NULL, 0 }
+};
+const static string_list_t am_pm_list[] = {
+ { "AM", 0 }, { "A.M.", 0 },
+ { "PM", 1 }, { "P.M.", 1 },
+ { NULL, 0 }
+};
+
+/* Parse a string from the given string_list_t array. Puts the
+ * associated value in 'retval' and returns the first character
+ * not in the string if found in 'sl'; or else returns NULL if
+ * the string is not found in the list. */
+static const char *get_string(const char *s, int *retval,
+ const string_list_t *sl) {
+ int i;
+ for (i=0; sl[i].phrase!=NULL; i++)
+ if (strncasecmp(s, sl[i].phrase, strlen(sl[i].phrase))==0) {
+ *retval = sl[i].val;
+ return s+strlen(sl[i].phrase);
+ }
+ return NULL;
+}
+
+/* scan a number between 'min' and 'max' which is at most 'width'
+ digits long. returns a pointer to the first character after the
+ number if successful, or NULL if not. */
+static const char *get_number(const char *s, int *retval,
+ int min, int max, int width) {
+ const char *cp = s;
+ int val = 0;
+ /* must start with a number */
+ if (!(*cp >= '0' && *cp <= '9'))
+ return NULL;
+ /* first number */
+ val = (*cp++)-'0';
+ if (val>max) return NULL; /* can't even get 1 digit w/o being too large */
+ /* get digits until: none left, max width, or value is too large */
+ while (*cp >= '0' && *cp <= '9' && (cp-s) < width && val < max)
+ val = (val*10)+((*cp++)-'0');
+ /* we've gotten all the digits; let's make sure it's valid */
+ if (val>max) {
+ /* if value is too large, try to fix it up by 'ungetting' last digit */
+ val/=10; cp--;
+ }
+ /* if value is still out of range, return NULL */
+ if (val<min || val>max) return NULL;
+ /* okay, we've got a good number. */
+ *retval = val;
+ return cp;
+}
+
+/* internal state of the parser, preserved even if we invoke
+ * apr_strptime_internal recursively. We need this information in
+ * order to fix up certain fields at the end of the parse. */
+typedef struct {
+ int got_12hour, got_ampm, got_wday, got_mday, got_mon, got_yday, got_year;
+ int got_year_in_century, got_century;
+ int is_pm, year_in_century, century;
+} internal_state_t;
+
+static const char *apr_strptime_internal(const char *s, const char *format,
+ apr_exploded_time_t *tm,
+ internal_state_t *state) {
+ int i;
+ while (*format!='\0') {
+ /* spaces in the format string match 0 or more spaces in the input */
+ if (apr_isspace(*format)) {
+ while (apr_isspace(*s))
+ s++;
+ format++;
+ continue;
+ }
+ /* any character other than '%' must match itself. */
+ if (*format!='%') {
+ if (*s!=*format)
+ return NULL; /* no match */
+ format++; s++;
+ continue;
+ }
+ /* okay, we've got a '%' field descriptor. Advance past the '%' and
+ * switch on the field descriptor. */
+ format++;
+ if (*format=='E' || *format=='O')
+ format++; /* skip 'locale-dependent' field modifier */
+ switch(*format++) {
+ case '%':
+ if (*s++!='%') return NULL;
+ break;
+ /* recursive fields: these descriptors are just abbreviations of
+ * others. We invoke the parser recursively to handle them. */
+ case 'c':
+ if (NULL == (s = apr_strptime_internal(s, "%x %X", tm, state)))
+ return NULL;
+ break;
+ case 'D':
+ if (NULL == (s = apr_strptime_internal(s, "%m/%d/%y", tm, state)))
+ return NULL;
+ break;
+ case 'r':
+ if (NULL == (s = apr_strptime_internal(s, "%I:%M:%S %p",
+ tm, state)))
+ return NULL;
+ break;
+ case 'R':
+ if (NULL == (s = apr_strptime_internal(s, "%H:%M", tm, state)))
+ return NULL;
+ break;
+ case 'T':
+ if (NULL == (s = apr_strptime_internal(s, "%H:%M:%S", tm, state)))
+ return NULL;
+ break;
+ case 'x':
+ /* "locale's date format" is %D */
+ if (NULL == (s = apr_strptime_internal(s, "%D", tm, state)))
+ return NULL;
+ break;
+ case 'X':
+ /* "locale's time format" is %T */
+ if (NULL == (s = apr_strptime_internal(s, "%T", tm, state)))
+ return NULL;
+ break;
+ /* -------- */
+ /* Numeric field types. We use the 'get_number' routine to
+ * parse these from the input. get_number grabs the longest
+ * string from the input that satisfies its value constraints. */
+ case 'C': /* century */
+ if (NULL == (s = get_number(s, &i, 0, 99, 2)))
+ return NULL;
+ state->got_century = 1;
+ state->century = i;
+ break;
+ case 'd':
+ case 'e':
+ if (NULL == (s = get_number(s, &i, 1, 31, 2)))
+ return NULL;
+ tm->tm_mday = i;
+ state->got_mday = 1;
+ break;
+ case 'H':
+ case 'k':
+ if (NULL == (s = get_number(s, &i, 0, 23, 2)))
+ return NULL;
+ tm->tm_hour = i;
+ state->got_12hour = 0;
+ break;
+ case 'I':
+ case 'l':
+ if (NULL == (s = get_number(s, &i, 0, 12, 2)))
+ return NULL;
+ tm->tm_hour = i % 12;
+ state->got_12hour = 1;
+ break;
+ case 'j':
+ if (NULL == (s = get_number(s, &i, 1, 366, 3)))
+ return NULL;
+ tm->tm_yday = i - 1;
+ state->got_yday = 1;
+ break;
+ case 'm':
+ if (NULL == (s = get_number(s, &i, 1, 12, 2)))
+ return NULL;
+ tm->tm_mon = i - 1;
+ state->got_mon = 1;
+ break;
+ case 'M':
+ if (NULL == (s = get_number(s, &i, 0, 59, 2)))
+ return NULL;
+ tm->tm_min = i;
+ break;
+ case 'S':
+ /* despite what the Single Unix specification says, no minute
+ * has more than one leap second. Thus the range is 0-60
+ * (not 0-61, as the spec says). glibc backs me up on this. */
+ if (NULL == (s = get_number(s, &i, 0, 60, 2)))
+ return NULL;
+ tm->tm_sec = i;
+ break;
+ /* No implementation for %U and %W, which are week numbers using
+ * Sunday as the first day of the week and Monday as the first
+ * day of the week, respectively. They're not terribly useful,
+ * don't correspond to fields in apr_exploded_time_t, and aren't
+ * even useful for parsing the ISO 8601 format for dates, because
+ * they're defined wrong. So we don't support them. */
+ case 'w':
+ if (NULL == (s = get_number(s, &i, 0, 6, 1)))
+ return NULL;
+ tm->tm_wday = i;
+ state->got_wday = 1;
+ break;
+ case 'y': /* 'year within century'. when century not otherwise
+ * specified, refers to 1969-2068 */
+ if (NULL == (s = get_number(s, &i, 0, 99, 2)))
+ return NULL;
+ state->year_in_century = i;
+ state->got_year_in_century = 1;
+ break;
+ case 'Y': /* year including century */
+ /* not y10k aware */
+ if (NULL == (s = get_number(s, &i, 0, 9999, 4)))
+ return NULL;
+ tm->tm_year = i-1900;
+ state->got_year_in_century = state->got_century = 0;
+ state->got_year = 1;
+ break;
+ /* -------- */
+ /* String field types. We use 'get_string' with our lists
+ * of possible input strings. This would be much more
+ * complicated if we were trying to make this routine
+ * localizable, but we're not. Use your system-defined
+ * strptime(3) routine if you want localizability. */
+ case 'A':
+ case 'a':
+ if (NULL == (s = get_string(s, &i, weekday_list)))
+ return NULL;
+ tm->tm_wday = i;
+ state->got_wday = 1;
+ break;
+ case 'B':
+ case 'b':
+ case 'h':
+ if (NULL == (s = get_string(s, &i, month_list)))
+ return NULL;
+ tm->tm_mon = i;
+ state->got_mon = 1;
+ break;
+ case 'p':
+ if (NULL == (s = get_string(s, &i, am_pm_list)))
+ return NULL;
+ state->is_pm = i;
+ state->got_ampm = 1;
+ break;
+ /* -------- */
+ /* Invalid field specified. Return 'no match'. */
+ default:
+ return NULL;
+ }
+ }
+ /* done parsing the format string! */
+ return s;
+}
+
+apr_status_t apr_strptime(const char *s, char **retptr,
+ const char *format, apr_exploded_time_t *tm)
+{
+ const char *cp;
+ internal_state_t state;
+ memset(&state, 0, sizeof(state));
+
+ cp = apr_strptime_internal(s, format, tm, &state);
+ if (cp==NULL)
+ return APR_EBADDATE;
+ /* fixup year if year-in-century/century was given */
+ if (state.got_year_in_century) {
+ if (state.got_century)
+ tm->tm_year = (state.century*100 + state.year_in_century) - 1900;
+ else {/* without century designation, years 00-68 refer to 2000-2068 */
+ tm->tm_year = state.year_in_century;
+ if (tm->tm_year < 69) tm->tm_year+=100;
+ }
+ state.got_year = 1;
+ }
+ /* fix-up 12hour when am/pm is known */
+ if (state.got_12hour && state.got_ampm) {
+ if (state.is_pm) tm->tm_hour+=12;
+ }
+ /* fixup month and mday if yday and year are known */
+ if (state.got_yday && state.got_year &&
+ !(state.got_mon && state.got_mday)) {
+ month_and_day(tm);
+ state.got_mon = state.got_mday = 1;
+ }
+ /* fixup wday if enough info is present */
+ if (state.got_mon && state.got_mday && state.got_year &&
+ !state.got_wday) {
+ day_of_the_week(tm);
+ state.got_wday = 1;
+ }
+ /* fixup yday if enough info is present */
+ if (state.got_mon && state.got_mday && state.got_year &&
+ !state.got_yday) {
+ day_of_the_year(tm);
+ state.got_yday = 1;
+ }
+ /* done */
+ *retptr = (char*) cp; /* cast necessary due to hole in type system */
+ return APR_SUCCESS;
+}
+
+#endif /* !HAVE_STRPTIME */
diff -ruHpN -x SVN svn.orig/trunk/apr/time/unix/timestr.c svn/trunk/apr/time/unix/timestr.c
--- svn.orig/trunk/apr/time/unix/timestr.c Wed Sep 5 14:50:52 2001
+++ svn/trunk/apr/time/unix/timestr.c Wed Sep 5 14:54:09 2001
@@ -167,6 +167,10 @@ apr_status_t apr_ctime(char *date_str, a
     return APR_SUCCESS;
 }
 
+/* note the this strftime function doesn't handle %z or %Z in the
+ * correct way if HAVE_GMTOFF or HAVE___OFFSET is not defined, as
+ * timezone information should be taken from the apr_exploded_time_t,
+ * not the environment. */
 apr_status_t apr_strftime(char *s, apr_size_t *retsize, apr_size_t max,
                         const char *format, apr_exploded_time_t *xt)
 {
diff -ruHpN -x SVN svn.orig/trunk/autogen.sh svn/trunk/autogen.sh
--- svn.orig/trunk/autogen.sh Wed Sep 5 14:50:51 2001
+++ svn/trunk/autogen.sh Wed Sep 5 14:54:09 2001
@@ -102,23 +102,6 @@ cp $ltfile ac-helpers/libtool.m4
 # any old aclocal.m4 left over from prior build so it doesn't cause errors.
 rm -f aclocal.m4
 
-# Produce getdate.c from getdate.y.
-# Again, this means that "developers" who run autogen.sh need either
-# yacc or bison -- but not people who compile sourceballs, since `make
-# dist` will include getdate.c.
-echo "Creating getdate.c..."
-bison -o subversion/libsvn_subr/getdate.c subversion/libsvn_subr/getdate.y
-if [ $? -ne 0 ]; then
- yacc -o subversion/libsvn_subr/getdate.c subversion/libsvn_subr/getdate.y
- if [ $? -ne 0 ]; then
- echo
- echo " Error: can't find either bison or yacc."
- echo " One of these is needed to generate the date parser."
- echo
- exit 1
- fi
-fi
-
 # Create the file detailing all of the build outputs for SVN.
 #
 # Note: this dependency on Python is fine: only SVN developers use autogen.sh
diff -ruHpN -x SVN svn.orig/trunk/build.conf svn/trunk/build.conf
--- svn.orig/trunk/build.conf Wed Sep 5 14:50:51 2001
+++ svn/trunk/build.conf Wed Sep 5 14:54:09 2001
@@ -250,6 +250,15 @@ sources = path-test.c
 install = test
 libs = libsvn_test libsvn_delta libsvn_subr $(SVN_APR_LIBS) libexpat
 
+# test svn_parse_date function.
+[date-test]
+type = exe
+path = subversion/tests/libsvn_subr
+sources = date-test.c
+install = test
+group = programs
+libs = libsvn_test libsvn_delta libsvn_subr $(SVN_APR_LIBS) libexpat
+
 
 ### Tests that are simply broken (fix?) ----------
 
diff -ruHpN -x SVN svn.orig/trunk/subversion/clients/cmdline/main.c svn/trunk/subversion/clients/cmdline/main.c
--- svn.orig/trunk/subversion/clients/cmdline/main.c Wed Sep 5 14:50:51 2001
+++ svn/trunk/subversion/clients/cmdline/main.c Wed Sep 5 14:54:09 2001
@@ -249,14 +249,29 @@ main (int argc, const char * const *argv
         opt_state.revision = (svn_revnum_t) atoi (opt_arg);
         break;
       case 'D':
- /* svn_parse_date() originates in getdate.y; while I'd love to
- change it to const char *, that turns out to be a little
- more complex than just adding the qualifier. So for now,
- I'm casting to get rid of the compilation warning, and have
- filed issue #408 so we don't forget about this. -kff */
- apr_ansi_time_to_apr_time (&opt_state.date,
- svn_parse_date ((char *) opt_arg, NULL));
- break;
+ {
+ apr_time_t now, then;
+ /* XXX: If we evaluate -D multiple times in the course of
+ * an operation, we probably want to use the same 'now'
+ * value every time, instead of always using the current time. */
+ now = apr_time_now();
+ apr_err = svn_parse_date(opt_arg, now, &then);
+ if (APR_STATUS_IS_SUCCESS (apr_err))
+ {
+ opt_state.date = then;
+ }
+ else
+ {
+ err = svn_error_createf (SVN_ERR_CL_ARG_PARSING_ERROR,
+ 0, NULL, pool,
+ "Invalid date specification `%s'",
+ opt_arg);
+ svn_handle_error (err, stderr, FALSE);
+ /* XXX: this should be fatal? Otherwise we may
+ * commit/checkout wrong versions? */
+ }
+ break;
+ }
       case 'v':
         opt_state.version = TRUE;
       case 'h':
@@ -314,6 +329,10 @@ main (int argc, const char * const *argv
                                      "The locale `%s' can not be set",
                                      opt_arg);
             svn_handle_error (err, stderr, FALSE);
+ /* XXX: this should be fatal? Otherwise we may
+ * commit/checkout wrong versions? (because we specified
+ * date strings etc according to a locale which wasn't actually
+ * set? */
           }
         break;
       case 'x':
diff -ruHpN -x SVN svn.orig/trunk/subversion/include/svn_time.h svn/trunk/subversion/include/svn_time.h
--- svn.orig/trunk/subversion/include/svn_time.h Wed Sep 5 14:50:51 2001
+++ svn/trunk/subversion/include/svn_time.h Wed Sep 5 14:54:09 2001
@@ -42,16 +42,15 @@ svn_stringbuf_t *svn_time_to_string (apr
 apr_time_t svn_time_from_string (svn_stringbuf_t *timestr);
 
 
-/* Needed by getdate.y parser */
-struct getdate_time {
- time_t time;
- short timezone;
-};
-
-/* The one interface in our getdate.y parser; convert human-readable
- date TEXT into a standard C time_t. The 2nd argument is unused;
- we always pass NULL. */
-time_t svn_parse_date (char *text, struct getdate_time *now);
+/* The one public interface of the date parser: convert human-readable
+ date TEXT into a standard C time_t. Note that 'now' is passed as
+ a parameter so that you can use this routine to find out how SVN
+ *would have* parsed some string at some *arbitrary* time: relative
+ times should always parse the same even if svn_parse_date is called
+ multiple times during a computation of finite length. For this reason,
+ the 'now' parameter is *mandatory*. Returns 0 on success. */
+apr_status_t svn_parse_date (const char *text, const apr_time_t now,
+ apr_time_t * result);
 
 #endif /* SVN_TIME_H */
 
diff -ruHpN -x SVN svn.orig/trunk/subversion/libsvn_subr/svn_date.c svn/trunk/subversion/libsvn_subr/svn_date.c
--- svn.orig/trunk/subversion/libsvn_subr/svn_date.c Wed Dec 31 19:00:00 1969
+++ svn/trunk/subversion/libsvn_subr/svn_date.c Wed Sep 5 14:54:09 2001
@@ -0,0 +1,956 @@
+/*
+ * svn_date.c: (natural language) date parsing routine.
+ * accepts most of the formats accepted by
+ * http://www.eyrx.com/DOC/D_GNUTIL/GSU_3.HTM
+ *
+ * ====================================================================
+ * Copyright (c) 2000-2001 CollabNet. All rights reserved.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://subversion.tigris.org/license-1.html.
+ * If newer versions of this license are posted there, you may use a
+ * newer version instead, at your option.
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals. For exact contribution history, see the revision
+ * history and logs, available at http://subversion.tigris.org/.
+ * ====================================================================
+ */
+
+
+#include <assert.h>
+#include <stdlib.h> /* for strtol */
+#define APR_WANT_STRFUNC /* for strncmp, strlen, etc */
+#define APR_WANT_MEMFUNC /* for memset, etc */
+#define APR_WANT_STDIO /* for sscanf */
+#include <apr_want.h> /* pulls in all the above */
+#include <apr_lib.h> /* apr_isdigit, apr_isspace */
+#include <apr_time.h> /* for apr_exploded_time_t & friends */
+#include <apr_errno.h> /* for apr_status_t, APR_SUCCESS, etc */
+#include "svn_time.h" /* for svn_parse_date prototype */
+
+/* SVN won't support internationalization until after v1.0, but I've
+ * already internationalized these routines using the gettext interface.
+ * It should be straight-forward to convert to catgets or what-have-you
+ * post v1.0; just search for 'gettext' calls. */
+#if 0 /* not yet */
+# include <libintl.h> /* for gettext */
+#else /* in the meantime */
+# define gettext(x) x
+#endif /* 0 */
+
+
+/*** Prototypes. */
+/* parsers for possible date formats */
+static apr_status_t
+ISO8601ext_parse (const char *text, const apr_time_t now, apr_time_t *resultp);
+static apr_status_t
+ISO8601bas_parse (const char *text, const apr_time_t now, apr_time_t *resultp);
+static apr_status_t
+locale_parse (const char *text, const apr_time_t now, apr_time_t *resultp);
+
+/* helper functions for the parsers */
+static apr_status_t
+ISO8601ext_parse_TZD (const char *text, apr_exploded_time_t *tm);
+static apr_status_t
+ISO8601bas_parse_TZD (const char *text, apr_exploded_time_t *tm);
+static apr_status_t
+locale_parse_time (const char *text, char **cp, apr_exploded_time_t *tm);
+static apr_status_t
+locale_parse_date (const char *text, char **cp, apr_exploded_time_t *tm);
+static apr_status_t
+locale_parse_day (const char *text, char **cp, apr_exploded_time_t *tm);
+static apr_status_t
+locale_parse_zone (const char *text, char **cp, apr_exploded_time_t *tm);
+static apr_status_t
+locale_parse_relative (const char *text, char **cp, apr_exploded_time_t *tm);
+
+/* an enumerated type to select between local time and GMT */
+typedef enum { SVN_LOCAL_TIME, SVN_GMT_TIME } timetype_t;
+
+
+/* The one public interface of the date parser: convert human-readable
+ date TEXT into an apr_time_t. Note that 'now' is passed as
+ a parameter so that you can use this routine to find out how SVN
+ *would have* parsed some string at some *arbitrary* time: relative
+ times should always parse the same even if svn_parse_date is called
+ multiple times during a computation of finite length. For this reason,
+ the 'now' parameter is *mandatory*. Returns APR_SUCCESS on success. */
+apr_status_t
+svn_parse_date (const char *text, const apr_time_t now, apr_time_t * result)
+{
+ apr_status_t status;
+ /* this is a succession of candidate parsers. the first one that
+ * produces a valid parse of the *complete* string wins. */
+ status = ISO8601ext_parse (text, now, result);
+ if (status==APR_SUCCESS) goto done;
+ status = ISO8601bas_parse (text, now, result);
+ if (status==APR_SUCCESS) goto done;
+ status = locale_parse (text, now, result);
+ if (status==APR_SUCCESS) goto done;
+ /* error: invalid date */
+ status = APR_EBADDATE;
+ done:
+ return status;
+}
+
+
+/* Initialize 'apr_exploded_time_t' to all zeros (except for tm_mday field,
+ * for which smallest valid value is 1). */
+static void
+init_tm (apr_exploded_time_t *tm)
+{
+ memset (tm, 0, sizeof (*tm));
+ tm->tm_mday = 1; /* tm is not valid with all zeros. */
+}
+
+/* Compute the number of days since 1jan1970; the 'year' parameter is
+ * "years since 1900". Month must be in bounds! */
+static apr_status_t
+days_since_1jan1970(int *days_ptr, int year, int month, int mday) {
+ static const int dayoffset[12] =
+ {306, 337, 0, 31, 61, 92, 122, 153, 184, 214, 245, 275};
+ int days;
+
+ if (year < 70 || month < 0 || month > 11)
+ return APR_EBADDATE;
+ /* (borrowed from apr/time/time.c apr_implode_time() ) */
+
+ /* shift new year to 1st March in order to make leap year calc easy */
+ if (month < 2)
+ year--;
+
+ /* Find number of days since 1st March 1900 (in the Gregorian calendar). */
+ days = year * 365 + year / 4 - year / 100 + (year / 100 + 3) / 4;
+ days += dayoffset[month] + mday - 1;
+ days -= 25508; /* 1 jan 1970 is 25508 days since 1 mar 1900 */
+
+ if (days < 0) return APR_EBADDATE;
+ *days_ptr = days;
+ return APR_SUCCESS;
+}
+
+/* we've got the following units:
+ * years, months, days, hours, minutes, seconds, microseconds.
+ * There are always: 12 mon/year, 24 hr/day, 60 min/hr, 10^6 usec/sec.
+ * There are variable numbers of days/month (leap years, etc) and
+ * variable numbers of seconds/minute (leap seconds).
+ * So we normalize the ones we can, and do a binary-search or ask an
+ * oracle for the ones we can't. */
+/* result is in GMT */
+static apr_status_t
+normalize_time(apr_exploded_time_t *tm, timetype_t local_or_gmt) {
+ apr_exploded_time_t tmcopy = *tm, tmtest;
+ apr_int64_t years, days, hours;
+ apr_time_t lo, hi, x;
+ apr_status_t status;
+ int i;
+ /* normalize all components in order from coarsest to finest,
+ * ignoring yday and wday for now. */
+
+ /* normalize months */
+ years = tmcopy.tm_mon/12;
+ if (tmcopy.tm_mon < 0)
+ years--;
+ tmcopy.tm_year += years;
+ tmcopy.tm_mon -= 12*years;
+ assert(tmcopy.tm_mon >= 0 && tmcopy.tm_mon < 12);
+ /* we do the next normalizations cleverly to avoid overflow problems */
+ /* normalize minutes */
+ hours = tmcopy.tm_min / 60;
+ if (tmcopy.tm_min < 0)
+ hours--;
+ tmcopy.tm_min += /*any extra minutes would go here*/- hours*60;
+ assert(tmcopy.tm_min >= 0 && tmcopy.tm_min < 60);
+ /* normalize hours */
+ days = (hours+tmcopy.tm_hour)/24;
+ if ((hours+tmcopy.tm_hour) < 0)
+ days--;
+ tmcopy.tm_hour += hours - days*24;
+ assert(tmcopy.tm_hour >= 0 && tmcopy.tm_hour < 24);
+ /* okay, now we have some extra days in 'days' */
+ /* binary-search for a dates with the proper number of days. */
+
+ /* desired days since 1jan1970: */
+ if (APR_SUCCESS !=
+ (status = days_since_1jan1970(&i, tmcopy.tm_year, tmcopy.tm_mon, 1)))
+ goto fail;
+ days += i + (tmcopy.tm_mday-1);
+ /* lowest possible time_t (negative leap second every minute) */
+ lo = days * (24*60*59); /* note: seconds, not usec */
+ /* highest possible time_t (positive leap second every minute) */
+ hi = days * (24*60*61); /* note: seconds, not usec */
+ hi++; /* in routine below, hi actually contains 1 more than max poss. val */
+ /* do the search */
+ while (hi >= lo)
+ {
+ int found = 0;
+ x = (hi+lo)/2;
+ if (APR_SUCCESS != (status = apr_explode_gmt
+ (&tmtest, x * APR_USEC_PER_SEC)) ||
+ APR_SUCCESS != (status = days_since_1jan1970
+ (&i, tmtest.tm_year, tmtest.tm_mon, tmtest.tm_mday)))
+ goto fail;
+ /* compare candidate tmtest with tmcopy */
+ if (i < days ||
+ (i==days &&
+ (tmtest.tm_hour < tmcopy.tm_hour ||
+ (tmtest.tm_hour==tmcopy.tm_hour &&
+ (tmtest.tm_min < tmcopy.tm_min ||
+ (tmtest.tm_min==tmcopy.tm_min &&
+ (tmtest.tm_sec < 0 ||
+ (tmtest.tm_sec==0 &&
+ !(found=1/*perfect match!*/)))))))))
+ lo = x+1;
+ else
+ hi = x-1;
+ if (found)
+ {
+ /* found match, so mday is now normalized. Just account for the
+ * time zone offset and the left-over seconds and microseconds. */
+ x = (x+tmcopy.tm_sec-tmcopy.tm_gmtoff)*APR_USEC_PER_SEC
+ + tmcopy.tm_usec;
+ if (SVN_LOCAL_TIME == local_or_gmt)
+ return apr_explode_localtime(tm, x);
+ else
+ return apr_explode_gmt(tm, x);
+
+ }
+ }
+ /* match not found, something went wrong. */
+ status = APR_EBADDATE;
+ fail:
+ return status;
+}
+
+
+
+/* Parse according to W3C subset of ISO 8601: see
+ * http://www.w3.org/TR/NOTE-datetime
+ * The complete date must be specified.
+ * Any unspecified time components are assumed to be zeros.
+ * This is the 'extended' format of ISO8601.
+ * As per W3C subset, we don't allow week and day numbers. */
+static apr_status_t
+ISO8601ext_parse (const char *text, const apr_time_t now, apr_time_t * resultp)
+{
+ apr_exploded_time_t tm;
+ char *cp;
+ apr_time_t result;
+ apr_status_t status;
+ /* GENERAL EXTENSIONS TO W3C SUBSET: leading zeros are optional in
+ * year, month, day, hour, minute, and second fields. We also
+ * allow the locale's alternative numeric symbols, if any. */
+
+ /* Try: YYYY-MM-DDThh:mm:ssTZD YYYY-MM-DDThh:mm:ss.sTZD */
+ /* EXTENSION TO W3C SUBSET: Allow commas as decimal points as per ISO. */
+ /* Allow YYYY-MM-DDThh:mm:ss,sTZD too */
+ init_tm (&tm);
+ if (APR_SUCCESS == apr_strptime (text, &cp, " %EY-%Om-%OdT%OH:%OM:%OS", &tm))
+ {
+ if (('.' == cp[0] || ',' == cp[0]) && apr_isdigit (cp[1]))
+ {
+ /* Discard fractional seconds. */
+ for (cp++; apr_isdigit (*cp); cp++)
+ ;
+ }
+ if (APR_SUCCESS == ISO8601ext_parse_TZD (cp, &tm))
+ goto success;
+ }
+
+ /* Try: YYYY-MM-DDThh:mmTZD */
+ init_tm (&tm);
+ if (APR_SUCCESS == apr_strptime (text, &cp, " %EY-%Om-%OdT%OH:%OM", &tm) &&
+ APR_SUCCESS == ISO8601ext_parse_TZD (cp, &tm))
+ goto success;
+
+ /* Try: YYYY-MM-DD */
+ init_tm (&tm);
+ if (APR_SUCCESS == apr_strptime (text, &cp, " %EY-%Om-%Od ", &tm)
+ && 0 == *cp /* matched to end of string */)
+ goto success;
+ /* sketchy: we're parsing this as midnight UTC. User may expect this
+ * to be parsed as midnight localtime. Should require that a time zone
+ * designator be present, but this is not in ISO8601 spec. */
+
+ /* The YYYY and YYYY-MM ISO8601 formats are disallowed as incomplete. */
+
+ /* Give up: can't parse as ISO8601 extended format. */
+ return APR_EBADDATE;
+
+success:
+ /* We succeeded in parsing as ISO8601 extended format! */
+ status = apr_implode_gmt(&result, &tm);
+ if (APR_SUCCESS == status)
+ *resultp = result; /* success */
+ return status;
+}
+
+/* Parse an ISO8601 extended Time Zone Designator from 'text'.
+ * Update the 'apr_exploded_time_t' according to parsed time zone. Returns
+ * non-zero if we parse successfully and there is nothing but spaces
+ * following the time zone designator. */
+static apr_status_t
+ISO8601ext_parse_TZD (const char *text, apr_exploded_time_t *tm)
+{
+ /* Time Zone Designator is 'Z' or '+hh:mm' or '-hh:mm' */
+ apr_status_t status;
+ int isplus = 0;
+ switch (*(text++))
+ {
+ case 'Z':
+ break; /* done! */
+ case '+':
+ isplus = 1;
+ case '-':
+ /* parse hh:mm */
+ if (apr_isdigit (text[0]) && apr_isdigit (text[1]) && (text[2] == ':') &&
+ apr_isdigit (text[3]) && apr_isdigit (text[4]))
+ {
+ /* we jump through hoops to use strtol here, in the hopes that
+ * the c-library has localized this function (along with isdigit)
+ * to deal with possibly non-roman digit sets */
+ char buf[3] = { '0', '0', '\0' };
+ int hours, minutes;
+ buf[0] = text[0];
+ buf[1] = text[1];
+ hours = (int) strtol(buf, NULL, 10);
+ buf[0] = text[3];
+ buf[1] = text[4];
+ minutes = (int) strtol(buf, NULL, 10);
+ /* set gmtoff field (we'll renormalize to GMT at the end) */
+ tm->tm_gmtoff = 60*(60*hours+minutes) * (isplus?1:-1);
+ /* skip parsed hh:mm string */
+ text += 5;
+ break; /* done! */
+ }
+ /* EXTENSION TO W3C SUBSET: allow omitting the minutes section. */
+ /* parse hh */
+ if (apr_isdigit (text[0]) && apr_isdigit (text[1]))
+ {
+ /* we jump through hoops to use strtol here, in the hopes that
+ * the c-library has localized this function (along with isdigit)
+ * to deal with possibly non-roman digit sets */
+ char buf[3] = { '0', '0', '\0' };
+ int hours;
+ buf[0] = text[0];
+ buf[1] = text[1];
+ hours = (int) strtol(buf, NULL, 10);
+ /* set gmtoff field (we'll renormalize to GMT at the end) */
+ tm->tm_gmtoff = 60*(60*hours+0) * (isplus?1:-1);
+ /* skip parsed hh string */
+ text += 2;
+ break; /* done! */
+ }
+ default:
+ status = APR_EBADDATE; /* fail */
+ goto out;
+ }
+ /* renormalize to GMT */
+ if (APR_SUCCESS != (status = normalize_time(tm, SVN_GMT_TIME)))
+ goto out;
+ /* should be only spaces until the end of string */
+ while (apr_isspace (*text))
+ text++; /* skip spaces */
+ /* success if this is the end of string */
+ status = (*text == '\0') ? APR_SUCCESS : APR_EBADDATE;
+ /* done. */
+ out:
+ return status;
+}
+
+
+/* Parse according to 'basic' format of ISO8601.
+ * We don't allow week and day numbers.
+ */
+static apr_status_t
+ISO8601bas_parse (const char *text, const apr_time_t now, apr_time_t * resultp)
+{
+ apr_exploded_time_t tm;
+ char *cp;
+ apr_time_t result;
+ apr_status_t status;
+ /* extensions to ISO8601: leading zeros are optional in
+ * year, month, day, hour, minute, and second fields. We also
+ * allow the locale's alternative numeric symbols, if any. */
+
+ /* Try: YYYYMMDDThhmmssTZD YYYYMMDDThhmmss.sTZD YYYYMMDDThhmmss,sTZD */
+ init_tm (&tm);
+ if (APR_SUCCESS == apr_strptime (text, &cp, " %EY%Om%OdT%OH%OM%OS", &tm))
+ {
+ if (('.' == cp[0] || ',' == cp[0]) && apr_isdigit (cp[1]))
+ {
+ /* Discard fractional seconds. */
+ for (cp++; apr_isdigit (*cp); cp++)
+ ;
+ }
+ if (APR_SUCCESS == ISO8601bas_parse_TZD (cp, &tm))
+ goto success;
+ }
+
+ /* Try: YYYYMMDDThhmmTZD */
+ init_tm (&tm);
+ if (APR_SUCCESS == apr_strptime (text, &cp, " %EY%Om%OdT%OH%OM", &tm) &&
+ APR_SUCCESS == ISO8601bas_parse_TZD (cp, &tm))
+ goto success;
+
+ /* Try: YYYYMMDD */
+ init_tm (&tm);
+ if (APR_SUCCESS == apr_strptime (text, &cp, " %EY%Om%Od ", &tm)
+ && 0 == *cp /* matched to end of string */)
+ goto success;
+ /* sketchy: we're parsing this as midnight UTC. User may expect this
+ * to be parsed as midnight localtime. Should require that a time zone
+ * designator be present, but this is not in ISO8601 spec. */
+
+ /* The YYYY and YYYYMM ISO8601 formats are disallowed as incomplete. */
+
+ /* Give up: can't parse as ISO8601 basic format. */
+ return APR_EBADDATE;
+
+success:
+ /* We succeeded in parsing as ISO8601 basic format! */
+ status = apr_implode_gmt(&result, &tm);
+ if (APR_SUCCESS == status)
+ *resultp = result; /* success */
+ return status;
+}
+
+/* Parse an ISO8601 basic Time Zone Designator from 'text'.
+ * Update the 'apr_exploded_time_t' according to parsed time zone. Returns
+ * non-zero if we parse successfully and there is nothing but spaces
+ * following the time zone designator. */
+static apr_status_t
+ISO8601bas_parse_TZD (const char *text, apr_exploded_time_t *tm)
+{
+/* Time Zone Designator is: 'Z' or '+hhmm' or '-hhmm' */
+ apr_status_t status;
+ int isplus = 0;
+ switch (*(text++))
+ {
+ case 'Z':
+ break; /* done! */
+ case '+':
+ isplus = 1;
+ case '-':
+ /* parse hhmm */
+ if (apr_isdigit (text[0]) && apr_isdigit (text[1]) &&
+ apr_isdigit (text[2]) && apr_isdigit (text[3]))
+ {
+ /* we jump through hoops to use strtol here, in the hopes that
+ * the c-library has localized this function (along with isdigit)
+ * to deal with possibly non-roman digit sets */
+ char buf[3] = { '0', '0', '\0' };
+ int hours, minutes;
+ buf[0] = text[0];
+ buf[1] = text[1];
+ hours = (int) strtol(buf, NULL, 10);
+ buf[0] = text[2];
+ buf[1] = text[3];
+ minutes = (int) strtol(buf, NULL, 10);
+ /* set gmtoff field (we'll renormalize to GMT at the end) */
+ tm->tm_gmtoff = 60*(60*hours+minutes) * (isplus?1:-1);
+ /* skip parsed hhmm string */
+ text += 4;
+ break; /* done! */
+ }
+ /* parse hh */
+ if (apr_isdigit (text[0]) && apr_isdigit (text[1]))
+ {
+ /* we jump through hoops to use strtol here, in the hopes that
+ * the c-library has localized this function (along with isdigit)
+ * to deal with possibly non-roman digit sets */
+ char buf[3] = { '0', '0', '\0' };
+ int hours;
+ buf[0] = text[0];
+ buf[1] = text[1];
+ hours = (int) strtol(buf, NULL, 10);
+ /* set gmtoff field (we'll renormalize to GMT at the end) */
+ tm->tm_gmtoff = 60*(60*hours+0) * (isplus?1:-1);
+ /* skip parsed hh string */
+ text += 2;
+ break; /* done! */
+ }
+ default:
+ status = APR_EBADDATE; /* fail */
+ goto out;
+ }
+ /* renormalize to GMT */
+ if (APR_SUCCESS != (status = normalize_time(tm, SVN_GMT_TIME)))
+ goto out;
+ /* should be only spaces until the end of string */
+ while (apr_isspace (*text))
+ text++; /* skip spaces */
+ /* success if this is the end of string */
+ status = (*text == '\0') ? APR_SUCCESS : APR_EBADDATE;
+ /* done. */
+ out:
+ return status;
+}
+
+
+/* Parse 'human-readable' date, for the current locale. Words not
+ * numbers. =) This does absolute and relative time/dates. All times are
+ * *LOCAL* unless time zone is specified.
+ */
+static apr_status_t
+locale_parse (const char *text, const apr_time_t now, apr_time_t * resultp)
+{
+ apr_exploded_time_t tm;
+ apr_status_t status;
+ apr_time_t result;
+ char *cp;
+
+ /* initialize tm to 'now' in localtime */
+ if (APR_SUCCESS != (status = apr_explode_localtime(&tm, now)))
+ goto out;
+
+ /* skip leading whitespace */
+ while (apr_isspace (*text))
+ text++;
+ /* parse elements repeatedly until no elements are left (or we find
+ * an unparsable string. */
+ while (*text != '\0')
+ {
+ /* now attempt parse */
+ if (APR_SUCCESS != locale_parse_time (text, &cp, &tm) &&
+ APR_SUCCESS != locale_parse_date (text, &cp, &tm) &&
+ APR_SUCCESS != locale_parse_day (text, &cp, &tm) &&
+ APR_SUCCESS != locale_parse_zone (text, &cp, &tm) &&
+ APR_SUCCESS != locale_parse_relative (text, &cp, &tm))
+ /* can't parse as 'human-readable' absolute/relative date */
+ return APR_EBADDATE;
+ /* advance text pointer */
+ text = cp;
+ /* normalize tm to localtime */
+ if (APR_SUCCESS != (status = normalize_time(&tm, SVN_LOCAL_TIME)))
+ goto out;
+ /* skip leading whitespace */
+ while (apr_isspace (*text))
+ text++;
+ }
+
+ if (APR_SUCCESS != (status = apr_implode_gmt(&result, &tm)))
+ goto out;
+ *resultp = result; /* success */
+ out:
+ return status;
+}
+
+/* Parse a time specification from the 'text' string. Fill in fields
+ * of 'tm' if successful and return a pointer to the end of the time
+ * specification; else leave 'tm' untouched and return NULL. */
+static apr_status_t
+locale_parse_time (const char *text, char **retptr, apr_exploded_time_t *tm)
+{
+ static const char *formats[] = {
+ /* note that formats are ordered from most-specific to least */
+ "%OI:%OM:%OS %p ",
+ "%OH:%OM:%OS ",
+ "%OI:%OM %p ",
+ "%OH:%OM ",
+ "%OI %p ",
+ /* Finally, try locale-specific time format. */
+ "%EX ",
+ NULL /* end of list */
+ };
+ apr_exploded_time_t tmcopy, tmtrial;
+ char *cp;
+ int i;
+ /* Time fields are zero if not explicitly specified. */
+ tmcopy = *tm;
+ tmcopy.tm_usec = tmcopy.tm_sec = tmcopy.tm_min = tmcopy.tm_hour = 0;
+ for (i=0; formats[i]!=NULL; i++)
+ {
+ tmtrial = tmcopy;
+ if (APR_SUCCESS == apr_strptime (text, &cp, formats[i], &tmtrial))
+ {
+ /* success! */
+ *tm = tmtrial;
+ *retptr = cp;
+ return APR_SUCCESS;
+ }
+ }
+ /* failure */
+ return APR_EBADDATE;
+}
+
+/* Parse a date specification from the 'text' string. Fill in fields
+ * of 'tm' if successful and return a pointer to the end of the date
+ * specification; else leave 'tm' untouched and return NULL. */
+static apr_status_t
+locale_parse_date (const char *text, char **retptr, apr_exploded_time_t *tm)
+{
+ /* note that formats are ordered from most-specific to least */
+ static const char *formats[] = {
+ /* 1976-09-27: ISO 8601 */
+ "%EY-%Om-%Od ",
+ /* 27 September 1976 or 27sep1976, etc */
+ "%Od %b %EY ",
+ /* 27-September-1976 */
+ "%Od-%b-%EY ",
+ /* 27 sep */
+ "%Od %b ",
+ /* 27-sep */
+ "%Od-%b ",
+ /* September-27-1976 */
+ "%b-%Od-%EY ",
+ /* Sep 27, 1976 */
+ "%b %Od, %EY ",
+ /* Sep 27, */
+ "%b %Od, ",
+ /* Sep 27 */
+ "%b %Od ",
+ /* Sep-27 */
+ "%b-%Od ",
+ /* Finally, try locale-specific date format. */
+ "%Ex ",
+ NULL /* end of list */
+ };
+ apr_exploded_time_t tmtrial;
+ char *cp;
+ int i;
+ /* All date fields default to current date unless specified. */
+ for (i=0; formats[i]!=NULL; i++)
+ {
+ tmtrial = *tm;
+ if (APR_SUCCESS == apr_strptime (text, &cp, formats[i], &tmtrial))
+ {
+ /* success! */
+ *tm = tmtrial;
+ *retptr = cp;
+ return APR_SUCCESS;
+ }
+ }
+ /* failure */
+ return APR_EBADDATE;
+}
+
+/* Parse a day-of-week specification from the 'text' string. Advance
+ * date of 'tm' to that day-of-week if parse is successful and return
+ * a pointer to the end of the day-of-week specification; else leave
+ * 'tm' untouched and return NULL. */
+static apr_status_t
+locale_parse_day (const char *text, char **retptr, apr_exploded_time_t *tm)
+{
+ apr_exploded_time_t tmtrial = *tm;
+ int daydiff = 0;
+ char *cp;
+ int i;
+ /* Parse phrases like 'first monday', 'last monday', 'next monday'. */
+ static const struct
+ {
+ const char *phrase;
+ int weekdiff;
+ } phrases[] = {
+ /* These phrases should be translated: note that we call gettext below. */
+ /* multiple adjective cases or variant forms can be separated with
+ * semicolons. */
+ { "last %a", -1 },
+ { "%a; this %a", 0 },
+ { "next %a; first %a", 1 },
+ { "second %a", 2 },
+ { "third %a", 3 },
+ { "fourth %a", 4 },
+ { "fifth %a", 5 },
+ { "sixth %a", 6 },
+ { "seventh %a", 7 },
+ { "eighth %a", 8 },
+ { "ninth %a", 9 },
+ { 0, 0 }
+ };
+ /* Go through phrases and find the longest match. */
+ cp = NULL;
+ for (i = 0; phrases[i].phrase != NULL; i++)
+ {
+ const char *tr_phrase = gettext (phrases[i].phrase);
+ char buf[80];
+ int j;
+ while (*tr_phrase!='\0')
+ {
+ char *cptrial;
+ /* separate out ';'-delimited phrases */
+ for (j=0; *tr_phrase!='\0' && *tr_phrase!=';'; tr_phrase++)
+ if (j < (sizeof(buf)-1))
+ buf[j++]=*tr_phrase;
+ buf[j++]='\0';
+ /* skip delimiting semicolons */
+ while (';' == *tr_phrase)
+ tr_phrase++;
+ /* try this phrase; ensure there is a space, punct. or
+ end-of-string after the phrase; success only if
+ this match ends up being longer than previous best. */
+ if (APR_SUCCESS ==
+ apr_strptime (text, &cptrial, buf, &tmtrial) &&
+ (*cptrial=='\0' || *cptrial=='.' || *cptrial==',' ||
+ apr_isspace(*cptrial)) &&
+ (cp==NULL || cptrial > cp /* match must be longer */))
+ {
+ daydiff = 7 * phrases[i].weekdiff;
+ cp = cptrial;
+ }
+ /* okay, go back up and try the next ;-delimited translated phrase */
+ }
+ /* okay, go back up and try next phrase list */
+ }
+ if (cp==NULL)
+ /* failure: couldn't match any phrases */
+ return APR_EBADDATE;
+ /* else, success! */
+
+ /* tmtrial.tm_wday has the day of the week. Push current date ahead if
+ * necessary. */
+ if (tmtrial.tm_wday != tm->tm_wday) /* If selected day is not current day: */
+ /* Compute the number of days we need to push ahead. */
+ daydiff += (7 + tmtrial.tm_wday - tm->tm_wday) % 7;
+ /* Increment day of the month. */
+ tmtrial = *tm;
+ tmtrial.tm_mday += daydiff;
+ /* Renormalize tmtrial to localtime. */
+ if (APR_SUCCESS != normalize_time(&tmtrial, SVN_LOCAL_TIME))
+ return APR_EBADDATE;
+ *tm = tmtrial; /* This is the new date. */
+
+ /* A period or comma following a day of the week item is ignored. */
+ while (apr_isspace (*cp))
+ cp++;
+ if (*cp == ',' || *cp == '.')
+ cp++;
+ /* done! */
+ *retptr = cp;
+ return APR_SUCCESS;
+}
+
+/* Parse 'relative items' in date string, such as '1 year', '2 years
+* ago', etc. The strings in this function should be translated.
+* Adjust 'tm' if parse is successful and return a pointer to the end
+* of the 'relative item' specification; else leave 'tm' untouched and
+* return NULL. */
+static apr_status_t
+locale_parse_relative (const char *text, char **retptr,
+ apr_exploded_time_t *tm)
+{
+ const char *cp;
+ int i, secdiff, mindiff, mondiff;
+ /* Phrases in this list should all be translated. */
+ /* multiple adjective cases or variant forms can be separated with
+ * semicolons. If some concept below is untranslatable, a zero-length
+ * string may be used as the "translation". */
+ const static struct
+ {
+ const char *phrase;
+ int has_multiplier;
+ /* A 'month' and a 'year' may be different numbers of seconds depending
+ * on which month/year we're talking about; likewise a 'minute' may
+ * include a leap second making it 59 or 61 seconds. We express the
+ * time using three canonical units: second, minutes, and months. */
+ int seconds;
+ int minutes;
+ int months;
+ }
+ phrases[] = {
+ /* semicolon-delimited phrases has_mult s min months */
+
+ /* times in the past */
+ { "%d second ago; %d seconds ago", 1,-1, 0, 0 },
+ { "%d minute ago; %d minutes ago", 1, 0, -1, 0 },
+ { "%d hour ago; %d hours ago", 1, 0, -60, 0 },
+ { "%d day ago; %d days ago", 1, 0, -60 * 24, 0 },
+ { "%d week ago; %d weeks ago", 1, 0, -60 * 24 * 7, 0 },
+ { "%d fortnight ago; %d fortnights ago", 1, 0, -60 * 24 * 7 * 2, 0 },
+ { "%d month ago; %d months ago", 1, 0, 0, -1 },
+ { "%d year ago; %d years ago", 1, 0, 0, -12 },
+ /* times in the future */
+ { "%d second; %d seconds", 1, 1, 0, 0 },
+ { "%d minute; %d minutes", 1, 0, 1, 0 },
+ { "%d hour; %d hours", 1, 0, 60, 0 },
+ { "%d day; %d days", 1, 0, 60 * 24, 0 },
+ { "%d week; %d weeks", 1, 0, 60 * 24 * 7, 0 },
+ { "%d fortnight; %d fortnights", 1, 0, 60 * 24 * 7 * 2, 0 },
+ { "%d month; %d months", 1, 0, 0, 1 },
+ { "%d year; %d years", 1, 0, 0, 12 },
+ /* other special times */
+ { "tomorrow", 0, 0, 60 * 24, 0 },
+ { "next week", 0, 0, 60 * 24 * 7, 0 },
+ { "next fortnight", 0, 0, 60 * 24 * 7 * 2, 0 },
+ { "next month", 0, 0, 0, 1 },
+ { "next year", 0, 0, 0, 12 },
+ { "yesterday", 0, 0, -60 * 24, 0 },
+ { "last week", 0, 0, -60 * 24 * 7, 0 },
+ { "last fortnight", 0, 0, -60 * 24 * 7 * 2, 0 },
+ { "last month", 0, 0, 0, -1 },
+ { "last year", 0, 0, 0, -12 },
+ /* the current time */
+ { "today; now", 0, 0, 0, 0 },
+ /* nonce words */
+ { "at; from", 0, 0, 0, 0 },
+ /* end of list */
+ { 0, 0, 0, 0, 0 }
+ };
+ /* Try matching the phrases in the list; find the longest.
+ * Note the use of %n in the format string to find out how many letters
+ * sscanf has grokked over in its match. */
+ cp = NULL;
+ for (i = 0; phrases[i].phrase != NULL; i++)
+ {
+ const char *tr_phrase = gettext (phrases[i].phrase);
+ char buf[80];
+ while (*tr_phrase!='\0')
+ {
+ int j, multiplier, chars = 0;
+ /* separate out ';'-delimited phrases */
+ for (j=0; *tr_phrase!='\0' && *tr_phrase!=';'; tr_phrase++ )
+ if (j < (sizeof(buf)-3))
+ buf[j++]=*tr_phrase;
+ /* add '%n' to the end of the format string */
+ buf[j++]='%';
+ buf[j++]='n';
+ buf[j++]='\0';
+ /* skip delimiting semicolons */
+ while (';' == *tr_phrase)
+ tr_phrase++;
+ /* some phrases have multiplier patterns, some do not. */
+ multiplier = 1;
+ if (phrases[i].has_multiplier)
+ sscanf (text, buf, &multiplier, &chars);
+ else
+ sscanf (text, buf, &chars);
+ /* if sscanf errors out before matching the final %n
+ * pattern, then 'chars' will still be its initial 0 value.
+ * We also want to make sure that there is a space or
+ * end-of-string delimiter after our candidate phrase, and
+ * that it's longer than any other matches found so far. */
+ if (chars != 0 &&
+ (text[chars] == '\0' || apr_isspace (text[chars])) &&
+ (cp==NULL || text + chars > cp /* match must be longer */))
+ {
+ /* Found matching phrase! */
+ secdiff = multiplier * phrases[i].seconds;
+ mindiff = multiplier * phrases[i].minutes;
+ mondiff = multiplier * phrases[i].months;
+ cp = text + chars;
+ }
+ /* okay, go back up and try the next ;-delimited translated phrase */
+ }
+ /* okay, go back up and try next phrase list */
+ }
+ if (cp==NULL)
+ /* failure: couldn't match any phrases */
+ return APR_EBADDATE;
+ /* else, success! */
+
+ /* adjust tm */
+ tm->tm_sec += secdiff;
+ tm->tm_min += mindiff;
+ tm->tm_mon += mondiff;
+ /* Renormalize tm to localtime. */
+ if (APR_SUCCESS != normalize_time(tm, SVN_LOCAL_TIME))
+ return APR_EBADDATE;
+ /* done */
+ *retptr = (char*) cp; /* must lose const here */
+ return APR_SUCCESS;
+}
+
+/* Parse a time-zone specification from the 'text' string. Adjust
+ * time of 'tm' to that time zone if parse is successful and return a
+ * pointer to the end of the time-zone specification; else leave 'tm'
+ * untouched and return NULL. */
+static apr_status_t
+locale_parse_zone (const char *text, char **retptr, apr_exploded_time_t *tm)
+{
+ /* this is ugly: we accept '-0100', '+0100', '-01:00', and '+01:00'
+ * as well as 'GMT' 'UTC' and 'UT' for UTC. We also allow a single-letter
+ * 'military' time zone designation, so long as it is followed by a space
+ * or end-of-string.
+ * No other time zone designations are allowed; too many ambiguities.
+ */
+ const char *cp;
+ int i;
+ int minutediff;
+ const static struct
+ {
+ const char *name;
+ int hourdiff;
+ } zones[] = {
+ { "GMT", 0 },
+ { "UTC", 0 },
+ { "UT", 0 },
+ { "A", 1 },
+ { "B", 2 },
+ { "C", 3 },
+ { "D", 4 },
+ { "E", 5 },
+ { "F", 6 },
+ { "G", 7 },
+ { "H", 8 },
+ { "I", 9 },
+ { "K", 10 },
+ { "L", 11 },
+ { "M", 12 },
+ { "N", -1 },
+ { "O", -2 },
+ { "P", -3 },
+ { "Q", -4 },
+ { "R", -5 },
+ { "S", -6 },
+ { "T", -7 },
+ { "U", -8 },
+ { "V", -9 },
+ { "W", -10 },
+ { "X", -11 },
+ { "Y", -12 },
+ { "Z", 0 },
+ { 0, 0 }
+ };
+ /* Try time zone abbreviations from zones list. */
+ for (i = 0; zones[i].name != NULL; i++)
+ {
+ int zonelen = strlen (zones[i].name);
+ if (strncmp (text, zones[i].name, zonelen) == 0 &&
+ (zonelen > 1 || apr_isspace(text[zonelen]) || text[zonelen] == '\0'))
+ {
+ cp = text + zonelen;
+ minutediff = 60 * zones[i].hourdiff;
+ goto success;
+ }
+ }
+ /* Try +0000 +00:00 formats. */
+ if ((text[0] == '-' || text[0] == '+') &&
+ apr_isdigit (text[1]) && apr_isdigit (text[2]))
+ {
+ /* Again, jump through hoops to use strtol in order to accomodate
+ * non-roman digit sets. */
+ char buf[3] = { '0', '0', '\0' };
+ buf[0] = text[1];
+ buf[1] = text[2];
+ minutediff = 60 * strtol (buf, NULL, 10);
+ cp = text + 3;
+ if (*cp == ':')
+ cp++;
+ if (apr_isdigit (cp[0]) && apr_isdigit (cp[1]))
+ {
+ buf[0] = cp[0];
+ buf[1] = cp[1];
+ minutediff += strtol (buf, NULL, 10);
+ if (text[0] == '-')
+ minutediff = -minutediff;
+ cp += 2;
+ goto success;
+ }
+ }
+ /* failure */
+ return APR_EBADDATE;
+
+ success:
+ tm->tm_gmtoff = minutediff * 60; /* seconds east of UTC */
+ /* Done. */
+ *retptr = (char*) cp; /* well known type hole: must lose const here */
+ return APR_SUCCESS;
+}
+
+
+/*
+ * local variables:
+ * eval: (load-file "../svn-dev.el")
+ * end: */
diff -ruHpN -x SVN svn.orig/trunk/subversion/tests/libsvn_subr/date-test.c svn/trunk/subversion/tests/libsvn_subr/date-test.c
--- svn.orig/trunk/subversion/tests/libsvn_subr/date-test.c Wed Dec 31 19:00:00 1969
+++ svn/trunk/subversion/tests/libsvn_subr/date-test.c Wed Sep 5 14:54:09 2001
@@ -0,0 +1,233 @@
+/*
+ * date-test.c -- test the date parsing functions.
+ *
+ * ====================================================================
+ * Copyright (c) 2000-2001 CollabNet. All rights reserved.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://subversion.tigris.org/license-1.html.
+ * If newer versions of this license are posted there, you may use a
+ * newer version instead, at your option.
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals. For exact contribution history, see the revision
+ * history and logs, available at http://subversion.tigris.org/.
+ * ====================================================================
+ */
+
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h> /* for tzset */
+#include <apr_general.h>
+#include <apr_time.h>
+#include "svn_time.h"
+#include "svn_test.h"
+
+
+static svn_error_t *
+test_date_good (const char **msg,
+ apr_pool_t *pool)
+{
+ const struct {
+ const char *input;
+ const char *output;
+ } test_vector[] = {
+ /* iso extended dates */
+ {" 1976-09-27T06:30:01.23456789-05:00 ","Mon, 27 Sep 1976 11:30:01 GMT" },
+ {" 1976-09-27T06:30:01.23456789Z ","Mon, 27 Sep 1976 06:30:01 GMT" },
+ {" 1976-09-27T06:30:01.23456789+05:00 ","Mon, 27 Sep 1976 01:30:01 GMT" },
+
+ {" 1976-09-27T06:30:01,23456789-05:00 ","Mon, 27 Sep 1976 11:30:01 GMT" },
+ {" 1976-09-27T06:30:01,23456789Z ","Mon, 27 Sep 1976 06:30:01 GMT" },
+ {" 1976-09-27T06:30:01,23456789+05:00 ","Mon, 27 Sep 1976 01:30:01 GMT" },
+
+ {" 1976-09-27T06:30:01-05:00 ","Mon, 27 Sep 1976 11:30:01 GMT" },
+ {" 1976-09-27T06:30:01Z ","Mon, 27 Sep 1976 06:30:01 GMT" },
+ {" 1976-09-27T06:30:01+05:00 ","Mon, 27 Sep 1976 01:30:01 GMT" },
+
+ {" 1976-09-27T06:30-05:00 ","Mon, 27 Sep 1976 11:30:00 GMT" },
+ {" 1976-09-27T06:30Z ","Mon, 27 Sep 1976 06:30:00 GMT" },
+ {" 1976-09-27T06:30+05:00 ","Mon, 27 Sep 1976 01:30:00 GMT" },
+
+ {" 1976-09-27 ","Mon, 27 Sep 1976 00:00:00 GMT" },
+
+ /* iso basic dates */
+ {" 19760927T063001.23456789-0500 ","Mon, 27 Sep 1976 11:30:01 GMT" },
+ {" 19760927T063001.23456789Z ","Mon, 27 Sep 1976 06:30:01 GMT" },
+ {" 19760927T063001.23456789+0500 ","Mon, 27 Sep 1976 01:30:01 GMT" },
+
+ {" 19760927T063001,23456789-0500 ","Mon, 27 Sep 1976 11:30:01 GMT" },
+ {" 19760927T063001,23456789Z ","Mon, 27 Sep 1976 06:30:01 GMT" },
+ {" 19760927T063001,23456789+0500 ","Mon, 27 Sep 1976 01:30:01 GMT" },
+
+ {" 19760927T063001-0500 ","Mon, 27 Sep 1976 11:30:01 GMT" },
+ {" 19760927T063001Z ","Mon, 27 Sep 1976 06:30:01 GMT" },
+ {" 19760927T063001+0500 ","Mon, 27 Sep 1976 01:30:01 GMT" },
+
+ {" 19760927T0630-0500 ","Mon, 27 Sep 1976 11:30:00 GMT" },
+ {" 19760927T0630Z ","Mon, 27 Sep 1976 06:30:00 GMT" },
+ {" 19760927T0630+0500 ","Mon, 27 Sep 1976 01:30:00 GMT" },
+
+ {" 19760927 ","Mon, 27 Sep 1976 00:00:00 GMT" },
+
+ /* We parse RFC-style dates */
+ {"Mon, 27 Sep 1976 06:30:01 GMT","Mon, 27 Sep 1976 06:30:01 GMT" },
+
+ /* "natural language" dates */
+ { "now","Fri, 24 Aug 2001 00:47:12 GMT"},
+ { "today","Fri, 24 Aug 2001 00:47:12 GMT"},
+ { "yesterday","Thu, 23 Aug 2001 00:47:12 GMT"},
+ { "tomorrow","Sat, 25 Aug 2001 00:47:12 GMT"},
+ { "18:00:00 -0400","Thu, 23 Aug 2001 22:00:00 GMT"},
+ { "18:00:00","Thu, 23 Aug 2001 22:00:00 GMT"}, /* TZ is -0400 */
+ /* this next one is rather unusual: because it is 23 aug *LOCAL* time,
+ * we parse this as 18:00 on *23-aug* GMT, even though "now" is
+ * just after midnight *24-aug* GMT. */
+ { "18:00:00 GMT","Thu, 23 Aug 2001 18:00:00 GMT"},
+ { "6pm","Thu, 23 Aug 2001 22:00:00 GMT"}, /* TZ is -0400 */
+ { "at 6pm","Thu, 23 Aug 2001 22:00:00 GMT"},
+ { "6pm -04:00","Thu, 23 Aug 2001 22:00:00 GMT"},
+ { "6pm Q","Thu, 23 Aug 2001 22:00:00 GMT"},
+ /* again, this next one is a little odd. */
+ { "6pm GMT","Thu, 23 Aug 2001 18:00:00 GMT"},
+ { "monday","Tue, 28 Aug 2001 00:47:12 GMT"},
+ { "thursday","Fri, 24 Aug 2001 00:47:12 GMT"},/* it's thursday localtime*/
+ { "friday","Sat, 25 Aug 2001 00:47:12 GMT"},
+ { "this friday","Sat, 25 Aug 2001 00:47:12 GMT"},
+ { "next friday","Sat, 01 Sep 2001 00:47:12 GMT"},
+ { "last friday","Sat, 18 Aug 2001 00:47:12 GMT"},
+ /* note the tricky prefix issues here: */
+ { "last mon", "Tue, 21 Aug 2001 00:47:12 GMT"},
+ { "last mon.", "Tue, 21 Aug 2001 00:47:12 GMT"},
+ { "last monday", "Tue, 21 Aug 2001 00:47:12 GMT"},
+ { "last month","Tue, 24 Jul 2001 00:47:12 GMT"},
+ /* --- */
+ { " 1 month ago ","Tue, 24 Jul 2001 00:47:12 GMT"},
+ { " 8pm GMT 1 month ago ","Mon, 23 Jul 2001 20:00:00 GMT" },
+ { " 2 months ago ","Sun, 24 Jun 2001 00:47:12 GMT"},
+ { " 3 months from now ","Sat, 24 Nov 2001 00:47:12 GMT" },
+ { " 6 months 6 days ","Sat, 02 Mar 2002 00:47:12 GMT" },
+ { " 6:30am September 27, last year", "Wed, 27 Sep 2000 10:30:00 GMT"},
+ }, *tvp;
+ const apr_time_t now = ((apr_time_t)998614032) * APR_USEC_PER_SEC;
+ apr_time_t then;
+ apr_status_t status;
+ char actual[APR_RFC822_DATE_LEN];
+
+ *msg = "test svn_parse_good (good dates)";
+
+ /* set locale */
+ setlocale(LC_ALL, "C");
+ /* set time zone */
+ setenv("TZ", "XXX +04:00:00", 1); /* local time zone is -0400 */
+ tzset();
+
+ for (tvp=test_vector;
+ tvp-test_vector < sizeof(test_vector)/sizeof(*test_vector);
+ tvp++)
+ {
+ status = svn_parse_date(tvp->input, now, &then);
+ if (APR_SUCCESS != status)
+ {
+ return svn_error_createf
+ (SVN_ERR_TEST_FAILED, 0, NULL, pool,
+ "svn_parse_date (\"%s\", ...) failed to parse: %d",
+ tvp->input, (int) status);
+ }
+ apr_rfc822_date(actual, then);
+ if (strcmp(actual, tvp->output)!=0)
+ {
+ return svn_error_createf
+ (SVN_ERR_TEST_FAILED, 0, NULL, pool,
+ "svn_parse_date (\"%s\", ...) returned '%s'"
+ " instead of '%s'",
+ tvp->input, actual, tvp->output);
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+/* test bad dates */
+static svn_error_t *
+test_date_bad (const char **msg,
+ apr_pool_t *pool)
+{
+ const apr_time_t now = ((apr_time_t)998614032) * ((apr_time_t)1000000);
+ apr_time_t then;
+ apr_status_t status;
+ static const char *test_vector[] = {
+ /* bad ISO dates: mix of basic and extended formats */
+ "1976-09-27T06:30:01.23456789-0500",
+ "1976-09-27T06:30:01.23456789ZZ",
+ "1976-09-27T06:30:01.23456789+0500",
+
+ "1976-09-27T06:30:01,23456789-0500",
+ "1976-09-27T06:30:01,23456789ZZ",
+ "1976-09-27T06:30:01,23456789+0500",
+
+ "1976-09-27T06:30:01-0500",
+ "1976-09-27T06:30:01ZZ",
+ "1976-09-27T06:30:01+0500",
+
+ "1976-09-27T06:30-0500",
+ "1976-09-27T06:30ZZ",
+ "1976-09-27T06:30+0500",
+
+ "19760927T063001.23456789-05:00",
+ "19760927T063001.23456789ZZ",
+ "19760927T063001.23456789+05:00",
+
+ "19760927T063001,23456789-05:00",
+ "19760927T063001,23456789ZZ",
+ "19760927T063001,23456789+05:00",
+
+ "19760927T063001-05:00",
+ "19760927T063001ZZ",
+ "19760927T063001+05:00",
+
+ "19760927T0630-05:00",
+ "19760927T0630ZZ",
+ "19760927T0630+05:00",
+ }, **tvp;
+
+ *msg = "test svn_parse_bad (bad dates)";
+ for (tvp=test_vector;
+ tvp-test_vector < sizeof(test_vector)/sizeof(*test_vector);
+ tvp++)
+ {
+ /* svn_parse_date should fail on these bad inputs */
+ status = svn_parse_date(*tvp, now, &then);
+ if (APR_SUCCESS == status)
+ {
+ return svn_error_createf
+ (SVN_ERR_TEST_FAILED, 0, NULL, pool,
+ "svn_parse_date () claimed to successfully parse"
+ " the illegal date string \"%s\"", *tvp);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* The test table. */
+
+svn_error_t * (*test_funcs[]) (const char **msg,
+ apr_pool_t *pool) = {
+ 0,
+ test_date_good,
+ test_date_bad,
+ 0
+};
+
+
+
+/*
+ * local variables:
+ * eval: (load-file "../../svn-dev.el")
+ * end:
+ */
+
diff -ruHpN -x SVN svn.orig/trunk/www/project_tasks.html svn/trunk/www/project_tasks.html
--- svn.orig/trunk/www/project_tasks.html Wed Sep 5 14:50:52 2001
+++ svn/trunk/www/project_tasks.html Wed Sep 5 14:54:09 2001
@@ -121,46 +121,6 @@ Here are the tasks:
 
    <!-- ---------------------------------------------------------- -->
 
- <li> <b>Fix up date parsing library issues</b> <p>
- </li>
- This task is probably small, but will require some investigation
- and list discussion first probably. The basic issue is this: Ben
- took the getdate.y date grammar file from CVS (that file has always
- been in the public domain) and imported it into Subversion. So now
- Subversion has CVS's date parsing capabilities, which are good, but
- not perfect. Aside from the functionality issues, there's also the
- problem that getdate.c needs to be automatically generated from
- getdate.y, and it would be better to have a .c file that we edit
- directly, than a .y file which causes Subversion developers to be
- dependent on having the correct version of Yacc/Bison/Whatever
- installed.
- <p>
- This message from Branko summarizes the issues pretty well; read
- it, then move back and forth in the thread to get some context and
- a sense of what people see as the solution domain right now:
- <p>
- <a
- href="http://subversion.tigris.org/servlets/ReadMsg?msgId=31147&listName=dev"
- >http://subversion.tigris.org/servlets/ReadMsg?msgId=31147&listName=dev</a>
- <p>
-
- <!-- ---------------------------------------------------------- -->
-
- <li> <b>Constify svn_parse_date()'s first parameter</b> <p>
- </li>
- This is
- <a
- href="http://subversion.tigris.org/issues/show_bug.cgi?id=408">issue
- #408</a>, the description is:
- <p>
- The first argument of svn_parse_date() should be const. However,
- because the function originates in getdate.y and that parameter is
- related to the global yyInput variable, there may be more to this
- change than just adding the qualifier...
- <p>
-
- <!-- ---------------------------------------------------------- -->
-
    <li> <b>Implement .svnignore functionality</b> <p>
    </li>
    This is

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Sat Oct 21 14:36:40 2006

This is an archived mail posted to the Subversion Dev mailing list.

This site is subject to the Apache Privacy Policy and the Apache Public Forum Archive Policy.