source: valtobtest/subversion-1.6.2/subversion/libsvn_subr/date.c @ 3

Last change on this file since 3 was 3, checked in by valtob, 15 years ago

subversion source 1.6.2 as test

File size: 9.7 KB
Line 
1/* date.c:  date parsing for Subversion
2 *
3 * ====================================================================
4 * Copyright (c) 2000-2004 CollabNet.  All rights reserved.
5 *
6 * This software is licensed as described in the file COPYING, which
7 * you should have received as part of this distribution.  The terms
8 * are also available at http://subversion.tigris.org/license-1.html.
9 * If newer versions of this license are posted there, you may use a
10 * newer version instead, at your option.
11 *
12 * This software consists of voluntary contributions made by many
13 * individuals.  For exact contribution history, see the revision
14 * history and logs, available at http://subversion.tigris.org/.
15 * ====================================================================
16 */
17
18#include "svn_time.h"
19#include "svn_error.h"
20
21#include "svn_private_config.h"
22
23/* Valid rule actions */
24enum rule_action {
25  ACCUM,    /* Accumulate a decimal value */
26  MICRO,    /* Accumulate microseconds */
27  TZIND,    /* Handle +, -, Z */
28  NOOP,     /* Do nothing */
29  SKIPFROM, /* If at end-of-value, accept the match.  Otherwise,
30               if the next template character matches the current
31               value character, continue processing as normal.
32               Otherwise, attempt to complete matching starting
33               immediately after the first subsequent occurrance of
34               ']' in the template. */
35  SKIP,     /* Ignore this template character */
36  ACCEPT    /* Accept the value */
37};
38
39/* How to handle a particular character in a template */
40typedef struct
41{
42  char key;                /* The template char that this rule matches */
43  const char *valid;       /* String of valid chars for this rule */
44  enum rule_action action; /* What action to take when the rule is matched */
45  int offset;              /* Where to store the any results of the action,
46                              expressed in terms of bytes relative to the
47                              base of a match_state object. */
48} rule;
49
50/* The parsed values, before localtime/gmt processing */
51typedef struct
52{
53  apr_time_exp_t base;
54  apr_int32_t offhours;
55  apr_int32_t offminutes;
56} match_state;
57
58#define DIGITS "0123456789"
59
60/* A declarative specification of how each template character
61   should be processed, using a rule for each valid symbol. */
62static const rule
63rules[] =
64{
65  { 'Y', DIGITS, ACCUM, APR_OFFSETOF(match_state, base.tm_year) },
66  { 'M', DIGITS, ACCUM, APR_OFFSETOF(match_state, base.tm_mon) },
67  { 'D', DIGITS, ACCUM, APR_OFFSETOF(match_state, base.tm_mday) },
68  { 'h', DIGITS, ACCUM, APR_OFFSETOF(match_state, base.tm_hour) },
69  { 'm', DIGITS, ACCUM, APR_OFFSETOF(match_state, base.tm_min) },
70  { 's', DIGITS, ACCUM, APR_OFFSETOF(match_state, base.tm_sec) },
71  { 'u', DIGITS, MICRO, APR_OFFSETOF(match_state, base.tm_usec) },
72  { 'O', DIGITS, ACCUM, APR_OFFSETOF(match_state, offhours) },
73  { 'o', DIGITS, ACCUM, APR_OFFSETOF(match_state, offminutes) },
74  { '+', "-+", TZIND, 0 },
75  { 'Z', "Z", TZIND, 0 },
76  { ':', ":", NOOP, 0 },
77  { '-', "-", NOOP, 0 },
78  { 'T', "T", NOOP, 0 },
79  { ' ', " ", NOOP, 0 },
80  { '.', ".,", NOOP, 0 },
81  { '[', NULL, SKIPFROM, 0 },
82  { ']', NULL, SKIP, 0 },
83  { '\0', NULL, ACCEPT, 0 },
84};
85
86/* Return the rule associated with TCHAR, or NULL if there
87   is no such rule. */
88static const rule *
89find_rule(char tchar)
90{
91  int i = sizeof(rules)/sizeof(rules[0]);
92  while (i--)
93    if (rules[i].key == tchar)
94      return &rules[i];
95  return NULL;
96}
97
98/* Attempt to match the date-string in VALUE to the provided TEMPLATE,
99   using the rules defined above.  Return TRUE on successful match,
100   FALSE otherwise.  On successful match, fill in *EXP with the
101   matched values and set *LOCALTZ to TRUE if the local time zone
102   should be used to interpret the match (i.e. if no time zone
103   information was provided), or FALSE if not. */
104static svn_boolean_t
105template_match(apr_time_exp_t *expt, svn_boolean_t *localtz,
106               const char *template, const char *value)
107{
108  int multiplier = 100000;
109  int tzind = 0;
110  match_state ms;
111  char *base = (char *)&ms;
112
113  memset(&ms, 0, sizeof(ms));
114
115  for (;;)
116    {
117      const rule *match = find_rule(*template++);
118      char vchar = *value++;
119      apr_int32_t *place;
120
121      if (!match || (match->valid
122                     && (!vchar || !strchr(match->valid, vchar))))
123        return FALSE;
124
125      /* Compute the address of memory location affected by this
126         rule by adding match->offset bytes to the address of ms.
127         Because this is a byte-quantity, it is necessary to cast
128         &ms to char *. */
129      place = (apr_int32_t *)(base + match->offset);
130      switch (match->action)
131        {
132        case ACCUM:
133          *place = *place * 10 + vchar - '0';
134          continue;
135        case MICRO:
136          *place += (vchar - '0') * multiplier;
137          multiplier /= 10;
138          continue;
139        case TZIND:
140          tzind = vchar;
141          continue;
142        case SKIP:
143          value--;
144          continue;
145        case NOOP:
146          continue;
147        case SKIPFROM:
148          if (!vchar)
149            break;
150          match = find_rule(*template);
151          if (!strchr(match->valid, vchar))
152            template = strchr(template, ']') + 1;
153          value--;
154          continue;
155        case ACCEPT:
156          if (vchar)
157            return FALSE;
158          break;
159        }
160
161      break;
162    }
163
164  /* Validate gmt offset here, since we can't reliably do it later. */
165  if (ms.offhours > 23 || ms.offminutes > 59)
166    return FALSE;
167
168  /* tzind will be '+' or '-' for an explicit time zone, 'Z' to
169     indicate UTC, or 0 to indicate local time. */
170  switch (tzind)
171    {
172    case '+':
173      ms.base.tm_gmtoff = ms.offhours * 3600 + ms.offminutes * 60;
174      break;
175    case '-':
176      ms.base.tm_gmtoff = -(ms.offhours * 3600 + ms.offminutes * 60);
177      break;
178    }
179
180  *expt = ms.base;
181  *localtz = (tzind == 0);
182  return TRUE;
183}
184
185static int
186valid_days_by_month[] = {
187  31, 29, 31, 30,
188  31, 30, 31, 31,
189  30, 31, 30, 31
190};
191
192svn_error_t *
193svn_parse_date(svn_boolean_t *matched, apr_time_t *result, const char *text,
194               apr_time_t now, apr_pool_t *pool)
195{
196  apr_time_exp_t expt, expnow;
197  apr_status_t apr_err;
198  svn_boolean_t localtz;
199
200  *matched = FALSE;
201
202  apr_err = apr_time_exp_lt(&expnow, now);
203  if (apr_err != APR_SUCCESS)
204    return svn_error_wrap_apr(apr_err, _("Can't manipulate current date"));
205
206  if (template_match(&expt, &localtz, /* ISO-8601 extended, date only */
207                     "YYYY-M[M]-D[D]",
208                     text)
209      || template_match(&expt, &localtz, /* ISO-8601 extended, UTC */
210                        "YYYY-M[M]-D[D]Th[h]:mm[:ss[.u[u[u[u[u[u][Z]",
211                        text)
212      || template_match(&expt, &localtz, /* ISO-8601 extended, with offset */
213                        "YYYY-M[M]-D[D]Th[h]:mm[:ss[.u[u[u[u[u[u]+OO[:oo]",
214                        text)
215      || template_match(&expt, &localtz, /* ISO-8601 basic, date only */
216                        "YYYYMMDD",
217                        text)
218      || template_match(&expt, &localtz, /* ISO-8601 basic, UTC */
219                        "YYYYMMDDThhmm[ss[.u[u[u[u[u[u][Z]",
220                        text)
221      || template_match(&expt, &localtz, /* ISO-8601 basic, with offset */
222                        "YYYYMMDDThhmm[ss[.u[u[u[u[u[u]+OO[oo]",
223                        text)
224      || template_match(&expt, &localtz, /* "svn log" format */
225                        "YYYY-M[M]-D[D] h[h]:mm[:ss[.u[u[u[u[u[u][ +OO[oo]",
226                        text)
227      || template_match(&expt, &localtz, /* GNU date's iso-8601 */
228                        "YYYY-M[M]-D[D]Th[h]:mm[:ss[.u[u[u[u[u[u]+OO[oo]",
229                        text))
230    {
231      expt.tm_year -= 1900;
232      expt.tm_mon -= 1;
233    }
234  else if (template_match(&expt, &localtz, /* Just a time */
235                          "h[h]:mm[:ss[.u[u[u[u[u[u]",
236                          text))
237    {
238      expt.tm_year = expnow.tm_year;
239      expt.tm_mon = expnow.tm_mon;
240      expt.tm_mday = expnow.tm_mday;
241    }
242  else
243    return SVN_NO_ERROR;
244
245  /* Range validation, allowing for leap seconds */
246  if (expt.tm_mon < 0 || expt.tm_mon > 11
247      || expt.tm_mday > valid_days_by_month[expt.tm_mon]
248      || expt.tm_mday < 1
249      || expt.tm_hour > 23
250      || expt.tm_min > 59
251      || expt.tm_sec > 60)
252    return SVN_NO_ERROR;
253
254  /* february/leap-year day checking.  tm_year is bias-1900, so centuries
255     that equal 100 (mod 400) are multiples of 400. */
256  if (expt.tm_mon == 1
257      && expt.tm_mday == 29
258      && (expt.tm_year % 4 != 0
259          || (expt.tm_year % 100 == 0 && expt.tm_year % 400 != 100)))
260    return SVN_NO_ERROR;
261
262  if (localtz)
263    {
264      apr_time_t candidate;
265      apr_time_exp_t expthen;
266
267      /* We need to know the GMT offset of the requested time, not the
268         current time.  In some cases, that quantity is ambiguous,
269         since at the end of daylight saving's time, an hour's worth
270         of local time happens twice.  For those cases, we should
271         prefer DST if we are currently in DST, and standard time if
272         not.  So, calculate the time value using the current time's
273         GMT offset and use the GMT offset of the resulting time. */
274      expt.tm_gmtoff = expnow.tm_gmtoff;
275      apr_err = apr_time_exp_gmt_get(&candidate, &expt);
276      if (apr_err != APR_SUCCESS)
277        return svn_error_wrap_apr(apr_err,
278                                  _("Can't calculate requested date"));
279      apr_err = apr_time_exp_lt(&expthen, candidate);
280      if (apr_err != APR_SUCCESS)
281        return svn_error_wrap_apr(apr_err, _("Can't expand time"));
282      expt.tm_gmtoff = expthen.tm_gmtoff;
283    }
284  apr_err = apr_time_exp_gmt_get(result, &expt);
285  if (apr_err != APR_SUCCESS)
286    return svn_error_wrap_apr(apr_err, _("Can't calculate requested date"));
287
288  *matched = TRUE;
289  return SVN_NO_ERROR;
290}
Note: See TracBrowser for help on using the repository browser.