1
/* date.c: date parsing for Subversion
3
* ====================================================================
4
* Copyright (c) 2000-2004 CollabNet. All rights reserved.
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.
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
* ====================================================================
19
#include "svn_error.h"
21
#include "svn_private_config.h"
23
/* Valid rule actions */
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 */
39
/* How to handle a particular character in a template */
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. */
50
/* The parsed values, before localtime/gmt processing */
55
apr_int32_t offminutes;
58
#define DIGITS "0123456789"
60
/* A declarative specification of how each template character
61
should be processed, using a rule for each valid symbol. */
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 },
86
/* Return the rule associated with TCHAR, or NULL if there
89
find_rule (char tchar)
91
int i = sizeof (rules)/sizeof (rules[0]);
93
if (rules[i].key == tchar)
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. */
105
template_match (apr_time_exp_t *expt, svn_boolean_t *localtz,
106
const char *template, const char *value)
108
int multiplier = 100000;
111
char *base = (char *)&ms;
113
memset (&ms, 0, sizeof (ms));
117
const rule *match = find_rule (*template++);
118
char vchar = *value++;
121
if (!match || (match->valid
122
&& (!vchar || !strchr (match->valid, vchar))))
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
129
place = (apr_int32_t *)(base + match->offset);
130
switch (match->action)
133
*place = *place * 10 + vchar - '0';
136
*place += (vchar - '0') * multiplier;
150
match = find_rule (*template);
151
if (!strchr (match->valid, vchar))
152
template = strchr (template, ']') + 1;
164
/* Validate gmt offset here, since we can't reliably do it later. */
165
if (ms.offhours > 23 || ms.offminutes > 59)
168
/* tzind will be '+' or '-' for an explicit time zone, 'Z' to
169
indicate UTC, or 0 to indicate local time. */
173
ms.base.tm_gmtoff = ms.offhours * 3600 + ms.offminutes * 60;
176
ms.base.tm_gmtoff = -(ms.offhours * 3600 + ms.offminutes * 60);
181
*localtz = (tzind == 0);
186
valid_days_by_month[] = {
193
svn_parse_date (svn_boolean_t *matched, apr_time_t *result, const char *text,
194
apr_time_t now, apr_pool_t *pool)
196
apr_time_exp_t expt, expnow;
197
apr_status_t apr_err;
198
svn_boolean_t localtz;
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"));
206
if (template_match (&expt, &localtz, /* ISO-8601 extended, date only */
209
|| template_match (&expt, &localtz, /* ISO-8601 extended, UTC */
210
"YYYY-MM-DDThh:mm[:ss[.u[u[u[u[u[u][Z]",
212
|| template_match (&expt, &localtz, /* ISO-8601 extended, with offset */
213
"YYYY-MM-DDThh:mm[:ss[.u[u[u[u[u[u]+OO[:oo]",
215
|| template_match (&expt, &localtz, /* ISO-8601 basic, date only */
218
|| template_match (&expt, &localtz, /* ISO-8601 basic, UTC */
219
"YYYYMMDDThhmm[ss[.u[u[u[u[u[u][Z]",
221
|| template_match (&expt, &localtz, /* ISO-8601 basic, with offset */
222
"YYYYMMDDThhmm[ss[.u[u[u[u[u[u]+OO[oo]",
224
|| template_match (&expt, &localtz, /* "svn log" format */
225
"YYYY-MM-DD hh:mm[:ss[.u[u[u[u[u[u][ +OO[oo]",
228
expt.tm_year -= 1900;
231
else if (template_match (&expt, &localtz, /* Just a time */
232
"hh:mm[:ss[.u[u[u[u[u[u]",
235
expt.tm_year = expnow.tm_year;
236
expt.tm_mon = expnow.tm_mon;
237
expt.tm_mday = expnow.tm_mday;
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]
250
/* february/leap-year day checking. tm_year is bias-1900, so centuries
251
that equal 100 (mod 400) are multiples of 400. */
253
&& expt.tm_mday == 29
254
&& (expt.tm_year % 4 != 0
255
|| (expt.tm_year % 100 == 0 && expt.tm_year % 400 != 100)))
260
apr_time_t candidate;
261
apr_time_exp_t expthen;
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;
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"));