Add the restrict qualifier to strftime(3) and strptime(3).
[dragonfly.git] / lib / libc / stdtime / strptime.c
blob9b4b01268bc8976d3d8bd9ab6f02f1a47170186f
1 /* $NetBSD: strptime.c,v 1.31 2008/11/04 21:08:33 christos Exp $ */
2 /* $DragonFly: src/lib/libc/stdtime/strptime.c,v 1.5 2005/12/04 23:25:40 swildner Exp $ */
4 /*-
5 * Copyright (c) 1997, 1998, 2005, 2008 The NetBSD Foundation, Inc.
6 * All rights reserved.
8 * This code was contributed to The NetBSD Foundation by Klaus Klein.
9 * Heavily optimised by David Laight
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
20 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
33 #include <sys/localedef.h>
34 #include <ctype.h>
35 #include <locale.h>
36 #include <string.h>
37 #include <time.h>
38 #include "private.h"
39 #include "tzfile.h"
41 #define _ctloc(x) (_CurrentTimeLocale->x)
44 * We do not implement alternate representations. However, we always
45 * check whether a given modifier is allowed for a certain conversion.
47 #define ALT_E 0x01
48 #define ALT_O 0x02
49 #define LEGAL_ALT(x) { if (alt_format & ~(x)) return NULL; }
51 static char gmt[] = { "GMT" };
52 static char utc[] = { "UTC" };
54 static const u_char *conv_num(const unsigned char *, int *, uint, uint);
55 static const u_char *find_string(const u_char *, int *, const char * const *,
56 const char * const *, int);
58 char *
59 strptime(const char * __restrict buf, const char * __restrict fmt,
60 struct tm * __restrict tm)
62 unsigned char c;
63 const unsigned char *bp;
64 int alt_format, i, split_year = 0, neg, offs;
65 const char *new_fmt;
67 bp = (const u_char *)buf;
69 while (bp != NULL && (c = *fmt++) != '\0') {
70 /* Clear `alternate' modifier prior to new conversion. */
71 alt_format = 0;
72 i = 0;
74 /* Eat up white-space. */
75 if (isspace(c)) {
76 while (isspace(*bp))
77 bp++;
78 continue;
81 if (c != '%')
82 goto literal;
85 again: switch (c = *fmt++) {
86 case '%': /* "%%" is converted to "%". */
87 literal:
88 if (c != *bp++)
89 return NULL;
90 LEGAL_ALT(0);
91 continue;
94 * "Alternative" modifiers. Just set the appropriate flag
95 * and start over again.
97 case 'E': /* "%E?" alternative conversion modifier. */
98 LEGAL_ALT(0);
99 alt_format |= ALT_E;
100 goto again;
102 case 'O': /* "%O?" alternative conversion modifier. */
103 LEGAL_ALT(0);
104 alt_format |= ALT_O;
105 goto again;
108 * "Complex" conversion rules, implemented through recursion.
110 case 'c': /* Date and time, using the locale's format. */
111 new_fmt = _ctloc(d_t_fmt);
112 goto recurse;
114 case 'D': /* The date as "%m/%d/%y". */
115 new_fmt = "%m/%d/%y";
116 LEGAL_ALT(0);
117 goto recurse;
119 case 'F': /* The date as "%Y-%m-%d". */
120 new_fmt = "%Y-%m-%d";
121 LEGAL_ALT(0);
122 goto recurse;
124 case 'R': /* The time as "%H:%M". */
125 new_fmt = "%H:%M";
126 LEGAL_ALT(0);
127 goto recurse;
129 case 'r': /* The time in 12-hour clock representation. */
130 new_fmt =_ctloc(t_fmt_ampm);
131 LEGAL_ALT(0);
132 goto recurse;
134 case 'T': /* The time as "%H:%M:%S". */
135 new_fmt = "%H:%M:%S";
136 LEGAL_ALT(0);
137 goto recurse;
139 case 'X': /* The time, using the locale's format. */
140 new_fmt =_ctloc(t_fmt);
141 goto recurse;
143 case 'x': /* The date, using the locale's format. */
144 new_fmt =_ctloc(d_fmt);
145 recurse:
146 bp = (const u_char *)strptime((const char *)bp,
147 new_fmt, tm);
148 LEGAL_ALT(ALT_E);
149 continue;
152 * "Elementary" conversion rules.
154 case 'A': /* The day of week, using the locale's form. */
155 case 'a':
156 bp = find_string(bp, &tm->tm_wday, _ctloc(day),
157 _ctloc(abday), 7);
158 LEGAL_ALT(0);
159 continue;
161 case 'B': /* The month, using the locale's form. */
162 case 'b':
163 case 'h':
164 bp = find_string(bp, &tm->tm_mon, _ctloc(mon),
165 _ctloc(abmon), 12);
166 LEGAL_ALT(0);
167 continue;
169 case 'C': /* The century number. */
170 i = 20;
171 bp = conv_num(bp, &i, 0, 99);
173 i = i * 100 - TM_YEAR_BASE;
174 if (split_year)
175 i += tm->tm_year % 100;
176 split_year = 1;
177 tm->tm_year = i;
178 LEGAL_ALT(ALT_E);
179 continue;
181 case 'd': /* The day of month. */
182 case 'e':
183 bp = conv_num(bp, &tm->tm_mday, 1, 31);
184 LEGAL_ALT(ALT_O);
185 continue;
187 case 'k': /* The hour (24-hour clock representation). */
188 LEGAL_ALT(0);
189 /* FALLTHROUGH */
190 case 'H':
191 bp = conv_num(bp, &tm->tm_hour, 0, 23);
192 LEGAL_ALT(ALT_O);
193 continue;
195 case 'l': /* The hour (12-hour clock representation). */
196 LEGAL_ALT(0);
197 /* FALLTHROUGH */
198 case 'I':
199 bp = conv_num(bp, &tm->tm_hour, 1, 12);
200 if (tm->tm_hour == 12)
201 tm->tm_hour = 0;
202 LEGAL_ALT(ALT_O);
203 continue;
205 case 'j': /* The day of year. */
206 i = 1;
207 bp = conv_num(bp, &i, 1, 366);
208 tm->tm_yday = i - 1;
209 LEGAL_ALT(0);
210 continue;
212 case 'M': /* The minute. */
213 bp = conv_num(bp, &tm->tm_min, 0, 59);
214 LEGAL_ALT(ALT_O);
215 continue;
217 case 'm': /* The month. */
218 i = 1;
219 bp = conv_num(bp, &i, 1, 12);
220 tm->tm_mon = i - 1;
221 LEGAL_ALT(ALT_O);
222 continue;
224 case 'p': /* The locale's equivalent of AM/PM. */
225 bp = find_string(bp, &i, _ctloc(am_pm), NULL, 2);
226 if (tm->tm_hour > 11)
227 return NULL;
228 tm->tm_hour += i * 12;
229 LEGAL_ALT(0);
230 continue;
232 case 'S': /* The seconds. */
233 bp = conv_num(bp, &tm->tm_sec, 0, 61);
234 LEGAL_ALT(ALT_O);
235 continue;
237 case 'U': /* The week of year, beginning on sunday. */
238 case 'W': /* The week of year, beginning on monday. */
240 * XXX This is bogus, as we can not assume any valid
241 * information present in the tm structure at this
242 * point to calculate a real value, so just check the
243 * range for now.
245 bp = conv_num(bp, &i, 0, 53);
246 LEGAL_ALT(ALT_O);
247 continue;
249 case 'w': /* The day of week, beginning on sunday. */
250 bp = conv_num(bp, &tm->tm_wday, 0, 6);
251 LEGAL_ALT(ALT_O);
252 continue;
254 case 'u': /* The day of week, monday = 1. */
255 bp = conv_num(bp, &i, 1, 7);
256 tm->tm_wday = i % 7;
257 LEGAL_ALT(ALT_O);
258 continue;
260 case 'g': /* The year corresponding to the ISO week
261 * number but without the century.
263 bp = conv_num(bp, &i, 0, 99);
264 continue;
266 case 'G': /* The year corresponding to the ISO week
267 * number with century.
270 bp++;
271 while (isdigit(*bp));
272 continue;
274 case 'V': /* The ISO 8601:1988 week number as decimal */
275 bp = conv_num(bp, &i, 0, 53);
276 continue;
278 case 'Y': /* The year. */
279 i = TM_YEAR_BASE; /* just for data sanity... */
280 bp = conv_num(bp, &i, 0, 9999);
281 tm->tm_year = i - TM_YEAR_BASE;
282 LEGAL_ALT(ALT_E);
283 continue;
285 case 'y': /* The year within 100 years of the epoch. */
286 /* LEGAL_ALT(ALT_E | ALT_O); */
287 bp = conv_num(bp, &i, 0, 99);
289 if (split_year)
290 /* preserve century */
291 i += (tm->tm_year / 100) * 100;
292 else {
293 split_year = 1;
294 if (i <= 68)
295 i = i + 2000 - TM_YEAR_BASE;
296 else
297 i = i + 1900 - TM_YEAR_BASE;
299 tm->tm_year = i;
300 continue;
302 case 'Z':
303 tzset();
304 if (strncmp((const char *)bp, gmt, 3) == 0) {
305 tm->tm_isdst = 0;
306 #ifdef TM_GMTOFF
307 tm->TM_GMTOFF = 0;
308 #endif
309 #ifdef TM_ZONE
310 tm->TM_ZONE = gmt;
311 #endif
312 bp += 3;
313 } else {
314 const unsigned char *ep;
316 ep = find_string(bp, &i,
317 (const char * const *)tzname,
318 NULL, 2);
319 if (ep != NULL) {
320 tm->tm_isdst = i;
321 #ifdef TM_GMTOFF
322 tm->TM_GMTOFF = -(timezone);
323 #endif
324 #ifdef TM_ZONE
325 tm->TM_ZONE = tzname[i];
326 #endif
328 bp = ep;
330 continue;
332 case 'z':
334 * We recognize all ISO 8601 formats:
335 * Z = Zulu time/UTC
336 * [+-]hhmm
337 * [+-]hh:mm
338 * [+-]hh
340 while (isspace(*bp))
341 bp++;
343 switch (*bp++) {
344 case 'Z':
345 tm->tm_isdst = 0;
346 #ifdef TM_GMTOFF
347 tm->TM_GMTOFF = 0;
348 #endif
349 #ifdef TM_ZONE
350 tm->TM_ZONE = utc;
351 #endif
352 continue;
353 case '+':
354 neg = 0;
355 break;
356 case '-':
357 neg = 1;
358 break;
359 default:
360 return NULL;
362 offs = 0;
363 for (i = 0; i < 4; ) {
364 if (isdigit(*bp)) {
365 offs = offs * 10 + (*bp++ - '0');
366 i++;
367 continue;
369 if (i == 2 && *bp == ':') {
370 bp++;
371 continue;
373 break;
375 switch (i) {
376 case 2:
377 offs *= 100;
378 break;
379 case 4:
380 i = offs % 100;
381 if (i >= 60)
382 return NULL;
383 /* Convert minutes into decimal */
384 offs = (offs / 100) * 100 + (i * 50) / 30;
385 break;
386 default:
387 return NULL;
389 if (neg)
390 offs = -offs;
391 tm->tm_isdst = 0; /* XXX */
392 #ifdef TM_GMTOFF
393 tm->TM_GMTOFF = offs;
394 #endif
395 #ifdef TM_ZONE
396 tm->TM_ZONE = NULL; /* XXX */
397 #endif
398 continue;
401 * Miscellaneous conversions.
403 case 'n': /* Any kind of white-space. */
404 case 't':
405 while (isspace(*bp))
406 bp++;
407 LEGAL_ALT(0);
408 continue;
411 default: /* Unknown/unsupported conversion. */
412 return NULL;
416 return __DECONST(char *, bp);
420 static const u_char *
421 conv_num(const unsigned char *buf, int *dest, uint llim, uint ulim)
423 uint result = 0;
424 unsigned char ch;
426 /* The limit also determines the number of valid digits. */
427 uint rulim = ulim;
429 ch = *buf;
430 if (ch < '0' || ch > '9')
431 return NULL;
433 do {
434 result *= 10;
435 result += ch - '0';
436 rulim /= 10;
437 ch = *++buf;
438 } while ((result * 10 <= ulim) && rulim && ch >= '0' && ch <= '9');
440 if (result < llim || result > ulim)
441 return NULL;
443 *dest = result;
444 return buf;
447 static const u_char *
448 find_string(const u_char *bp, int *tgt, const char * const *n1,
449 const char * const *n2, int c)
451 int i;
452 unsigned int len;
454 /* check full name - then abbreviated ones */
455 for (; n1 != NULL; n1 = n2, n2 = NULL) {
456 for (i = 0; i < c; i++, n1++) {
457 len = strlen(*n1);
458 if (strncasecmp(*n1, (const char *)bp, len) == 0) {
459 *tgt = i;
460 return bp + len;
465 /* Nothing matched */
466 return NULL;