1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2020 The DragonFly Project. All rights reserved.
5 * Copyright (c) 1992-2009 Edwin Groothuis <edwin@FreeBSD.org>.
6 * All rights reserved.
7 *
8 * This code is derived from software contributed to The DragonFly Project
9 * by Aaron LI <aly@aaronly.me>
10 *
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.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 *
32 * $FreeBSD: head/usr.bin/calendar/parsedata.c 326276 2017-11-27 15:37:16Z pfg $
33 */
34
35 #include <ctype.h>
36 #include <err.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <time.h>
41
42 #include "calendar.h"
43 #include "basics.h"
44 #include "days.h"
45 #include "gregorian.h"
46 #include "io.h"
47 #include "nnames.h"
48 #include "parsedata.h"
49 #include "utils.h"
50
51 struct dateinfo {
52 int flags;
53 int sday_id;
54 int year;
55 int month;
56 int dayofmonth;
57 int dayofweek;
58 int offset;
59 int index;
60 };
61
62 static bool check_dayofweek(const char *s, size_t *len, int *dow);
63 static bool check_month(const char *s, size_t *len, int *month);
64 static bool determine_style(const char *date, struct dateinfo *di);
65 static bool is_onlydigits(const char *s, bool endstar);
66 static bool parse_angle(const char *s, double *result);
67 static const char *parse_int_ranged(const char *s, size_t len, int min,
68 int max, int *result);
69 static bool parse_index(const char *s, int *index);
70 static void show_dateinfo(struct dateinfo *di);
71
72 /*
73 * Expected styles:
74 *
75 * Date ::= Year . '/' . Month . '/' . DayOfMonth |
76 * Year . ' ' . Month . ' ' . DayOfMonth |
77 * Month . '/' . DayOfMonth |
78 * Month . ' ' . DayOfMonth |
79 * Month . '/' . DayOfWeek . Index |
80 * Month . ' ' . DayOfWeek . Index |
81 * MonthName . '/' . AllDays |
82 * MonthName . ' ' . AllDays |
83 * AllDays . '/' . MonthName |
84 * AllDays . ' ' . MonthName |
85 * AllMonths . '/' . DayOfMonth |
86 * AllMonths . ' ' . DayOfMonth |
87 * DayOfMonth . '/' . AllMonths |
88 * DayOfMonth . ' ' . AllMonths |
89 * DayOfMonth . '/' . Month |
90 * DayOfMonth . ' ' . Month |
91 * DayOfWeek . Index . '/' . MonthName |
92 * DayOfWeek . Index . ' ' . MonthName |
93 * DayOfWeek . Index
94 * SpecialDay . Offset
95 *
96 * Year ::= '0' ... '9' | '00' ... '09' | '10' ... '99' |
97 * '100' ... '999' | '1000' ... '9999'
98 *
99 * Month ::= MonthName | MonthNumber
100 * MonthNumber ::= '0' ... '9' | '00' ... '09' | '10' ... '12'
101 * MonthName ::= MonthNameShort | MonthNameLong
102 * MonthNameLong ::= 'January' ... 'December'
103 * MonthNameShort ::= 'Jan' ... 'Dec' | 'Jan.' ... 'Dec.'
104 *
105 * DayOfWeek ::= DayOfWeekShort | DayOfWeekLong
106 * DayOfWeekShort ::= 'Mon' ... 'Sun'
107 * DayOfWeekLong ::= 'Monday' ... 'Sunday'
108 * DayOfMonth ::= '0' ... '9' | '00' ... '09' | '10' ... '29' |
109 * '30' ... '31'
110 *
111 * AllMonths ::= '*'
112 * AllDays ::= '*'
113 *
114 * Index ::= '' | IndexName |
115 * '+' . IndexNumber | '-' . IndexNumber
116 * IndexName ::= 'First' | 'Second' | 'Third' | 'Fourth' |
117 * 'Fifth' | 'Last'
118 * IndexNumber ::= '1' ... '5'
119 *
120 * Offset ::= '' | '+' . OffsetNumber | '-' . OffsetNumber
121 * OffsetNumber ::= '0' ... '9' | '00' ... '99' | '000' ... '299' |
122 * '300' ... '359' | '360' ... '365'
123 *
124 * SpecialDay ::= 'Easter' | 'Paskha' | 'Advent' |
125 * 'ChineseNewYear' |
126 * 'ChineseQingming' | 'ChineseJieqi' |
127 * 'NewMoon' | 'FullMoon' |
128 * 'MarEquinox' | 'SepEquinox' |
129 * 'JunSolstice' | 'DecSolstice'
130 */
131 static bool
determine_style(const char * date,struct dateinfo * di)132 determine_style(const char *date, struct dateinfo *di)
133 {
134 static char date2[128];
135 struct specialday *sday;
136 char *p, *p1, *p2;
137 size_t len;
138
139 snprintf(date2, sizeof(date2), "%s", date);
140
141 if ((p = strchr(date2, ' ')) == NULL &&
142 (p = strchr(date2, '/')) == NULL) {
143 for (size_t i = 0; specialdays[i].id != SD_NONE; i++) {
144 sday = &specialdays[i];
145 if (strncasecmp(date2, sday->name, sday->len) == 0) {
146 len = sday->len;
147 } else if (sday->n_len > 0 && strncasecmp(
148 date2, sday->n_name, sday->n_len) == 0) {
149 len = sday->n_len;
150 } else {
151 continue;
152 }
153
154 di->flags |= (F_SPECIALDAY | F_VARIABLE);
155 di->sday_id = sday->id;
156 if (strlen(date2) == len)
157 return true;
158
159 di->offset = (int)strtol(date2+len, NULL, 10);
160 di->flags |= F_OFFSET;
161 return true;
162 }
163
164 if (check_dayofweek(date2, &len, &di->dayofweek)) {
165 di->flags |= (F_DAYOFWEEK | F_VARIABLE);
166 if (strlen(date2) == len)
167 return true;
168 if (parse_index(date2+len, &di->index)) {
169 di->flags |= F_INDEX;
170 return true;
171 }
172 }
173
174 goto error;
175 }
176
177 *p = '\0';
178 p1 = date2;
179 p2 = p + 1;
180 /* Now p1 and p2 point to the first and second fields, respectively */
181
182 if ((p = strchr(p2, ' ')) != NULL ||
183 (p = strchr(p2, '/')) != NULL) {
184 /* Got a year in the date string. */
185 di->flags |= F_YEAR;
186 di->year = (int)strtol(p1, NULL, 10);
187 *p = '\0';
188 p1 = p2;
189 p2 = p + 1;
190 }
191
192 /* Both month and day as numbers */
193 if (is_onlydigits(p1, false) && is_onlydigits(p2, true)) {
194 di->flags |= (F_MONTH | F_DAYOFMONTH);
195 if (strchr(p2, '*') != NULL)
196 di->flags |= F_VARIABLE;
197
198 int m = (int)strtol(p1, NULL, 10);
199 int d = (int)strtol(p2, NULL, 10);
200 if (m > 12 && d > 12) {
201 warnx("%s: invalid month |%d| in date: |%s|",
202 __func__, m, date);
203 goto error;
204 }
205 if (m > 12)
206 swap(&m, &d);
207
208 di->month = m;
209 di->dayofmonth = d;
210 return true;
211 }
212
213 /* Check if there is an every-month specifier */
214 if ((strcmp(p1, "*") == 0 && is_onlydigits(p2, false)) ||
215 (strcmp(p2, "*") == 0 && is_onlydigits(p1, false) && (p2 = p1))) {
216 di->flags |= (F_ALLMONTH | F_DAYOFMONTH);
217 di->dayofmonth = (int)strtol(p2, NULL, 10);
218 return true;
219 }
220
221 /* Month as a number, then a weekday */
222 if (is_onlydigits(p1, false) &&
223 check_dayofweek(p2, &len, &di->dayofweek)) {
224 di->flags |= (F_MONTH | F_DAYOFWEEK | F_VARIABLE);
225 di->month = (int)strtol(p1, NULL, 10);
226
227 if (strlen(p2) == len)
228 return true;
229 if (parse_index(p2+len, &di->index)) {
230 di->flags |= F_INDEX;
231 return true;
232 }
233
234 warnx("%s: invalid weekday part |%s| in date |%s|",
235 __func__, p2, date);
236 goto error;
237 }
238
239 /*
240 * Check if there is a month string.
241 * NOTE: Need to check month name/string *after* month number,
242 * because a national month name can be the *same* as the
243 * month number (e.g., 'zh_CN.UTF-8' on macOS), which can
244 * confuse the date parsing if this case is checked *before*
245 * the month number case.
246 */
247 if (check_month(p1, &len, &di->month) ||
248 (check_month(p2, &len, &di->month) && (p2 = p1))) {
249 /* Now p2 is the non-month part */
250 di->flags |= F_MONTH;
251 if (strcmp(p2, "*") == 0) {
252 di->flags |= F_ALLDAY;
253 return true;
254 }
255 if (is_onlydigits(p2, false)) {
256 di->dayofmonth = (int)strtol(p2, NULL, 10);
257 di->flags |= F_DAYOFMONTH;
258 return true;
259 }
260 if (check_dayofweek(p2, &len, &di->dayofweek)) {
261 di->flags |= (F_DAYOFWEEK | F_VARIABLE);
262 if (strlen(p2) == len)
263 return true;
264 if (parse_index(p2+len, &di->index)) {
265 di->flags |= F_INDEX;
266 return true;
267 }
268 }
269
270 warnx("%s: invalid non-month part |%s| in date |%s|",
271 __func__, p2, date);
272 goto error;
273 }
274
275 error:
276 warnx("%s: unrecognized date: |%s|", __func__, date);
277 return false;
278 }
279
280 static void
show_dateinfo(struct dateinfo * di)281 show_dateinfo(struct dateinfo *di)
282 {
283 struct specialday *sday;
284
285 fprintf(stderr, "flags: 0x%x -", di->flags);
286
287 if ((di->flags & F_YEAR) != 0)
288 fprintf(stderr, " year(%d)", di->year);
289 if ((di->flags & F_MONTH) != 0)
290 fprintf(stderr, " month(%d)", di->month);
291 if ((di->flags & F_DAYOFWEEK) != 0)
292 fprintf(stderr, " dayofweek(%d)", di->dayofweek);
293 if ((di->flags & F_DAYOFMONTH) != 0)
294 fprintf(stderr, " dayofmonth(%d)", di->dayofmonth);
295 if ((di->flags & F_INDEX) != 0)
296 fprintf(stderr, " index(%d)", di->index);
297
298 if ((di->flags & F_SPECIALDAY) != 0) {
299 fprintf(stderr, " specialday");
300 for (size_t i = 0; specialdays[i].id != SD_NONE; i++) {
301 sday = &specialdays[i];
302 if (di->sday_id == sday->id)
303 fprintf(stderr, "(%s)", sday->name);
304 }
305 }
306 if ((di->flags & F_OFFSET) != 0)
307 fprintf(stderr, " offset(%d)", di->offset);
308
309 if ((di->flags & F_ALLMONTH) != 0)
310 fprintf(stderr, " allmonth");
311 if ((di->flags & F_ALLDAY) != 0)
312 fprintf(stderr, " allday");
313 if ((di->flags & F_VARIABLE) != 0)
314 fprintf(stderr, " variable");
315
316 fprintf(stderr, "\n");
317 fflush(stderr);
318 }
319
320 int
parse_cal_date(const char * date,int * flags,struct cal_day ** dayp,char ** edp)321 parse_cal_date(const char *date, int *flags, struct cal_day **dayp, char **edp)
322 {
323 struct specialday *sday;
324 struct dateinfo di;
325 int index, offset;
326
327 memset(&di, 0, sizeof(di));
328 di.flags = F_NONE;
329
330 if (!determine_style(date, &di)) {
331 if (Options.debug)
332 show_dateinfo(&di);
333 return -1;
334 }
335
336 if (Options.debug >= 3)
337 show_dateinfo(&di);
338
339 *flags = di.flags;
340 index = (di.flags & F_INDEX) ? di.index : 0;
341 offset = (di.flags & F_OFFSET) ? di.offset : 0;
342
343 /* Specified year, month and day (e.g., '2020/Aug/16') */
344 if ((di.flags & ~F_VARIABLE) == (F_YEAR | F_MONTH | F_DAYOFMONTH) &&
345 Calendar->find_days_ymd != NULL) {
346 return (Calendar->find_days_ymd)(di.year, di.month,
347 di.dayofmonth, dayp, edp);
348 }
349
350 /* Specified month and day (e.g., 'Aug/16') */
351 if ((di.flags & ~F_VARIABLE) == (F_MONTH | F_DAYOFMONTH) &&
352 Calendar->find_days_ymd != NULL) {
353 return (Calendar->find_days_ymd)(-1, di.month, di.dayofmonth,
354 dayp, edp);
355 }
356
357 /* Same day every month (e.g., '* 16') */
358 if (di.flags == (F_ALLMONTH | F_DAYOFMONTH) &&
359 Calendar->find_days_dom != NULL) {
360 return (Calendar->find_days_dom)(di.dayofmonth, dayp, edp);
361 }
362
363 /* Every day of a month (e.g., 'Aug *') */
364 if (di.flags == (F_ALLDAY | F_MONTH) &&
365 Calendar->find_days_month != NULL) {
366 return (Calendar->find_days_month)(di.month, dayp, edp);
367 }
368
369 /*
370 * Every day-of-week of a month (e.g., 'Aug/Sun')
371 * One indexed day-of-week of a month (e.g., 'Aug/Sun+3')
372 */
373 if ((di.flags & ~F_INDEX) == (F_MONTH | F_DAYOFWEEK | F_VARIABLE) &&
374 Calendar->find_days_mdow != NULL) {
375 return (Calendar->find_days_mdow)(di.month, di.dayofweek,
376 index, dayp, edp);
377 }
378
379 /*
380 * Every day-of-week of the year (e.g., 'Sun')
381 * One indexed day-of-week of every month (e.g., 'Sun+3')
382 */
383 if ((di.flags & ~F_INDEX) == (F_DAYOFWEEK | F_VARIABLE) &&
384 Calendar->find_days_mdow != NULL) {
385 return (Calendar->find_days_mdow)(-1, di.dayofweek, index,
386 dayp, edp);
387 }
388
389 /* Special days with optional offset (e.g., 'ChineseNewYear+14') */
390 if ((di.flags & F_SPECIALDAY) != 0) {
391 for (size_t i = 0; specialdays[i].id != SD_NONE; i++) {
392 sday = &specialdays[i];
393 if (di.sday_id == sday->id && sday->find_days != NULL)
394 return (sday->find_days)(offset, dayp, edp);
395 }
396 }
397
398 warnx("%s: Unsupported date |%s| in '%s' calendar",
399 __func__, date, Calendar->name);
400 if (Options.debug)
401 show_dateinfo(&di);
402
403 return -1;
404 }
405
406 static bool
check_month(const char * s,size_t * len,int * month)407 check_month(const char *s, size_t *len, int *month)
408 {
409 struct nname *nname;
410
411 for (int i = 0; month_names[i].name != NULL; i++) {
412 nname = &month_names[i];
413
414 if (nname->fn_name &&
415 strncasecmp(s, nname->fn_name, nname->fn_len) == 0) {
416 *len = nname->fn_len;
417 *month = nname->value;
418 return (true);
419 }
420
421 if (nname->n_name &&
422 strncasecmp(s, nname->n_name, nname->n_len) == 0) {
423 *len = nname->n_len;
424 *month = nname->value;
425 return (true);
426 }
427
428 if (nname->f_name &&
429 strncasecmp(s, nname->f_name, nname->f_len) == 0) {
430 *len = nname->f_len;
431 *month = nname->value;
432 return (true);
433 }
434
435 if (strncasecmp(s, nname->name, nname->len) == 0) {
436 *len = nname->len;
437 *month = nname->value;
438 return (true);
439 }
440 }
441
442 return (false);
443 }
444
445 static bool
check_dayofweek(const char * s,size_t * len,int * dow)446 check_dayofweek(const char *s, size_t *len, int *dow)
447 {
448 struct nname *nname;
449
450 for (int i = 0; dow_names[i].name != NULL; i++) {
451 nname = &dow_names[i];
452
453 if (nname->fn_name &&
454 strncasecmp(s, nname->fn_name, nname->fn_len) == 0) {
455 *len = nname->fn_len;
456 *dow = nname->value;
457 return (true);
458 }
459
460 if (nname->n_name &&
461 strncasecmp(s, nname->n_name, nname->n_len) == 0) {
462 *len = nname->n_len;
463 *dow = nname->value;
464 return (true);
465 }
466
467 if (nname->f_name &&
468 strncasecmp(s, nname->f_name, nname->f_len) == 0) {
469 *len = nname->f_len;
470 *dow = nname->value;
471 return (true);
472 }
473
474 if (strncasecmp(s, nname->name, nname->len) == 0) {
475 *len = nname->len;
476 *dow = nname->value;
477 return (true);
478 }
479 }
480
481 return (false);
482 }
483
484 static bool
is_onlydigits(const char * s,bool endstar)485 is_onlydigits(const char *s, bool endstar)
486 {
487 for (int i = 0; s[i] != '\0'; i++) {
488 if (endstar && i > 0 && s[i] == '*' && s[i+1] == '\0')
489 return (true);
490 if (!isdigit((unsigned char)s[i]))
491 return (false);
492 }
493 return (true);
494 }
495
496 static bool
parse_index(const char * s,int * index)497 parse_index(const char *s, int *index)
498 {
499 struct nname *nname;
500 bool parsed = false;
501
502 if (s[0] == '+' || s[0] == '-') {
503 char *endp;
504 int v = (int)strtol(s, &endp, 10);
505 if (*endp != '\0')
506 return false; /* has trailing junk */
507 if (v == 0 || v <= -6 || v >= 6) {
508 warnx("%s: invalid value: %d", __func__, v);
509 return false;
510 }
511
512 *index = v;
513 parsed = true;
514 }
515
516 for (int i = 0; !parsed && sequence_names[i].name != NULL; i++) {
517 nname = &sequence_names[i];
518 if (strcasecmp(s, nname->name) == 0 ||
519 (nname->n_name && strcasecmp(s, nname->n_name) == 0)) {
520 *index = nname->value;
521 parsed = true;
522 }
523 }
524
525 DPRINTF2("%s: |%s| -> %d (status=%s)\n",
526 __func__, s, *index, (parsed ? "ok" : "fail"));
527 return parsed;
528 }
529
530
531 /*
532 * Parse the specified length of a string to an integer and check its range.
533 * Return the pointer to the next character of the parsed string on success,
534 * otherwise return NULL.
535 */
536 static const char *
parse_int_ranged(const char * s,size_t len,int min,int max,int * result)537 parse_int_ranged(const char *s, size_t len, int min, int max, int *result)
538 {
539 if (strlen(s) < len)
540 return NULL;
541
542 const char *end = s + len;
543 int v = 0;
544 while (s < end) {
545 if (isdigit((unsigned char)*s) == 0)
546 return NULL;
547 v = 10 * v + (*s - '0');
548 s++;
549 }
550
551 if (v < min || v > max)
552 return NULL;
553
554 *result = v;
555 return end;
556 }
557
558 /*
559 * Parse the timezone string (format: ±hh:mm, ±hhmm, or ±hh) to the number
560 * of seconds east of UTC.
561 * Return true on success, otherwise false.
562 */
563 bool
parse_timezone(const char * s,int * result)564 parse_timezone(const char *s, int *result)
565 {
566 if (*s != '+' && *s != '-')
567 return false;
568 char sign = *s++;
569
570 int hh = 0;
571 int mm = 0;
572 if ((s = parse_int_ranged(s, 2, 0, 23, &hh)) == NULL)
573 return false;
574 if (*s != '\0') {
575 if (*s == ':')
576 s++;
577 if ((s = parse_int_ranged(s, 2, 0, 59, &mm)) == NULL)
578 return false;
579 }
580 if (*s != '\0')
581 return false;
582
583 int offset = hh * 3600 + mm * 60;
584 *result = (sign == '+') ? offset : -offset;
585
586 DPRINTF("%s: parsed |%s| -> %d seconds\n", __func__, s, *result);
587 return true;
588 }
589
590 /*
591 * Parse a angle/coordinate string in format of a single float number or
592 * [+-]d:m:s, where 'd' and 'm' are degrees and minutes of integer type,
593 * and 's' is seconds of float type.
594 * Return true on success, otherwise false.
595 */
596 static bool
parse_angle(const char * s,double * result)597 parse_angle(const char *s, double *result)
598 {
599 char sign = '+';
600 if (*s == '+' || *s == '-')
601 sign = *s++;
602
603 char *endp;
604 double v;
605 v = strtod(s, &endp);
606 if (s == endp || *endp != '\0') {
607 /* try to parse format of 'd:m:s' */
608 int deg = 0;
609 int min = 0;
610 double sec = 0.0;
611 switch (sscanf(s, "%d:%d:%lf", °, &min, &sec)) {
612 case 3:
613 case 2:
614 case 1:
615 if (min < 0 || min >= 60 || sec < 0 || sec >= 60)
616 return false;
617 v = deg + min / 60.0 + sec / 3600.0;
618 break;
619 default:
620 return false;
621 }
622 }
623
624 *result = (sign == '+') ? v : -v;
625 return true;
626 }
627
628 /*
629 * Parse location of format: latitude,longitude[,elevation]
630 * where 'latitude' and 'longitude' can be represented as a float number or
631 * in '[+-]d:m:s' format, and 'elevation' is optional and should be a
632 * positive float number.
633 * Return true on success, otherwise false.
634 */
635 bool
parse_location(const char * s,double * latitude,double * longitude,double * elevation)636 parse_location(const char *s, double *latitude, double *longitude,
637 double *elevation)
638 {
639 char *ds = xstrdup(s);
640 const char *sep = ",";
641 char *p;
642 double v;
643
644 p = strtok(ds, sep);
645 if (parse_angle(p, &v) && fabs(v) <= 90) {
646 *latitude = v;
647 } else {
648 warnx("%s: invalid latitude: |%s|", __func__, p);
649 return false;
650 }
651
652 p = strtok(NULL, sep);
653 if (p == NULL) {
654 warnx("%s: missing longitude", __func__);
655 return false;
656 }
657 if (parse_angle(p, &v) && fabs(v) <= 180) {
658 *longitude = v;
659 } else {
660 warnx("%s: invalid longitude: |%s|", __func__, p);
661 return false;
662 }
663
664 p = strtok(NULL, sep);
665 if (p != NULL) {
666 char *endp;
667 v = strtod(p, &endp);
668 if (p == endp || *endp != '\0' || v < 0) {
669 warnx("%s: invalid elevation: |%s|", __func__, p);
670 return false;
671 }
672 *elevation = v;
673 }
674
675 if ((p = strtok(NULL, sep)) != NULL) {
676 warnx("%s: unknown value: |%s|", __func__, p);
677 return false;
678 }
679
680 return true;
681 }
682
683 /*
684 * Parse date string of format '[[[CC]YY]MM]DD' into a fixed date.
685 * Return true on success, otherwise false.
686 */
687 bool
parse_date(const char * date,int * rd_out)688 parse_date(const char *date, int *rd_out)
689 {
690 size_t len;
691 time_t now;
692 struct tm tm;
693 struct date gdate;
694
695 now = time(NULL);
696 tzset();
697 localtime_r(&now, &tm);
698 date_set(&gdate, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
699
700 len = strlen(date);
701 if (len < 2)
702 return false;
703
704 if (!parse_int_ranged(date+len-2, 2, 1, 31, &gdate.day))
705 return false;
706
707 if (len >= 4) {
708 if (!parse_int_ranged(date+len-4, 2, 1, 12, &gdate.month))
709 return false;
710 }
711
712 if (len >= 6) {
713 if (!parse_int_ranged(date, len-4, 0, 9999, &gdate.year))
714 return false;
715 if (gdate.year < 69) /* Y2K */
716 gdate.year += 2000;
717 else if (gdate.year < 100)
718 gdate.year += 1900;
719 }
720
721 *rd_out = fixed_from_gregorian(&gdate);
722
723 DPRINTF("%s: parsed |%s| -> %d-%02d-%02d\n",
724 __func__, date, gdate.year, gdate.month, gdate.day);
725 return true;
726 }
727
728 /*
729 * Parse time string of format 'hh:mm[:ss]' into a float time in
730 * units of days.
731 * Return true on success, otherwise false.
732 */
733 bool
parse_time(const char * time,double * t_out)734 parse_time(const char *time, double *t_out)
735 {
736 int hh = 0;
737 int mm = 0;
738 int ss = 0;
739
740 switch (sscanf(time, "%d:%d:%d", &hh, &mm, &ss)) {
741 case 3:
742 case 2:
743 break;
744 default:
745 return false;
746 }
747
748 if (hh < 0 || hh >= 24 || mm < 0 || mm >= 60 || ss < 0 || ss > 60)
749 return false;
750
751 *t_out = (hh + mm/60.0 + ss/3600.0) / 24.0;
752
753 DPRINTF("%s: parsed |%s| -> %.3lf day\n", __func__, time, *t_out);
754 return true;
755 }
756