~svn/ubuntu/oneiric/subversion/ppa

« back to all changes in this revision

Viewing changes to subversion/libsvn_subr/date.c

  • Committer: Bazaar Package Importer
  • Author(s): Adam Conrad
  • Date: 2005-12-05 01:26:14 UTC
  • mfrom: (1.1.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20051205012614-qom4xfypgtsqc2xq
Tags: 1.2.3dfsg1-3ubuntu1
Merge with the final Debian release of 1.2.3dfsg1-3, bringing in
fixes to the clean target, better documentation of the libdb4.3
upgrade and build fixes to work with swig1.3_1.3.27.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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 */
 
24
enum 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 */
 
40
typedef 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 */
 
51
typedef 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. */
 
62
static const rule
 
63
rules[] =
 
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. */
 
88
static const rule *
 
89
find_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. */
 
104
static svn_boolean_t
 
105
template_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
 
 
185
static int
 
186
valid_days_by_month[] = {
 
187
  31, 29, 31, 30,
 
188
  31, 30, 31, 31,
 
189
  30, 31, 30, 31
 
190
};
 
191
 
 
192
svn_error_t *
 
193
svn_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-MM-DD",
 
208
                      text)
 
209
      || template_match (&expt, &localtz, /* ISO-8601 extended, UTC */
 
210
                      "YYYY-MM-DDThh:mm[:ss[.u[u[u[u[u[u][Z]",
 
211
                      text)
 
212
      || template_match (&expt, &localtz, /* ISO-8601 extended, with offset */
 
213
                         "YYYY-MM-DDThh: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-MM-DD hh:mm[:ss[.u[u[u[u[u[u][ +OO[oo]",
 
226
                         text))
 
227
    {
 
228
      expt.tm_year -= 1900;
 
229
      expt.tm_mon -= 1;
 
230
    }
 
231
  else if (template_match (&expt, &localtz, /* Just a time */
 
232
                           "hh:mm[:ss[.u[u[u[u[u[u]",
 
233
                           text))
 
234
    {
 
235
      expt.tm_year = expnow.tm_year;
 
236
      expt.tm_mon = expnow.tm_mon;
 
237
      expt.tm_mday = expnow.tm_mday;
 
238
    }
 
239
  else
 
240
    return SVN_NO_ERROR;
 
241
 
 
242
  /* Range validation, allowing for leap seconds */
 
243
  if (expt.tm_mon < 0 || expt.tm_mon > 11
 
244
      || expt.tm_mday > valid_days_by_month[expt.tm_mon]
 
245
      || expt.tm_hour > 23
 
246
      || expt.tm_min > 59
 
247
      || expt.tm_sec > 60)
 
248
    return SVN_NO_ERROR;
 
249
 
 
250
  /* february/leap-year day checking.  tm_year is bias-1900, so centuries
 
251
     that equal 100 (mod 400) are multiples of 400. */
 
252
  if (expt.tm_mon == 1
 
253
      && expt.tm_mday == 29
 
254
      && (expt.tm_year % 4 != 0
 
255
          || (expt.tm_year % 100 == 0 && expt.tm_year % 400 != 100)))
 
256
    return SVN_NO_ERROR;
 
257
 
 
258
  if (localtz)
 
259
    {
 
260
      apr_time_t candidate;
 
261
      apr_time_exp_t expthen;
 
262
 
 
263
      /* We need to know the GMT offset of the requested time, not the
 
264
         current time.  In some cases, that quantity is ambiguous,
 
265
         since at the end of daylight saving's time, an hour's worth
 
266
         of local time happens twice.  For those cases, we should
 
267
         prefer DST if we are currently in DST, and standard time if
 
268
         not.  So, calculate the time value using the current time's
 
269
         GMT offset and use the GMT offset of the resulting time. */
 
270
      expt.tm_gmtoff = expnow.tm_gmtoff;
 
271
      apr_err = apr_time_exp_gmt_get (&candidate, &expt);
 
272
      if (apr_err != APR_SUCCESS)
 
273
        return svn_error_wrap_apr (apr_err,
 
274
                                   _("Can't calculate requested date"));
 
275
      apr_err = apr_time_exp_lt (&expthen, candidate);
 
276
      if (apr_err != APR_SUCCESS)
 
277
        return svn_error_wrap_apr (apr_err, _("Can't expand time"));
 
278
      expt.tm_gmtoff = expthen.tm_gmtoff;
 
279
    }
 
280
  apr_err = apr_time_exp_gmt_get (result, &expt);
 
281
  if (apr_err != APR_SUCCESS)
 
282
    return svn_error_wrap_apr (apr_err, _("Can't calculate requested date"));
 
283
 
 
284
  *matched = TRUE;
 
285
  return SVN_NO_ERROR;
 
286
}