2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2020 The DragonFly Project. All rights reserved.
5 * Copyright (c) 1992-2009 Edwin Groothuis <edwin@FreeBSD.org>.
8 * This code is derived from software contributed to The DragonFly Project
9 * by Aaron LI <aly@aaronly.me>
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
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.
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
32 * $FreeBSD: head/usr.bin/calendar/parsedata.c 326276 2017-11-27 15:37:16Z pfg $
45 #include "gregorian.h"
48 #include "parsedata.h"
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);
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 |
96 * Year ::= '0' ... '9' | '00' ... '09' | '10' ... '99' |
97 * '100' ... '999' | '1000' ... '9999'
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.'
105 * DayOfWeek ::= DayOfWeekShort | DayOfWeekLong
106 * DayOfWeekShort ::= 'Mon' ... 'Sun'
107 * DayOfWeekLong ::= 'Monday' ... 'Sunday'
108 * DayOfMonth ::= '0' ... '9' | '00' ... '09' | '10' ... '29' |
114 * Index ::= '' | IndexName |
115 * '+' . IndexNumber | '-' . IndexNumber
116 * IndexName ::= 'First' | 'Second' | 'Third' | 'Fourth' |
118 * IndexNumber ::= '1' ... '5'
120 * Offset ::= '' | '+' . OffsetNumber | '-' . OffsetNumber
121 * OffsetNumber ::= '0' ... '9' | '00' ... '99' | '000' ... '299' |
122 * '300' ... '359' | '360' ... '365'
124 * SpecialDay ::= 'Easter' | 'Paskha' | 'Advent' |
126 * 'ChineseQingming' | 'ChineseJieqi' |
127 * 'NewMoon' | 'FullMoon' |
128 * 'MarEquinox' | 'SepEquinox' |
129 * 'JunSolstice' | 'DecSolstice'
132 determine_style(const char *date, struct dateinfo *di)
134 static char date2[128];
135 struct specialday *sday;
139 snprintf(date2, sizeof(date2), "%s", date);
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) {
147 } else if (sday->n_len > 0 && strncasecmp(
148 date2, sday->n_name, sday->n_len) == 0) {
154 di->flags |= (F_SPECIALDAY | F_VARIABLE);
155 di->sday_id = sday->id;
156 if (strlen(date2) == len)
159 di->offset = (int)strtol(date2+len, NULL, 10);
160 di->flags |= F_OFFSET;
164 if (check_dayofweek(date2, &len, &di->dayofweek)) {
165 di->flags |= (F_DAYOFWEEK | F_VARIABLE);
166 if (strlen(date2) == len)
168 if (parse_index(date2+len, &di->index)) {
169 di->flags |= F_INDEX;
180 /* Now p1 and p2 point to the first and second fields, respectively */
182 if ((p = strchr(p2, ' ')) != NULL ||
183 (p = strchr(p2, '/')) != NULL) {
184 /* Got a year in the date string. */
186 di->year = (int)strtol(p1, NULL, 10);
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;
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|",
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);
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);
227 if (strlen(p2) == len)
229 if (parse_index(p2+len, &di->index)) {
230 di->flags |= F_INDEX;
234 warnx("%s: invalid weekday part |%s| in date |%s|",
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.
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;
255 if (is_onlydigits(p2, false)) {
256 di->dayofmonth = (int)strtol(p2, NULL, 10);
257 di->flags |= F_DAYOFMONTH;
260 if (check_dayofweek(p2, &len, &di->dayofweek)) {
261 di->flags |= (F_DAYOFWEEK | F_VARIABLE);
262 if (strlen(p2) == len)
264 if (parse_index(p2+len, &di->index)) {
265 di->flags |= F_INDEX;
270 warnx("%s: invalid non-month part |%s| in date |%s|",
276 warnx("%s: unrecognized date: |%s|", __func__, date);
281 show_dateinfo(struct dateinfo *di)
283 struct specialday *sday;
285 fprintf(stderr, "flags: 0x%x -", di->flags);
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);
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);
306 if ((di->flags & F_OFFSET) != 0)
307 fprintf(stderr, " offset(%d)", di->offset);
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");
316 fprintf(stderr, "\n");
321 parse_cal_date(const char *date, int *flags, struct cal_day **dayp, char **edp)
323 struct specialday *sday;
327 memset(&di, 0, sizeof(di));
330 if (!determine_style(date, &di)) {
336 if (Options.debug >= 3)
340 index = (di.flags & F_INDEX) ? di.index : 0;
341 offset = (di.flags & F_OFFSET) ? di.offset : 0;
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);
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,
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);
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);
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')
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,
380 * Every day-of-week of the year (e.g., 'Sun')
381 * One indexed day-of-week of every month (e.g., 'Sun+3')
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,
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);
398 warnx("%s: Unsupported date |%s| in '%s' calendar",
399 __func__, date, Calendar->name);
407 check_month(const char *s, size_t *len, int *month)
411 for (int i = 0; month_names[i].name != NULL; i++) {
412 nname = &month_names[i];
414 if (nname->fn_name &&
415 strncasecmp(s, nname->fn_name, nname->fn_len) == 0) {
416 *len = nname->fn_len;
417 *month = nname->value;
422 strncasecmp(s, nname->n_name, nname->n_len) == 0) {
424 *month = nname->value;
429 strncasecmp(s, nname->f_name, nname->f_len) == 0) {
431 *month = nname->value;
435 if (strncasecmp(s, nname->name, nname->len) == 0) {
437 *month = nname->value;
446 check_dayofweek(const char *s, size_t *len, int *dow)
450 for (int i = 0; dow_names[i].name != NULL; i++) {
451 nname = &dow_names[i];
453 if (nname->fn_name &&
454 strncasecmp(s, nname->fn_name, nname->fn_len) == 0) {
455 *len = nname->fn_len;
461 strncasecmp(s, nname->n_name, nname->n_len) == 0) {
468 strncasecmp(s, nname->f_name, nname->f_len) == 0) {
474 if (strncasecmp(s, nname->name, nname->len) == 0) {
485 is_onlydigits(const char *s, bool endstar)
487 for (int i = 0; s[i] != '\0'; i++) {
488 if (endstar && i > 0 && s[i] == '*' && s[i+1] == '\0')
490 if (!isdigit((unsigned char)s[i]))
497 parse_index(const char *s, int *index)
502 if (s[0] == '+' || s[0] == '-') {
504 int v = (int)strtol(s, &endp, 10);
506 return false; /* has trailing junk */
507 if (v == 0 || v <= -6 || v >= 6) {
508 warnx("%s: invalid value: %d", __func__, v);
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;
525 DPRINTF2("%s: |%s| -> %d (status=%s)\n",
526 __func__, s, *index, (parsed ? "ok" : "fail"));
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.
537 parse_int_ranged(const char *s, size_t len, int min, int max, int *result)
542 const char *end = s + len;
545 if (isdigit((unsigned char)*s) == 0)
547 v = 10 * v + (*s - '0');
551 if (v < min || v > max)
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.
564 parse_timezone(const char *s, int *result)
566 if (*s != '+' && *s != '-')
572 if ((s = parse_int_ranged(s, 2, 0, 23, &hh)) == NULL)
577 if ((s = parse_int_ranged(s, 2, 0, 59, &mm)) == NULL)
583 int offset = hh * 3600 + mm * 60;
584 *result = (sign == '+') ? offset : -offset;
586 DPRINTF("%s: parsed |%s| -> %d seconds\n", __func__, s, *result);
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.
597 parse_angle(const char *s, double *result)
600 if (*s == '+' || *s == '-')
605 v = strtod(s, &endp);
606 if (s == endp || *endp != '\0') {
607 /* try to parse format of 'd:m:s' */
611 switch (sscanf(s, "%d:%d:%lf", °, &min, &sec)) {
615 if (min < 0 || min >= 60 || sec < 0 || sec >= 60)
617 v = deg + min / 60.0 + sec / 3600.0;
624 *result = (sign == '+') ? v : -v;
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.
636 parse_location(const char *s, double *latitude, double *longitude,
639 char *ds = xstrdup(s);
640 const char *sep = ",";
645 if (parse_angle(p, &v) && fabs(v) <= 90) {
648 warnx("%s: invalid latitude: |%s|", __func__, p);
652 p = strtok(NULL, sep);
654 warnx("%s: missing longitude", __func__);
657 if (parse_angle(p, &v) && fabs(v) <= 180) {
660 warnx("%s: invalid longitude: |%s|", __func__, p);
664 p = strtok(NULL, sep);
667 v = strtod(p, &endp);
668 if (p == endp || *endp != '\0' || v < 0) {
669 warnx("%s: invalid elevation: |%s|", __func__, p);
675 if ((p = strtok(NULL, sep)) != NULL) {
676 warnx("%s: unknown value: |%s|", __func__, p);
684 * Parse date string of format '[[[CC]YY]MM]DD' into a fixed date.
685 * Return true on success, otherwise false.
688 parse_date(const char *date, int *rd_out)
697 localtime_r(&now, &tm);
698 date_set(&gdate, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
704 if (!parse_int_ranged(date+len-2, 2, 1, 31, &gdate.day))
708 if (!parse_int_ranged(date+len-4, 2, 1, 12, &gdate.month))
713 if (!parse_int_ranged(date, len-4, 0, 9999, &gdate.year))
715 if (gdate.year < 69) /* Y2K */
717 else if (gdate.year < 100)
721 *rd_out = fixed_from_gregorian(&gdate);
723 DPRINTF("%s: parsed |%s| -> %d-%02d-%02d\n",
724 __func__, date, gdate.year, gdate.month, gdate.day);
729 * Parse time string of format 'hh:mm[:ss]' into a float time in
731 * Return true on success, otherwise false.
734 parse_time(const char *time, double *t_out)
740 switch (sscanf(time, "%d:%d:%d", &hh, &mm, &ss)) {
748 if (hh < 0 || hh >= 24 || mm < 0 || mm >= 60 || ss < 0 || ss > 60)
751 *t_out = (hh + mm/60.0 + ss/3600.0) / 24.0;
753 DPRINTF("%s: parsed |%s| -> %.3lf day\n", __func__, time, *t_out);