]>
Commit | Line | Data |
---|---|---|
974e3884 A |
1 | /*- |
2 | * Copyright (c) 2014 Gary Mills | |
3 | * Copyright 2011, Nexenta Systems, Inc. All rights reserved. | |
5b2abdfb A |
4 | * Copyright (c) 1994 Powerdog Industries. All rights reserved. |
5 | * | |
974e3884 A |
6 | * Copyright (c) 2011 The FreeBSD Foundation |
7 | * All rights reserved. | |
8 | * Portions of this software were developed by David Chisnall | |
9 | * under sponsorship from the FreeBSD Foundation. | |
10 | * | |
5b2abdfb A |
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 | |
18 | * in the documentation and/or other materials provided with the | |
19 | * distribution. | |
5b2abdfb A |
20 | * |
21 | * THIS SOFTWARE IS PROVIDED BY POWERDOG INDUSTRIES ``AS IS'' AND ANY | |
22 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
24 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE POWERDOG INDUSTRIES BE | |
25 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
26 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
27 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR | |
28 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | |
29 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | |
30 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, | |
31 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
974e3884 A |
32 | * |
33 | * The views and conclusions contained in the software and documentation | |
34 | * are those of the authors and should not be interpreted as representing | |
35 | * official policies, either expressed or implied, of Powerdog Industries. | |
5b2abdfb A |
36 | */ |
37 | ||
9385eb3d | 38 | #include <sys/cdefs.h> |
5b2abdfb A |
39 | #ifndef lint |
40 | #ifndef NOID | |
9385eb3d | 41 | static char copyright[] __unused = |
5b2abdfb | 42 | "@(#) Copyright (c) 1994 Powerdog Industries. All rights reserved."; |
9385eb3d | 43 | static char sccsid[] __unused = "@(#)strptime.c 0.1 (Powerdog) 94/03/27"; |
5b2abdfb A |
44 | #endif /* !defined NOID */ |
45 | #endif /* not lint */ | |
974e3884 | 46 | __FBSDID("$FreeBSD$"); |
5b2abdfb | 47 | |
ad3c9f2a A |
48 | #include "xlocale_private.h" |
49 | ||
9385eb3d | 50 | #include "namespace.h" |
5b2abdfb A |
51 | #include <time.h> |
52 | #include <ctype.h> | |
3d9156a7 | 53 | #include <errno.h> |
ad3c9f2a | 54 | #include <stdio.h> |
5b2abdfb A |
55 | #include <stdlib.h> |
56 | #include <string.h> | |
5b2abdfb | 57 | #include <pthread.h> |
9385eb3d A |
58 | #include "un-namespace.h" |
59 | #include "libc_private.h" | |
5b2abdfb | 60 | #include "timelocal.h" |
974e3884 | 61 | #include "tzfile.h" |
5b2abdfb | 62 | |
ad3c9f2a | 63 | time_t _mktime(struct tm *, const char *); |
5b2abdfb | 64 | |
974e3884 | 65 | #define asizeof(a) (sizeof(a) / sizeof((a)[0])) |
5b2abdfb | 66 | |
ad3c9f2a | 67 | enum {CONVERT_NONE, CONVERT_GMT, CONVERT_ZONE}; |
974e3884 A |
68 | enum week_kind { WEEK_U = 'U', WEEK_V = 'V', WEEK_W = 'W'}; |
69 | ||
70 | #define _strptime(b,f,t,c,l) _strptime0(b,f,t,c,l,FLAG_NONE,0,WEEK_U) | |
ad3c9f2a | 71 | |
974e3884 A |
72 | #define FLAG_NONE 0x01 |
73 | #define FLAG_YEAR 0x02 | |
74 | #define FLAG_MONTH 0x04 | |
75 | #define FLAG_YDAY 0x08 | |
76 | #define FLAG_MDAY 0x10 | |
77 | #define FLAG_WDAY 0x20 | |
78 | #define FLAG_WEEK 0x40 | |
79 | #define FLAG_CENTURY 0x100 | |
80 | #define FLAG_YEAR_IN_CENTURY 0x200 | |
6465356a | 81 | |
974e3884 A |
82 | /* |
83 | * Calculate the week day of the first day of a year. Valid for | |
84 | * the Gregorian calendar, which began Sept 14, 1752 in the UK | |
85 | * and its colonies. Ref: | |
86 | * http://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week | |
87 | */ | |
6465356a A |
88 | |
89 | static int | |
974e3884 | 90 | first_wday_of(int year) |
6465356a | 91 | { |
974e3884 A |
92 | return (((2 * (3 - (year / 100) % 4)) + (year % 100) + |
93 | ((year % 100) / 4) + (isleap(year) ? 6 : 0) + 1) % 7); | |
6465356a | 94 | } |
ad3c9f2a | 95 | |
507116e3 A |
96 | static inline bool is_plus(char c) { |
97 | return c == '+'; | |
98 | } | |
99 | ||
100 | static inline bool is_minus(char c) { | |
101 | return c == '-'; | |
102 | } | |
103 | ||
104 | static inline bool is_zero(char c) { | |
105 | return c == '0'; | |
106 | } | |
107 | ||
5b2abdfb | 108 | static char * |
974e3884 | 109 | _strptime0(const char *buf, const char *fmt, struct tm *tm, int *convp, locale_t locale, int flags, int week_number, enum week_kind week_kind) |
5b2abdfb A |
110 | { |
111 | char c; | |
112 | const char *ptr; | |
974e3884 A |
113 | int wday_offset; |
114 | int i, len; | |
507116e3 | 115 | bool negative; |
5b2abdfb | 116 | int Ealternative, Oalternative; |
974e3884 A |
117 | const struct lc_time_T *tptr = __get_current_time_locale(locale); |
118 | static int start_of_month[2][13] = { | |
119 | {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}, | |
120 | {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366} | |
121 | }; | |
5b2abdfb A |
122 | |
123 | ptr = fmt; | |
124 | while (*ptr != 0) { | |
507116e3 | 125 | int field_width = 0; |
5b2abdfb A |
126 | c = *ptr++; |
127 | ||
128 | if (c != '%') { | |
974e3884 A |
129 | if (isspace_l((unsigned char)c, locale)) |
130 | while (*buf != 0 && | |
131 | isspace_l((unsigned char)*buf, locale)) | |
5b2abdfb A |
132 | buf++; |
133 | else if (c != *buf++) | |
974e3884 | 134 | return (NULL); |
5b2abdfb A |
135 | continue; |
136 | } | |
137 | ||
138 | Ealternative = 0; | |
139 | Oalternative = 0; | |
140 | label: | |
141 | c = *ptr++; | |
507116e3 A |
142 | if (is_zero(c)) { |
143 | // Leading '0' is to be ignored. | |
144 | c = *ptr++; | |
145 | } else if (is_plus(c)) { | |
146 | // POSIX sats leading '+' should be ignored, but FreeBSD interprets | |
147 | // "%+" to mean locale-specific date format. Try to handle both by | |
148 | // checking the next character. | |
149 | char next = *ptr; | |
150 | if (next != '\0' && next != '%' && !isspace_l(next, locale)) { | |
151 | // Use POSIX interpretation. | |
152 | c = *ptr++; | |
153 | } | |
154 | } | |
155 | ||
156 | if (isdigit_l(c, locale)) { | |
157 | // Field width | |
158 | field_width = c - '0'; | |
159 | while (*ptr != '\0' && isdigit_l(*ptr, locale)) { | |
160 | field_width *= 10; | |
161 | field_width += *ptr++ - '0'; | |
162 | } | |
163 | c = *ptr++; | |
164 | } | |
5b2abdfb | 165 | switch (c) { |
5b2abdfb A |
166 | case '%': |
167 | if (*buf++ != '%') | |
974e3884 | 168 | return (NULL); |
5b2abdfb A |
169 | break; |
170 | ||
171 | case '+': | |
974e3884 A |
172 | buf = _strptime(buf, tptr->date_fmt, tm, convp, locale); |
173 | if (buf == NULL) | |
174 | return (NULL); | |
175 | flags |= FLAG_WDAY | FLAG_MONTH | FLAG_MDAY | FLAG_YEAR; | |
5b2abdfb A |
176 | break; |
177 | ||
178 | case 'C': | |
507116e3 | 179 | if (!isdigit_l((unsigned char)*buf, locale) && !is_plus(*buf) && !is_minus(*buf)) |
974e3884 | 180 | return (NULL); |
5b2abdfb A |
181 | |
182 | /* XXX This will break for 3-digit centuries. */ | |
507116e3 A |
183 | negative = false; |
184 | len = field_width ? field_width : 2; | |
185 | if (is_plus(*buf)) { | |
186 | len--; | |
187 | buf++; | |
188 | } else if (is_minus(*buf)) { | |
189 | len--; | |
190 | negative = true; | |
191 | buf++; | |
192 | } | |
974e3884 A |
193 | for (i = 0; len && *buf != 0 && |
194 | isdigit_l((unsigned char)*buf, locale); buf++) { | |
5b2abdfb A |
195 | i *= 10; |
196 | i += *buf - '0'; | |
197 | len--; | |
198 | } | |
507116e3 A |
199 | |
200 | if (negative) { | |
201 | i = -i; | |
202 | } | |
974e3884 A |
203 | |
204 | if (flags & FLAG_YEAR_IN_CENTURY) { | |
205 | tm->tm_year = i * 100 + (tm->tm_year % 100) - TM_YEAR_BASE; | |
206 | flags &= ~FLAG_YEAR_IN_CENTURY; | |
207 | } else { | |
208 | tm->tm_year = i * 100 - TM_YEAR_BASE; | |
209 | flags |= FLAG_YEAR; | |
210 | flags |= FLAG_CENTURY; | |
211 | } | |
5b2abdfb | 212 | |
5b2abdfb A |
213 | break; |
214 | ||
215 | case 'c': | |
974e3884 A |
216 | buf = _strptime(buf, tptr->c_fmt, tm, convp, locale); |
217 | if (buf == NULL) | |
218 | return (NULL); | |
219 | flags |= FLAG_WDAY | FLAG_MONTH | FLAG_MDAY | FLAG_YEAR; | |
220 | flags &= ~(FLAG_CENTURY | FLAG_YEAR_IN_CENTURY); | |
5b2abdfb A |
221 | break; |
222 | ||
223 | case 'D': | |
974e3884 A |
224 | buf = _strptime(buf, "%m/%d/%y", tm, convp, locale); |
225 | if (buf == NULL) | |
226 | return (NULL); | |
227 | flags |= FLAG_MONTH | FLAG_MDAY | FLAG_YEAR; | |
228 | flags &= ~(FLAG_CENTURY | FLAG_YEAR_IN_CENTURY); | |
5b2abdfb A |
229 | break; |
230 | ||
231 | case 'E': | |
232 | if (Ealternative || Oalternative) | |
233 | break; | |
234 | Ealternative++; | |
974e3884 | 235 | if (*ptr == '%') return (NULL); |
5b2abdfb A |
236 | goto label; |
237 | ||
238 | case 'O': | |
239 | if (Ealternative || Oalternative) | |
240 | break; | |
241 | Oalternative++; | |
974e3884 | 242 | if (*ptr == '%') return (NULL); |
5b2abdfb A |
243 | goto label; |
244 | ||
245 | case 'F': | |
974e3884 A |
246 | buf = _strptime(buf, "%Y-%m-%d", tm, convp, locale); |
247 | if (buf == NULL) | |
248 | return (NULL); | |
249 | flags |= FLAG_MONTH | FLAG_MDAY | FLAG_YEAR; | |
250 | flags &= ~(FLAG_CENTURY | FLAG_YEAR_IN_CENTURY); | |
5b2abdfb A |
251 | break; |
252 | ||
253 | case 'R': | |
974e3884 A |
254 | buf = _strptime(buf, "%H:%M", tm, convp, locale); |
255 | if (buf == NULL) | |
256 | return (NULL); | |
5b2abdfb A |
257 | break; |
258 | ||
259 | case 'r': | |
974e3884 A |
260 | buf = _strptime(buf, tptr->ampm_fmt, tm, convp, locale); |
261 | if (buf == NULL) | |
262 | return (NULL); | |
ad3c9f2a A |
263 | break; |
264 | ||
5b2abdfb | 265 | case 'T': |
974e3884 A |
266 | buf = _strptime(buf, "%H:%M:%S", tm, convp, locale); |
267 | if (buf == NULL) | |
268 | return (NULL); | |
5b2abdfb A |
269 | break; |
270 | ||
271 | case 'X': | |
974e3884 A |
272 | buf = _strptime(buf, tptr->X_fmt, tm, convp, locale); |
273 | if (buf == NULL) | |
274 | return (NULL); | |
5b2abdfb A |
275 | break; |
276 | ||
277 | case 'x': | |
974e3884 A |
278 | buf = _strptime(buf, tptr->x_fmt, tm, convp, locale); |
279 | if (buf == NULL) | |
280 | return (NULL); | |
281 | flags |= FLAG_MONTH | FLAG_MDAY | FLAG_YEAR; | |
282 | flags &= ~(FLAG_CENTURY | FLAG_YEAR_IN_CENTURY); | |
5b2abdfb A |
283 | break; |
284 | ||
285 | case 'j': | |
974e3884 A |
286 | if (!isdigit_l((unsigned char)*buf, locale)) |
287 | return (NULL); | |
5b2abdfb | 288 | |
507116e3 | 289 | len = field_width ? field_width : 3; |
974e3884 A |
290 | for (i = 0; len && *buf != 0 && |
291 | isdigit_l((unsigned char)*buf, locale); buf++){ | |
5b2abdfb A |
292 | i *= 10; |
293 | i += *buf - '0'; | |
294 | len--; | |
295 | } | |
296 | if (i < 1 || i > 366) | |
974e3884 A |
297 | return (NULL); |
298 | ||
299 | tm->tm_yday = i - 1; | |
300 | flags |= FLAG_YDAY; | |
5b2abdfb | 301 | |
5b2abdfb A |
302 | break; |
303 | ||
304 | case 'M': | |
305 | case 'S': | |
974e3884 A |
306 | if (*buf == 0 || |
307 | isspace_l((unsigned char)*buf, locale)) | |
5b2abdfb A |
308 | break; |
309 | ||
974e3884 A |
310 | if (!isdigit_l((unsigned char)*buf, locale)) |
311 | return (NULL); | |
5b2abdfb | 312 | |
507116e3 | 313 | len = field_width ? field_width : 2; |
974e3884 A |
314 | for (i = 0; len && *buf != 0 && |
315 | isdigit_l((unsigned char)*buf, locale); buf++){ | |
5b2abdfb A |
316 | i *= 10; |
317 | i += *buf - '0'; | |
318 | len--; | |
319 | } | |
320 | ||
321 | if (c == 'M') { | |
322 | if (i > 59) | |
974e3884 | 323 | return (NULL); |
5b2abdfb A |
324 | tm->tm_min = i; |
325 | } else { | |
326 | if (i > 60) | |
974e3884 | 327 | return (NULL); |
5b2abdfb A |
328 | tm->tm_sec = i; |
329 | } | |
330 | ||
5b2abdfb A |
331 | break; |
332 | ||
333 | case 'H': | |
334 | case 'I': | |
335 | case 'k': | |
336 | case 'l': | |
337 | /* | |
338 | * Of these, %l is the only specifier explicitly | |
339 | * documented as not being zero-padded. However, | |
340 | * there is no harm in allowing zero-padding. | |
341 | * | |
342 | * XXX The %l specifier may gobble one too many | |
343 | * digits if used incorrectly. | |
344 | */ | |
974e3884 A |
345 | if (!isdigit_l((unsigned char)*buf, locale)) |
346 | return (NULL); | |
5b2abdfb | 347 | |
507116e3 | 348 | len = field_width ? field_width : 2; |
974e3884 A |
349 | for (i = 0; len && *buf != 0 && |
350 | isdigit_l((unsigned char)*buf, locale); buf++) { | |
5b2abdfb A |
351 | i *= 10; |
352 | i += *buf - '0'; | |
353 | len--; | |
354 | } | |
355 | if (c == 'H' || c == 'k') { | |
356 | if (i > 23) | |
974e3884 | 357 | return (NULL); |
5b2abdfb | 358 | } else if (i > 12) |
974e3884 | 359 | return (NULL); |
5b2abdfb A |
360 | |
361 | tm->tm_hour = i; | |
362 | ||
5b2abdfb A |
363 | break; |
364 | ||
365 | case 'p': | |
366 | /* | |
367 | * XXX This is bogus if parsed before hour-related | |
368 | * specifiers. | |
369 | */ | |
b061a43b A |
370 | if (tm->tm_hour > 12) |
371 | return (NULL); | |
372 | ||
507116e3 | 373 | len = (int)strlen(tptr->am); |
974e3884 | 374 | if (strncasecmp_l(buf, tptr->am, len, locale) == 0) { |
5b2abdfb A |
375 | if (tm->tm_hour == 12) |
376 | tm->tm_hour = 0; | |
377 | buf += len; | |
378 | break; | |
379 | } | |
380 | ||
507116e3 | 381 | len = (int)strlen(tptr->pm); |
974e3884 | 382 | if (strncasecmp_l(buf, tptr->pm, len, locale) == 0) { |
5b2abdfb A |
383 | if (tm->tm_hour != 12) |
384 | tm->tm_hour += 12; | |
385 | buf += len; | |
386 | break; | |
387 | } | |
388 | ||
974e3884 | 389 | return (NULL); |
5b2abdfb A |
390 | |
391 | case 'A': | |
392 | case 'a': | |
9385eb3d | 393 | for (i = 0; i < asizeof(tptr->weekday); i++) { |
507116e3 | 394 | len = (int)strlen(tptr->weekday[i]); |
ad3c9f2a | 395 | if (strncasecmp_l(buf, tptr->weekday[i], |
974e3884 | 396 | len, locale) == 0) |
9385eb3d | 397 | break; |
507116e3 | 398 | len = (int)strlen(tptr->wday[i]); |
ad3c9f2a | 399 | if (strncasecmp_l(buf, tptr->wday[i], |
974e3884 | 400 | len, locale) == 0) |
9385eb3d | 401 | break; |
5b2abdfb | 402 | } |
9385eb3d | 403 | if (i == asizeof(tptr->weekday)) |
974e3884 | 404 | return (NULL); |
5b2abdfb | 405 | |
5b2abdfb | 406 | buf += len; |
974e3884 A |
407 | tm->tm_wday = i; |
408 | flags |= FLAG_WDAY; | |
5b2abdfb A |
409 | break; |
410 | ||
974e3884 A |
411 | case 'U': /* Sunday week */ |
412 | case 'V': /* ISO 8601 week */ | |
413 | case 'W': /* Monday week */ | |
414 | if (!isdigit_l((unsigned char)*buf, locale)) | |
415 | return (NULL); | |
5b2abdfb | 416 | |
507116e3 | 417 | len = field_width ? field_width : 2; |
974e3884 A |
418 | for (i = 0; len && *buf != 0 && |
419 | isdigit_l((unsigned char)*buf, locale); buf++) { | |
5b2abdfb A |
420 | i *= 10; |
421 | i += *buf - '0'; | |
422 | len--; | |
423 | } | |
424 | if (i > 53) | |
974e3884 A |
425 | return (NULL); |
426 | if (c == WEEK_V && i < 1) | |
427 | return (NULL); | |
6465356a | 428 | |
974e3884 A |
429 | week_number = i; |
430 | week_kind = c; | |
431 | flags |= FLAG_WEEK; | |
5b2abdfb | 432 | |
5b2abdfb A |
433 | break; |
434 | ||
974e3884 A |
435 | case 'u': /* [1,7] */ |
436 | case 'w': /* [0,6] */ | |
437 | if (!isdigit_l((unsigned char)*buf, locale)) | |
438 | return (NULL); | |
5b2abdfb A |
439 | |
440 | i = *buf - '0'; | |
b061a43b A |
441 | if (i < 0 || i > 7 || (c == 'u' && i < 1) || |
442 | (c == 'w' && i > 6)) | |
974e3884 | 443 | return (NULL); |
6465356a | 444 | |
b061a43b | 445 | tm->tm_wday = i % 7; |
974e3884 | 446 | flags |= FLAG_WDAY; |
ad3c9f2a | 447 | buf++; |
974e3884 | 448 | |
5b2abdfb A |
449 | break; |
450 | ||
5b2abdfb A |
451 | case 'e': |
452 | /* | |
974e3884 A |
453 | * With %e format, our strftime(3) adds a blank space |
454 | * before single digits. | |
455 | */ | |
456 | if (*buf != 0 && | |
457 | isspace_l((unsigned char)*buf, locale)) | |
458 | buf++; | |
459 | /* FALLTHROUGH */ | |
460 | case 'd': | |
461 | /* | |
462 | * The %e specifier was once explicitly documented as | |
463 | * not being zero-padded but was later changed to | |
464 | * equivalent to %d. There is no harm in allowing | |
5b2abdfb A |
465 | * such padding. |
466 | * | |
467 | * XXX The %e specifier may gobble one too many | |
468 | * digits if used incorrectly. | |
469 | */ | |
ad3c9f2a | 470 | /* Leading space is ok if date is single digit */ |
507116e3 | 471 | len = field_width ? field_width : 2; |
974e3884 A |
472 | if (isspace_l((unsigned char)buf[0], locale) && |
473 | isdigit_l((unsigned char)buf[1], locale) && | |
474 | !isdigit_l((unsigned char)buf[2], locale)) { | |
ad3c9f2a A |
475 | len = 1; |
476 | buf++; | |
477 | } | |
974e3884 A |
478 | if (!isdigit_l((unsigned char)*buf, locale)) |
479 | return (NULL); | |
5b2abdfb | 480 | |
974e3884 A |
481 | for (i = 0; len && *buf != 0 && |
482 | isdigit_l((unsigned char)*buf, locale); buf++) { | |
5b2abdfb A |
483 | i *= 10; |
484 | i += *buf - '0'; | |
485 | len--; | |
486 | } | |
487 | if (i > 31) | |
974e3884 | 488 | return (NULL); |
5b2abdfb A |
489 | |
490 | tm->tm_mday = i; | |
974e3884 | 491 | flags |= FLAG_MDAY; |
5b2abdfb | 492 | |
5b2abdfb A |
493 | break; |
494 | ||
495 | case 'B': | |
496 | case 'b': | |
497 | case 'h': | |
9385eb3d | 498 | for (i = 0; i < asizeof(tptr->month); i++) { |
5b2abdfb A |
499 | if (Oalternative) { |
500 | if (c == 'B') { | |
507116e3 | 501 | len = (int)strlen(tptr->alt_month[i]); |
ad3c9f2a | 502 | if (strncasecmp_l(buf, |
9385eb3d | 503 | tptr->alt_month[i], |
974e3884 | 504 | len, locale) == 0) |
5b2abdfb A |
505 | break; |
506 | } | |
507 | } else { | |
507116e3 | 508 | len = (int)strlen(tptr->month[i]); |
ad3c9f2a | 509 | if (strncasecmp_l(buf, tptr->month[i], |
974e3884 | 510 | len, locale) == 0) |
9385eb3d | 511 | break; |
974e3884 A |
512 | } |
513 | } | |
514 | /* | |
515 | * Try the abbreviated month name if the full name | |
516 | * wasn't found and Oalternative was not requested. | |
517 | */ | |
518 | if (i == asizeof(tptr->month) && !Oalternative) { | |
519 | for (i = 0; i < asizeof(tptr->month); i++) { | |
507116e3 | 520 | len = (int)strlen(tptr->mon[i]); |
ad3c9f2a | 521 | if (strncasecmp_l(buf, tptr->mon[i], |
974e3884 | 522 | len, locale) == 0) |
9385eb3d | 523 | break; |
5b2abdfb A |
524 | } |
525 | } | |
9385eb3d | 526 | if (i == asizeof(tptr->month)) |
974e3884 | 527 | return (NULL); |
5b2abdfb A |
528 | |
529 | tm->tm_mon = i; | |
530 | buf += len; | |
974e3884 A |
531 | flags |= FLAG_MONTH; |
532 | ||
5b2abdfb A |
533 | break; |
534 | ||
535 | case 'm': | |
974e3884 A |
536 | if (!isdigit_l((unsigned char)*buf, locale)) |
537 | return (NULL); | |
5b2abdfb | 538 | |
507116e3 | 539 | len = field_width ? field_width : 2; |
974e3884 A |
540 | for (i = 0; len && *buf != 0 && |
541 | isdigit_l((unsigned char)*buf, locale); buf++) { | |
5b2abdfb A |
542 | i *= 10; |
543 | i += *buf - '0'; | |
544 | len--; | |
545 | } | |
546 | if (i < 1 || i > 12) | |
974e3884 | 547 | return (NULL); |
5b2abdfb A |
548 | |
549 | tm->tm_mon = i - 1; | |
974e3884 | 550 | flags |= FLAG_MONTH; |
5b2abdfb | 551 | |
5b2abdfb A |
552 | break; |
553 | ||
554 | case 's': | |
555 | { | |
556 | char *cp; | |
3d9156a7 A |
557 | int sverrno; |
558 | long n; | |
5b2abdfb A |
559 | time_t t; |
560 | ||
3d9156a7 A |
561 | sverrno = errno; |
562 | errno = 0; | |
974e3884 | 563 | n = strtol_l(buf, &cp, 10, locale); |
3d9156a7 A |
564 | if (errno == ERANGE || (long)(t = n) != n) { |
565 | errno = sverrno; | |
974e3884 | 566 | return (NULL); |
3d9156a7 A |
567 | } |
568 | errno = sverrno; | |
5b2abdfb | 569 | buf = cp; |
974e3884 A |
570 | if (gmtime_r(&t, tm) == NULL) |
571 | return (NULL); | |
ad3c9f2a | 572 | *convp = CONVERT_GMT; |
974e3884 A |
573 | flags |= FLAG_YDAY | FLAG_WDAY | FLAG_MONTH | |
574 | FLAG_MDAY | FLAG_YEAR; | |
575 | flags &= ~(FLAG_CENTURY | FLAG_YEAR_IN_CENTURY); | |
5b2abdfb A |
576 | } |
577 | break; | |
578 | ||
579 | case 'Y': | |
580 | case 'y': | |
974e3884 A |
581 | if (*buf == 0 || |
582 | isspace_l((unsigned char)*buf, locale)) | |
5b2abdfb A |
583 | break; |
584 | ||
507116e3 A |
585 | if (!isdigit_l((unsigned char)*buf, locale) && !is_plus(*buf) |
586 | && !is_minus(*buf)) | |
974e3884 | 587 | return (NULL); |
5b2abdfb | 588 | |
507116e3 A |
589 | len = field_width ? field_width : ((c == 'Y') ? 4 : 2); |
590 | negative = false; | |
591 | if (is_plus(*buf)) { | |
592 | len--; | |
593 | buf++; | |
594 | } else if (is_minus(*buf)) { | |
595 | len--; | |
596 | buf++; | |
597 | negative = true; | |
598 | } | |
599 | for (i = 0; len && *buf != 0 && | |
600 | isdigit_l((unsigned char)*buf, locale); buf++) { | |
601 | i *= 10; | |
602 | i += *buf - '0'; | |
603 | len--; | |
ad3c9f2a | 604 | } |
5b2abdfb | 605 | |
974e3884 A |
606 | if (i < 0) |
607 | return (NULL); | |
608 | ||
507116e3 A |
609 | if (negative) { |
610 | i = -i; | |
611 | } | |
612 | ||
974e3884 A |
613 | if (c == 'Y'){ |
614 | i -= TM_YEAR_BASE; | |
615 | } else if (c == 'y' && flags & FLAG_CENTURY) { | |
616 | i = tm->tm_year + (i % 100); | |
617 | flags &= ~FLAG_CENTURY; | |
618 | } else if (c == 'y'){ | |
619 | if (i < 69) i += 100; | |
620 | flags |= FLAG_YEAR_IN_CENTURY; | |
621 | } | |
5b2abdfb | 622 | |
974e3884 A |
623 | tm->tm_year = i; |
624 | flags |= FLAG_YEAR; | |
625 | if (c == 'Y'){ | |
626 | flags &= ~(FLAG_CENTURY | FLAG_YEAR_IN_CENTURY); | |
6465356a A |
627 | } |
628 | ||
5b2abdfb A |
629 | break; |
630 | ||
631 | case 'Z': | |
632 | { | |
974e3884 A |
633 | const char *cp; |
634 | size_t tzlen, len; | |
5b2abdfb | 635 | |
974e3884 A |
636 | for (cp = buf; *cp && |
637 | isupper_l((unsigned char)*cp, locale); ++cp) { | |
638 | /*empty*/ | |
639 | } | |
507116e3 | 640 | len = field_width ? field_width : cp - buf; |
974e3884 A |
641 | if (len == 3 && strncmp(buf, "GMT", 3) == 0) { |
642 | *convp = CONVERT_GMT; | |
643 | buf += len; | |
644 | break; | |
645 | } | |
646 | ||
647 | tzset(); | |
648 | tzlen = strlen(tzname[0]); | |
649 | if (len == tzlen && strncmp(buf, tzname[0], tzlen) == 0) { | |
650 | tm->tm_isdst = 0; | |
651 | buf += len; | |
652 | break; | |
653 | } | |
654 | tzlen = strlen(tzname[1]); | |
655 | if (len == tzlen && strncmp(buf, tzname[1], tzlen) == 0) { | |
656 | tm->tm_isdst = 1; | |
657 | buf += len; | |
658 | break; | |
659 | } | |
660 | return (NULL); | |
5b2abdfb | 661 | } |
1f2f436a A |
662 | |
663 | case 'z': | |
664 | { | |
974e3884 A |
665 | char sign; |
666 | int hr, min; | |
667 | if ((buf[0] != '+' && buf[0] != '-') | |
668 | || !isdigit_l((unsigned char)buf[1], locale) | |
669 | || !isdigit_l((unsigned char)buf[2], locale) | |
670 | || !isdigit_l((unsigned char)buf[3], locale) | |
671 | || !isdigit_l((unsigned char)buf[4], locale)) | |
1f2f436a | 672 | return 0; |
974e3884 A |
673 | sscanf(buf, "%c%2d%2d", &sign, &hr, &min); |
674 | *convp = CONVERT_ZONE; | |
675 | tm->tm_gmtoff = 60 * (60 * hr + min); | |
676 | if (sign == '-') | |
677 | tm->tm_gmtoff = -tm->tm_gmtoff; | |
678 | buf += 5; | |
1f2f436a | 679 | } |
974e3884 | 680 | break; |
1f2f436a | 681 | |
974e3884 A |
682 | case 'n': |
683 | case 't': | |
684 | if (!isspace((unsigned char)*buf)) | |
685 | return 0; | |
686 | while (isspace_l((unsigned char)*buf, locale)) | |
687 | buf++; | |
688 | break; | |
689 | ||
690 | default: | |
691 | return (NULL); | |
692 | } | |
693 | } | |
694 | ||
695 | if (!(flags & FLAG_YDAY) && (flags & FLAG_YEAR)) { | |
696 | if ((flags & (FLAG_MONTH | FLAG_MDAY)) == | |
697 | (FLAG_MONTH | FLAG_MDAY)) { | |
698 | tm->tm_yday = start_of_month[isleap(tm->tm_year + | |
699 | TM_YEAR_BASE)][tm->tm_mon] + (tm->tm_mday - 1); | |
700 | flags |= FLAG_YDAY; | |
701 | } else if (flags & FLAG_WEEK){ | |
b061a43b A |
702 | int day_offset = week_kind == WEEK_U ? TM_SUNDAY : TM_MONDAY; |
703 | int fwo = first_wday_of(tm->tm_year + TM_YEAR_BASE); | |
704 | ||
705 | /* No incomplete week (week 0). */ | |
706 | if (week_number == 0 && fwo == day_offset) | |
707 | return (NULL); | |
708 | ||
974e3884 | 709 | if (!(flags & FLAG_WDAY)) { |
b061a43b A |
710 | /* |
711 | * Set the date to the first Sunday (or Monday) | |
712 | * of the specified week of the year. | |
713 | */ | |
714 | tm->tm_wday = day_offset; | |
974e3884 | 715 | flags |= FLAG_WDAY; |
1f2f436a A |
716 | } |
717 | ||
b061a43b A |
718 | /* |
719 | * Start our yday at the first day of the relevant week type. | |
720 | */ | |
721 | int tmpyday = (7 - fwo + day_offset) % 7; | |
974e3884 | 722 | |
b061a43b A |
723 | /* |
724 | * ISO Weeks start counting from the first week with at least | |
725 | * four days. If our first week had that, subtract off a week. | |
726 | */ | |
727 | if (week_kind == WEEK_V && fwo > TM_MONDAY && fwo <= TM_THURSDAY) { | |
728 | tmpyday -= 7; | |
1f2f436a | 729 | } |
b061a43b A |
730 | /* Advance the relevant number of weeks */ |
731 | tmpyday += (week_number - 1) * 7; | |
732 | /* And go to the right day of week */ | |
733 | tmpyday += (tm->tm_wday - day_offset + 7) % 7; | |
734 | ||
735 | /* Impossible yday for incomplete week (week 0). */ | |
736 | if (tmpyday < 0) { | |
737 | if (flags & FLAG_WDAY) | |
738 | return (NULL); | |
739 | tmpyday = 0; | |
974e3884 | 740 | } |
b061a43b | 741 | tm->tm_yday = tmpyday; |
974e3884 | 742 | flags |= FLAG_YDAY; |
5b2abdfb A |
743 | } |
744 | } | |
974e3884 A |
745 | |
746 | if ((flags & (FLAG_YEAR | FLAG_YDAY)) == (FLAG_YEAR | FLAG_YDAY)) { | |
747 | if (!(flags & FLAG_MONTH)) { | |
748 | i = 0; | |
749 | while (tm->tm_yday >= | |
750 | start_of_month[isleap(tm->tm_year + | |
751 | TM_YEAR_BASE)][i]) | |
752 | i++; | |
753 | if (i > 12) { | |
754 | i = 1; | |
755 | tm->tm_yday -= | |
756 | start_of_month[isleap(tm->tm_year + | |
757 | TM_YEAR_BASE)][12]; | |
758 | tm->tm_year++; | |
759 | } | |
760 | tm->tm_mon = i - 1; | |
761 | flags |= FLAG_MONTH; | |
762 | } | |
763 | if (!(flags & FLAG_MDAY)) { | |
764 | tm->tm_mday = tm->tm_yday - | |
765 | start_of_month[isleap(tm->tm_year + TM_YEAR_BASE)] | |
766 | [tm->tm_mon] + 1; | |
767 | flags |= FLAG_MDAY; | |
768 | } | |
769 | if (!(flags & FLAG_WDAY)) { | |
b061a43b A |
770 | wday_offset = first_wday_of(tm->tm_year + TM_YEAR_BASE); |
771 | tm->tm_wday = (wday_offset + tm->tm_yday) % 7; | |
974e3884 A |
772 | flags |= FLAG_WDAY; |
773 | } | |
774 | } | |
775 | ||
776 | return ((char *)buf); | |
5b2abdfb A |
777 | } |
778 | ||
779 | ||
780 | char * | |
9385eb3d | 781 | strptime(const char * __restrict buf, const char * __restrict fmt, |
974e3884 | 782 | struct tm * __restrict tm) |
5b2abdfb | 783 | { |
ad3c9f2a A |
784 | return strptime_l(buf, fmt, tm, __current_locale()); |
785 | } | |
786 | ||
787 | extern time_t timeoff(struct tm *, long); | |
5b2abdfb | 788 | |
ad3c9f2a A |
789 | char * |
790 | strptime_l(const char * __restrict buf, const char * __restrict fmt, | |
791 | struct tm * __restrict tm, locale_t loc) | |
792 | { | |
793 | char *ret; | |
794 | int conv; | |
795 | ||
796 | NORMALIZE_LOCALE(loc); | |
797 | conv = CONVERT_NONE; | |
798 | tm->tm_zone = NULL; | |
799 | ret = _strptime(buf, fmt, tm, &conv, loc); | |
800 | if (ret) { | |
801 | time_t t; | |
802 | ||
803 | switch(conv) { | |
804 | case CONVERT_GMT: | |
805 | t = timegm(tm); | |
806 | localtime_r(&t, tm); | |
807 | break; | |
808 | case CONVERT_ZONE: | |
809 | { | |
810 | long offset = tm->tm_gmtoff; | |
811 | tm->tm_gmtoff = 0; | |
812 | t = timeoff(tm, offset); | |
813 | localtime_r(&t, tm); | |
814 | break; | |
815 | } | |
816 | } | |
5b2abdfb A |
817 | } |
818 | ||
9385eb3d | 819 | return (ret); |
5b2abdfb | 820 | } |