strptime.c revision 1.18 1 /* $NetBSD: strptime.c,v 1.18 1999/04/29 02:58:30 tv Exp $ */
2
3 /*-
4 * Copyright (c) 1997, 1998 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code was contributed to The NetBSD Foundation by Klaus Klein.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 * 3. All advertising materials mentioning features or use of this software
18 * must display the following acknowledgement:
19 * This product includes software developed by the NetBSD
20 * Foundation, Inc. and its contributors.
21 * 4. Neither the name of The NetBSD Foundation nor the names of its
22 * contributors may be used to endorse or promote products derived
23 * from this software without specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
26 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
27 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
28 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
29 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 * POSSIBILITY OF SUCH DAMAGE.
36 */
37
38 #include <sys/cdefs.h>
39 #if defined(LIBC_SCCS) && !defined(lint)
40 __RCSID("$NetBSD: strptime.c,v 1.18 1999/04/29 02:58:30 tv Exp $");
41 #endif
42
43 #include "namespace.h"
44 #include <sys/localedef.h>
45 #include <ctype.h>
46 #include <locale.h>
47 #include <string.h>
48 #include <time.h>
49 #include <tzfile.h>
50
51 #ifdef __weak_alias
52 __weak_alias(strptime,_strptime);
53 #endif
54
55 #define _ctloc(x) __CONCAT(_CurrentTimeLocale->,x)
56
57 /*
58 * We do not implement alternate representations. However, we always
59 * check whether a given modifier is allowed for a certain conversion.
60 */
61 #define ALT_E 0x01
62 #define ALT_O 0x02
63 #define LEGAL_ALT(x) { if (alt_format & ~(x)) return (0); }
64
65
66 static int conv_num __P((const char **, int *, int, int));
67
68
69 char *
70 strptime(buf, fmt, tm)
71 const char *buf, *fmt;
72 struct tm *tm;
73 {
74 char c;
75 const char *bp;
76 size_t len = 0;
77 int alt_format, i, split_year = 0;
78
79 bp = buf;
80
81 while ((c = *fmt) != '\0') {
82 /* Clear `alternate' modifier prior to new conversion. */
83 alt_format = 0;
84
85 /* Eat up white-space. */
86 if (isspace(c)) {
87 while (isspace(*bp))
88 bp++;
89
90 fmt++;
91 continue;
92 }
93
94 if ((c = *fmt++) != '%')
95 goto literal;
96
97
98 again: switch (c = *fmt++) {
99 case '%': /* "%%" is converted to "%". */
100 literal:
101 if (c != *bp++)
102 return (0);
103 break;
104
105 /*
106 * "Alternative" modifiers. Just set the appropriate flag
107 * and start over again.
108 */
109 case 'E': /* "%E?" alternative conversion modifier. */
110 LEGAL_ALT(0);
111 alt_format |= ALT_E;
112 goto again;
113
114 case 'O': /* "%O?" alternative conversion modifier. */
115 LEGAL_ALT(0);
116 alt_format |= ALT_O;
117 goto again;
118
119 /*
120 * "Complex" conversion rules, implemented through recursion.
121 */
122 case 'c': /* Date and time, using the locale's format. */
123 LEGAL_ALT(ALT_E);
124 if (!(bp = strptime(bp, _ctloc(d_t_fmt), tm)))
125 return (0);
126 break;
127
128 case 'D': /* The date as "%m/%d/%y". */
129 LEGAL_ALT(0);
130 if (!(bp = strptime(bp, "%m/%d/%y", tm)))
131 return (0);
132 break;
133
134 case 'R': /* The time as "%H:%M". */
135 LEGAL_ALT(0);
136 if (!(bp = strptime(bp, "%H:%M", tm)))
137 return (0);
138 break;
139
140 case 'r': /* The time in 12-hour clock representation. */
141 LEGAL_ALT(0);
142 if (!(bp = strptime(bp, _ctloc(t_fmt_ampm), tm)))
143 return (0);
144 break;
145
146 case 'T': /* The time as "%H:%M:%S". */
147 LEGAL_ALT(0);
148 if (!(bp = strptime(bp, "%H:%M:%S", tm)))
149 return (0);
150 break;
151
152 case 'X': /* The time, using the locale's format. */
153 LEGAL_ALT(ALT_E);
154 if (!(bp = strptime(bp, _ctloc(t_fmt), tm)))
155 return (0);
156 break;
157
158 case 'x': /* The date, using the locale's format. */
159 LEGAL_ALT(ALT_E);
160 if (!(bp = strptime(bp, _ctloc(d_fmt), tm)))
161 return (0);
162 break;
163
164 /*
165 * "Elementary" conversion rules.
166 */
167 case 'A': /* The day of week, using the locale's form. */
168 case 'a':
169 LEGAL_ALT(0);
170 for (i = 0; i < 7; i++) {
171 /* Full name. */
172 len = strlen(_ctloc(day[i]));
173 if (strncasecmp(_ctloc(day[i]), bp, len) == 0)
174 break;
175
176 /* Abbreviated name. */
177 len = strlen(_ctloc(abday[i]));
178 if (strncasecmp(_ctloc(abday[i]), bp, len) == 0)
179 break;
180 }
181
182 /* Nothing matched. */
183 if (i == 7)
184 return (0);
185
186 tm->tm_wday = i;
187 bp += len;
188 break;
189
190 case 'B': /* The month, using the locale's form. */
191 case 'b':
192 case 'h':
193 LEGAL_ALT(0);
194 for (i = 0; i < 12; i++) {
195 /* Full name. */
196 len = strlen(_ctloc(mon[i]));
197 if (strncasecmp(_ctloc(mon[i]), bp, len) == 0)
198 break;
199
200 /* Abbreviated name. */
201 len = strlen(_ctloc(abmon[i]));
202 if (strncasecmp(_ctloc(abmon[i]), bp, len) == 0)
203 break;
204 }
205
206 /* Nothing matched. */
207 if (i == 12)
208 return (0);
209
210 tm->tm_mon = i;
211 bp += len;
212 break;
213
214 case 'C': /* The century number. */
215 LEGAL_ALT(ALT_E);
216 if (!(conv_num(&bp, &i, 0, 99)))
217 return (0);
218
219 if (split_year) {
220 tm->tm_year = (tm->tm_year % 100) + (i * 100);
221 } else {
222 tm->tm_year = i * 100;
223 split_year = 1;
224 }
225 break;
226
227 case 'd': /* The day of month. */
228 case 'e':
229 LEGAL_ALT(ALT_O);
230 if (!(conv_num(&bp, &tm->tm_mday, 1, 31)))
231 return (0);
232 break;
233
234 case 'k': /* The hour (24-hour clock representation). */
235 LEGAL_ALT(0);
236 /* FALLTHROUGH */
237 case 'H':
238 LEGAL_ALT(ALT_O);
239 if (!(conv_num(&bp, &tm->tm_hour, 0, 23)))
240 return (0);
241 break;
242
243 case 'l': /* The hour (12-hour clock representation). */
244 LEGAL_ALT(0);
245 /* FALLTHROUGH */
246 case 'I':
247 LEGAL_ALT(ALT_O);
248 if (!(conv_num(&bp, &tm->tm_hour, 1, 12)))
249 return (0);
250 if (tm->tm_hour == 12)
251 tm->tm_hour = 0;
252 break;
253
254 case 'j': /* The day of year. */
255 LEGAL_ALT(0);
256 if (!(conv_num(&bp, &i, 1, 366)))
257 return (0);
258 tm->tm_yday = i - 1;
259 break;
260
261 case 'M': /* The minute. */
262 LEGAL_ALT(ALT_O);
263 if (!(conv_num(&bp, &tm->tm_min, 0, 59)))
264 return (0);
265 break;
266
267 case 'm': /* The month. */
268 LEGAL_ALT(ALT_O);
269 if (!(conv_num(&bp, &i, 1, 12)))
270 return (0);
271 tm->tm_mon = i - 1;
272 break;
273
274 case 'p': /* The locale's equivalent of AM/PM. */
275 LEGAL_ALT(0);
276 /* AM? */
277 if (strcasecmp(_ctloc(am_pm[0]), bp) == 0) {
278 if (tm->tm_hour > 11)
279 return (0);
280
281 bp += strlen(_ctloc(am_pm[0]));
282 break;
283 }
284 /* PM? */
285 else if (strcasecmp(_ctloc(am_pm[1]), bp) == 0) {
286 if (tm->tm_hour > 11)
287 return (0);
288
289 tm->tm_hour += 12;
290 bp += strlen(_ctloc(am_pm[1]));
291 break;
292 }
293
294 /* Nothing matched. */
295 return (0);
296
297 case 'S': /* The seconds. */
298 LEGAL_ALT(ALT_O);
299 if (!(conv_num(&bp, &tm->tm_sec, 0, 61)))
300 return (0);
301 break;
302
303 case 'U': /* The week of year, beginning on sunday. */
304 case 'W': /* The week of year, beginning on monday. */
305 LEGAL_ALT(ALT_O);
306 /*
307 * XXX This is bogus, as we can not assume any valid
308 * information present in the tm structure at this
309 * point to calculate a real value, so just check the
310 * range for now.
311 */
312 if (!(conv_num(&bp, &i, 0, 53)))
313 return (0);
314 break;
315
316 case 'w': /* The day of week, beginning on sunday. */
317 LEGAL_ALT(ALT_O);
318 if (!(conv_num(&bp, &tm->tm_wday, 0, 6)))
319 return (0);
320 break;
321
322 case 'Y': /* The year. */
323 LEGAL_ALT(ALT_E);
324 if (!(conv_num(&bp, &i, 0, 9999)))
325 return (0);
326
327 tm->tm_year = i - TM_YEAR_BASE;
328 break;
329
330 case 'y': /* The year within 100 years of the epoch. */
331 LEGAL_ALT(ALT_E | ALT_O);
332 if (!(conv_num(&bp, &i, 0, 99)))
333 return (0);
334
335 if (split_year) {
336 tm->tm_year = ((tm->tm_year / 100) * 100) + i;
337 break;
338 }
339 split_year = 1;
340 if (i <= 68)
341 tm->tm_year = i + 2000 - TM_YEAR_BASE;
342 else
343 tm->tm_year = i + 1900 - TM_YEAR_BASE;
344 break;
345
346 /*
347 * Miscellaneous conversions.
348 */
349 case 'n': /* Any kind of white-space. */
350 case 't':
351 LEGAL_ALT(0);
352 while (isspace(*bp))
353 bp++;
354 break;
355
356
357 default: /* Unknown/unsupported conversion. */
358 return (0);
359 }
360
361
362 }
363
364 /* LINTED functional specification */
365 return ((char *)bp);
366 }
367
368
369 static int
370 conv_num(buf, dest, llim, ulim)
371 const char **buf;
372 int *dest;
373 int llim, ulim;
374 {
375 int result = 0;
376
377 /* The limit also determines the number of valid digits. */
378 int rulim = ulim;
379
380 if (**buf < '0' || **buf > '9')
381 return (0);
382
383 do {
384 result *= 10;
385 result += *(*buf)++ - '0';
386 rulim /= 10;
387 } while ((result * 10 <= ulim) && rulim && **buf >= '0' && **buf <= '9');
388
389 if (result < llim || result > ulim)
390 return (0);
391
392 *dest = result;
393 return (1);
394 }
395