aboutsummaryrefslogtreecommitdiff
path: root/win32/strptime.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--win32/strptime.c603
1 files changed, 603 insertions, 0 deletions
diff --git a/win32/strptime.c b/win32/strptime.c
new file mode 100644
index 000000000..3205b95a2
--- /dev/null
+++ b/win32/strptime.c
@@ -0,0 +1,603 @@
1/* Copyright (C) 2002, 2004-2005, 2007, 2009-2020 Free Software Foundation,
2 Inc.
3 This file is part of the GNU C Library.
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2, or (at your option)
8 any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License along
16 with this program; if not, see <https://www.gnu.org/licenses/>. */
17
18/*
19 * File from gnulib (https://www.gnu.org/software/gnulib/), processed with
20 * coan source -U_LIBC -U_NL_CURRENT -UHAVE_TM_GMTOFF strptime.c
21 * and lightly edited.
22 *
23 * A form of support for tm_gmtoff was later restored.
24 */
25
26#include "libbb.h"
27#include <time.h>
28
29#include <assert.h>
30#include <ctype.h>
31#include <limits.h>
32#include <string.h>
33#include <stdbool.h>
34
35
36enum ptime_locale_status { not, loc, raw };
37
38
39
40#define match_char(ch1, ch2) if (ch1 != ch2) return NULL
41/* Oh come on. Get a reasonable compiler. */
42# define match_string(cs1, s2) \
43 (strncasecmp ((cs1), (s2), strlen (cs1)) ? 0 : ((s2) += strlen (cs1), 1))
44/* We intentionally do not use isdigit() for testing because this will
45 lead to problems with the wide character version. */
46#define get_number(from, to, n) \
47 do { \
48 int __n = n; \
49 val = 0; \
50 while (*rp == ' ') \
51 ++rp; \
52 if (*rp < '0' || *rp > '9') \
53 return NULL; \
54 do { \
55 val *= 10; \
56 val += *rp++ - '0'; \
57 } while (--__n > 0 && val * 10 <= to && *rp >= '0' && *rp <= '9'); \
58 if (val < from || val > to) \
59 return NULL; \
60 } while (0)
61# define get_alt_number(from, to, n) \
62 /* We don't have the alternate representation. */ \
63 get_number(from, to, n)
64#define recursive(new_fmt) \
65 (*(new_fmt) != '\0' \
66 && (rp = __strptime_internal (rp, (new_fmt), tm, \
67 decided, era_cnt, gmtoff)) != NULL)
68
69
70static char const weekday_name[][10] =
71 {
72 "Sunday", "Monday", "Tuesday", "Wednesday",
73 "Thursday", "Friday", "Saturday"
74 };
75static char const ab_weekday_name[][4] =
76 {
77 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
78 };
79static char const month_name[][10] =
80 {
81 "January", "February", "March", "April", "May", "June",
82 "July", "August", "September", "October", "November", "December"
83 };
84static char const ab_month_name[][4] =
85 {
86 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
87 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
88 };
89# define HERE_D_T_FMT "%a %b %e %H:%M:%S %Y"
90# define HERE_D_FMT "%m/%d/%y"
91# define HERE_AM_STR "AM"
92# define HERE_PM_STR "PM"
93# define HERE_T_FMT_AMPM "%I:%M:%S %p"
94# define HERE_T_FMT "%H:%M:%S"
95
96static const unsigned short int __mon_yday[2][13] =
97 {
98 /* Normal years. */
99 { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
100 /* Leap years. */
101 { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
102 };
103
104# define ISSPACE(Ch) isspace (Ch)
105
106
107
108
109#ifndef __isleap
110/* Nonzero if YEAR is a leap year (every 4 years,
111 except every 100th isn't, and every 400th is). */
112# define __isleap(year) \
113 ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
114#endif
115
116/* Compute the day of the week. */
117static void
118day_of_the_week (struct tm *tm)
119{
120 /* We know that January 1st 1970 was a Thursday (= 4). Compute the
121 difference between this data in the one on TM and so determine
122 the weekday. */
123 int corr_year = 1900 + tm->tm_year - (tm->tm_mon < 2);
124 int corr_quad = corr_year / 4;
125 int wday = (-473
126 + (365 * (tm->tm_year - 70))
127 + corr_quad
128 - ((corr_quad + (corr_quad < 0)) / 25 - (corr_quad < 0))
129 + ((corr_quad / 25) / 4)
130 + __mon_yday[0][tm->tm_mon]
131 + tm->tm_mday - 1);
132 tm->tm_wday = ((wday % 7) + 7) % 7;
133}
134
135/* Compute the day of the year. */
136static void
137day_of_the_year (struct tm *tm)
138{
139 tm->tm_yday = (__mon_yday[__isleap (1900 + tm->tm_year)][tm->tm_mon]
140 + (tm->tm_mday - 1));
141}
142
143
144static char *
145__strptime_internal (const char *rp, const char *fmt, struct tm *tm,
146 enum ptime_locale_status *decided, int era_cnt,
147 long *gmtoff)
148{
149
150 int cnt;
151 size_t val;
152 int have_I, is_pm;
153 int century, want_century;
154 int want_era;
155 int have_wday, want_xday;
156 int have_yday;
157 int have_mon, have_mday;
158 int have_uweek, have_wweek;
159 int week_no;
160
161 have_I = is_pm = 0;
162 century = -1;
163 want_century = 0;
164 want_era = 0;
165 week_no = 0;
166
167 have_wday = want_xday = have_yday = have_mon = have_mday = have_uweek = 0;
168 have_wweek = 0;
169
170 while (*fmt != '\0')
171 {
172 /* A white space in the format string matches 0 more or white
173 space in the input string. */
174 if (ISSPACE (*fmt))
175 {
176 while (ISSPACE (*rp))
177 ++rp;
178 ++fmt;
179 continue;
180 }
181
182 /* Any character but '%' must be matched by the same character
183 in the input string. */
184 if (*fmt != '%')
185 {
186 match_char (*fmt++, *rp++);
187 continue;
188 }
189
190 ++fmt;
191 /* We need this for handling the 'E' modifier. */
192 start_over:
193
194 switch (*fmt++)
195 {
196 case '%':
197 /* Match the '%' character itself. */
198 match_char ('%', *rp++);
199 break;
200 case 'a':
201 case 'A':
202 /* Match day of week. */
203 for (cnt = 0; cnt < 7; ++cnt)
204 {
205 if (*decided != loc
206 && (match_string (weekday_name[cnt], rp)
207 || match_string (ab_weekday_name[cnt], rp)))
208 {
209 *decided = raw;
210 break;
211 }
212 }
213 if (cnt == 7)
214 /* Does not match a weekday name. */
215 return NULL;
216 tm->tm_wday = cnt;
217 have_wday = 1;
218 break;
219 case 'b':
220 case 'B':
221 case 'h':
222 /* Match month name. */
223 for (cnt = 0; cnt < 12; ++cnt)
224 {
225 if (match_string (month_name[cnt], rp)
226 || match_string (ab_month_name[cnt], rp))
227 {
228 *decided = raw;
229 break;
230 }
231 }
232 if (cnt == 12)
233 /* Does not match a month name. */
234 return NULL;
235 tm->tm_mon = cnt;
236 want_xday = 1;
237 break;
238 case 'c':
239 /* Match locale's date and time format. */
240 if (!recursive (HERE_D_T_FMT))
241 return NULL;
242 want_xday = 1;
243 break;
244 case 'C':
245 /* Match century number. */
246 get_number (0, 99, 2);
247 century = val;
248 want_xday = 1;
249 break;
250 case 'd':
251 case 'e':
252 /* Match day of month. */
253 get_number (1, 31, 2);
254 tm->tm_mday = val;
255 have_mday = 1;
256 want_xday = 1;
257 break;
258 case 'F':
259 if (!recursive ("%Y-%m-%d"))
260 return NULL;
261 want_xday = 1;
262 break;
263 case 'x':
264 /* Fall through. */
265 case 'D':
266 /* Match standard day format. */
267 if (!recursive (HERE_D_FMT))
268 return NULL;
269 want_xday = 1;
270 break;
271 case 'k':
272 case 'H':
273 /* Match hour in 24-hour clock. */
274 get_number (0, 23, 2);
275 tm->tm_hour = val;
276 have_I = 0;
277 break;
278 case 'l':
279 /* Match hour in 12-hour clock. GNU extension. */
280 case 'I':
281 /* Match hour in 12-hour clock. */
282 get_number (1, 12, 2);
283 tm->tm_hour = val % 12;
284 have_I = 1;
285 break;
286 case 'j':
287 /* Match day number of year. */
288 get_number (1, 366, 3);
289 tm->tm_yday = val - 1;
290 have_yday = 1;
291 break;
292 case 'm':
293 /* Match number of month. */
294 get_number (1, 12, 2);
295 tm->tm_mon = val - 1;
296 have_mon = 1;
297 want_xday = 1;
298 break;
299 case 'M':
300 /* Match minute. */
301 get_number (0, 59, 2);
302 tm->tm_min = val;
303 break;
304 case 'n':
305 case 't':
306 /* Match any white space. */
307 while (ISSPACE (*rp))
308 ++rp;
309 break;
310 case 'p':
311 /* Match locale's equivalent of AM/PM. */
312 if (!match_string (HERE_AM_STR, rp))
313 {
314 if (match_string (HERE_PM_STR, rp))
315 is_pm = 1;
316 else
317 return NULL;
318 }
319 break;
320 case 'q':
321 /* Match quarter of year. GNU extension. */
322 get_number (1, 4, 1);
323 tm->tm_mon = (val - 1) * 3;
324 tm->tm_mday = 1;
325 have_mon = 1;
326 have_mday = 1;
327 want_xday = 1;
328 break;
329 case 'r':
330 if (!recursive (HERE_T_FMT_AMPM))
331 return NULL;
332 break;
333 case 'R':
334 if (!recursive ("%H:%M"))
335 return NULL;
336 break;
337 case 's':
338 {
339 /* The number of seconds may be very high so we cannot use
340 the 'get_number' macro. Instead read the number
341 character for character and construct the result while
342 doing this. */
343 time_t secs = 0;
344 if (*rp < '0' || *rp > '9')
345 /* We need at least one digit. */
346 return NULL;
347
348 do
349 {
350 secs *= 10;
351 secs += *rp++ - '0';
352 }
353 while (*rp >= '0' && *rp <= '9');
354
355 if (localtime_r (&secs, tm) == NULL)
356 /* Error in function. */
357 return NULL;
358 }
359 break;
360 case 'S':
361 get_number (0, 61, 2);
362 tm->tm_sec = val;
363 break;
364 case 'X':
365 /* Fall through. */
366 case 'T':
367 if (!recursive (HERE_T_FMT))
368 return NULL;
369 break;
370 case 'u':
371 get_number (1, 7, 1);
372 tm->tm_wday = val % 7;
373 have_wday = 1;
374 break;
375 case 'g':
376 get_number (0, 99, 2);
377 /* XXX This cannot determine any field in TM. */
378 break;
379 case 'G':
380 if (*rp < '0' || *rp > '9')
381 return NULL;
382 /* XXX Ignore the number since we would need some more
383 information to compute a real date. */
384 do
385 ++rp;
386 while (*rp >= '0' && *rp <= '9');
387 break;
388 case 'U':
389 get_number (0, 53, 2);
390 week_no = val;
391 have_uweek = 1;
392 break;
393 case 'W':
394 get_number (0, 53, 2);
395 week_no = val;
396 have_wweek = 1;
397 break;
398 case 'V':
399 get_number (0, 53, 2);
400 /* XXX This cannot determine any field in TM without some
401 information. */
402 break;
403 case 'w':
404 /* Match number of weekday. */
405 get_number (0, 6, 1);
406 tm->tm_wday = val;
407 have_wday = 1;
408 break;
409 case 'y':
410 /* Match year within century. */
411 get_number (0, 99, 2);
412 /* The "Year 2000: The Millennium Rollover" paper suggests that
413 values in the range 69-99 refer to the twentieth century. */
414 tm->tm_year = val >= 69 ? val : val + 100;
415 /* Indicate that we want to use the century, if specified. */
416 want_century = 1;
417 want_xday = 1;
418 break;
419 case 'Y':
420 /* Match year including century number. */
421 get_number (0, 9999, 4);
422 tm->tm_year = val - 1900;
423 want_century = 0;
424 want_xday = 1;
425 break;
426 case 'Z':
427 /* XXX How to handle this? */
428 break;
429 case 'z':
430 /* We recognize two formats: if two digits are given, these
431 specify hours. If fours digits are used, minutes are
432 also specified. And 'Z'.
433
434 Three formats! We recognize three formats... */
435 {
436 bool neg;
437 int n;
438
439 val = 0;
440 while (*rp == ' ')
441 ++rp;
442 if (*rp == 'Z') {
443 ++rp;
444 if (gmtoff)
445 *gmtoff = 0;
446 break;
447 }
448 if (*rp != '+' && *rp != '-')
449 return NULL;
450 neg = *rp++ == '-';
451 n = 0;
452 while (n < 4 && *rp >= '0' && *rp <= '9')
453 {
454 val = val * 10 + *rp++ - '0';
455 ++n;
456 }
457 if (n == 2)
458 val *= 100;
459 else if (n != 4)
460 /* Only two or four digits recognized. */
461 return NULL;
462 else
463 {
464 /* We have to convert the minutes into decimal. */
465 if (val % 100 >= 60)
466 return NULL;
467 val = (val / 100) * 100 + ((val % 100) * 50) / 30;
468 }
469 if (val > 1200)
470 return NULL;
471 if (gmtoff) {
472 *gmtoff = (val * 3600) / 100;
473 if (neg)
474 *gmtoff = -*gmtoff;
475 }
476 }
477 break;
478 case 'E':
479 /* We have no information about the era format. Just use
480 the normal format. */
481 if (strchr("cCyYxX", *fmt) == NULL)
482 /* This is an illegal format. */
483 return NULL;
484
485 goto start_over;
486 case 'O':
487 /* We don't have an alternative number format. Just use
488 the normal format. */
489 if (strchr("deHImMqSUWVwy", *fmt) == NULL)
490 /* This is an illegal format. */
491 return NULL;
492
493 goto start_over;
494 default:
495 return NULL;
496 }
497 }
498
499 if (have_I && is_pm)
500 tm->tm_hour += 12;
501
502 if (century != -1)
503 {
504 if (want_century)
505 tm->tm_year = tm->tm_year % 100 + (century - 19) * 100;
506 else
507 /* Only the century, but not the year. Strange, but so be it. */
508 tm->tm_year = (century - 19) * 100;
509 }
510
511 if (era_cnt != -1)
512 {
513 }
514 else
515 if (want_era)
516 {
517 /* No era found but we have seen an E modifier. Rectify some
518 values. */
519 if (want_century && century == -1 && tm->tm_year < 69)
520 tm->tm_year += 100;
521 }
522
523 if (want_xday && !have_wday)
524 {
525 if ( !(have_mon && have_mday) && have_yday)
526 {
527 /* We don't have tm_mon and/or tm_mday, compute them. */
528 int t_mon = 0;
529 while (__mon_yday[__isleap(1900 + tm->tm_year)][t_mon] <= tm->tm_yday)
530 t_mon++;
531 if (!have_mon)
532 tm->tm_mon = t_mon - 1;
533 if (!have_mday)
534 tm->tm_mday =
535 (tm->tm_yday
536 - __mon_yday[__isleap(1900 + tm->tm_year)][t_mon - 1] + 1);
537 }
538 day_of_the_week (tm);
539 }
540
541 if (want_xday && !have_yday)
542 day_of_the_year (tm);
543
544 if ((have_uweek || have_wweek) && have_wday)
545 {
546 int save_wday = tm->tm_wday;
547 int save_mday = tm->tm_mday;
548 int save_mon = tm->tm_mon;
549 int w_offset = have_uweek ? 0 : 1;
550
551 tm->tm_mday = 1;
552 tm->tm_mon = 0;
553 day_of_the_week (tm);
554 if (have_mday)
555 tm->tm_mday = save_mday;
556 if (have_mon)
557 tm->tm_mon = save_mon;
558
559 if (!have_yday)
560 tm->tm_yday = ((7 - (tm->tm_wday - w_offset)) % 7
561 + (week_no - 1) *7
562 + save_wday - w_offset);
563
564 if (!have_mday || !have_mon)
565 {
566 int t_mon = 0;
567 while (__mon_yday[__isleap(1900 + tm->tm_year)][t_mon]
568 <= tm->tm_yday)
569 t_mon++;
570 if (!have_mon)
571 tm->tm_mon = t_mon - 1;
572 if (!have_mday)
573 tm->tm_mday =
574 (tm->tm_yday
575 - __mon_yday[__isleap(1900 + tm->tm_year)][t_mon - 1] + 1);
576 }
577
578 tm->tm_wday = save_wday;
579 }
580
581 return (char *) rp;
582}
583
584
585char *
586strptime (const char *buf, const char *format, struct tm *tm)
587{
588 enum ptime_locale_status decided;
589
590 decided = raw;
591 return __strptime_internal (buf, format, tm, &decided, -1, NULL);
592}
593
594char *
595mingw_strptime (const char *buf, const char *format, struct tm *tm,
596 long *gmtoff)
597{
598 enum ptime_locale_status decided;
599
600 decided = raw;
601 return __strptime_internal (buf, format, tm, &decided, -1, gmtoff);
602}
603