nrelease - fix/improve livecd
[dragonfly.git] / usr.bin / calendar / parsedata.c
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
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
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
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
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
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
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
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 *
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
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
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", &deg, &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
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
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
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 }