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
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-M[M]-D[D]Th[h]:mm[:ss[.u[u[u[u[u[u][Z]",
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]",
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-M[M]-D[D] h[h]:mm[:ss[.u[u[u[u[u[u][ +OO[oo]",
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]",
231 expt
.tm_year
-= 1900;
234 else if (template_match(&expt
, &localtz
, /* Just a time */
235 "h[h]:mm[:ss[.u[u[u[u[u[u]",
238 expt
.tm_year
= expnow
.tm_year
;
239 expt
.tm_mon
= expnow
.tm_mon
;
240 expt
.tm_mday
= expnow
.tm_mday
;
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
]
254 /* february/leap-year day checking. tm_year is bias-1900, so centuries
255 that equal 100 (mod 400) are multiples of 400. */
257 && expt
.tm_mday
== 29
258 && (expt
.tm_year
% 4 != 0
259 || (expt
.tm_year
% 100 == 0 && expt
.tm_year
% 400 != 100)))
264 apr_time_t candidate
;
265 apr_time_exp_t expthen
;
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
;
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"));