Remove no-longer-used svn_*_get_mergeinfo_for_tree APIs.
[svn.git] / subversion / libsvn_subr / date.c
blob2c1803f879fc2d621e1e545a7a4d11d28c3ab15d
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 * ====================================================================
18 #include "svn_time.h"
19 #include "svn_error.h"
21 #include "svn_private_config.h"
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 */
39 /* How to handle a particular character in a template */
40 typedef struct
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;
50 /* The parsed values, before localtime/gmt processing */
51 typedef struct
53 apr_time_exp_t base;
54 apr_int32_t offhours;
55 apr_int32_t offminutes;
56 } match_state;
58 #define DIGITS "0123456789"
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[] =
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
87 is no such rule. */
88 static const rule *
89 find_rule(char tchar)
91 int i = sizeof(rules)/sizeof(rules[0]);
92 while (i--)
93 if (rules[i].key == tchar)
94 return &rules[i];
95 return NULL;
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)
108 int multiplier = 100000;
109 int tzind = 0;
110 match_state ms;
111 char *base = (char *)&ms;
113 memset(&ms, 0, sizeof(ms));
115 for (;;)
117 const rule *match = find_rule(*template++);
118 char vchar = *value++;
119 apr_int32_t *place;
121 if (!match || (match->valid
122 && (!vchar || !strchr(match->valid, vchar))))
123 return FALSE;
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)
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;
161 break;
164 /* Validate gmt offset here, since we can't reliably do it later. */
165 if (ms.offhours > 23 || ms.offminutes > 59)
166 return FALSE;
168 /* tzind will be '+' or '-' for an explicit time zone, 'Z' to
169 indicate UTC, or 0 to indicate local time. */
170 switch (tzind)
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;
180 *expt = ms.base;
181 *localtz = (tzind == 0);
182 return TRUE;
185 static int
186 valid_days_by_month[] = {
187 31, 29, 31, 30,
188 31, 30, 31, 31,
189 30, 31, 30, 31
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)
196 apr_time_exp_t expt, expnow;
197 apr_status_t apr_err;
198 svn_boolean_t localtz;
200 *matched = FALSE;
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 */
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))
231 expt.tm_year -= 1900;
232 expt.tm_mon -= 1;
234 else if (template_match(&expt, &localtz, /* Just a time */
235 "h[h]:mm[:ss[.u[u[u[u[u[u]",
236 text))
238 expt.tm_year = expnow.tm_year;
239 expt.tm_mon = expnow.tm_mon;
240 expt.tm_mday = expnow.tm_mday;
242 else
243 return SVN_NO_ERROR;
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;
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;
262 if (localtz)
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"));
288 *matched = TRUE;
289 return SVN_NO_ERROR;