xref: /dragonfly/usr.bin/calendar/parsedata.c (revision 2b7dbe20)
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 }
756