1 /* $NetBSD: partime.c,v 1.1.1.2 1996/10/13 21:57:06 veego Exp $ */
3 /* Parse a string, yielding a struct partime that describes it. */
5 /* Copyright 1993, 1994, 1995 Paul Eggert
6 Distributed under license by the Free Software Foundation, Inc.
8 This file is part of RCS.
10 RCS is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2, or (at your option)
15 RCS is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
20 You should have received a copy of the GNU General Public License
21 along with RCS; see the file COPYING.
22 If not, write to the Free Software Foundation,
23 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 Report problems and direct all questions to:
27 rcs-bugs@cs.purdue.edu
46 #define isdigit(c) (((unsigned)(c)-'0') <= 9) /* faster than stock */
50 char const partimeId
[]
51 = "Id: partime.c,v 5.13 1995/06/16 06:19:24 eggert Exp";
54 /* Lookup tables for names of months, weekdays, time zones. */
56 #define NAME_LENGTH_MAXIMUM 4
59 char name
[NAME_LENGTH_MAXIMUM
];
64 static char const *parse_decimal
P((char const*,int,int,int,int,int*,int*));
65 static char const *parse_fixed
P((char const*,int,int*));
66 static char const *parse_pattern_letter
P((char const*,int,struct partime
*));
67 static char const *parse_prefix
P((char const*,struct partime
*,int*));
68 static char const *parse_ranged
P((char const*,int,int,int,int*));
69 static int lookup
P((char const*,struct name_val
const[]));
70 static int merge_partime
P((struct partime
*, struct partime
const*));
71 static void undefine
P((struct partime
*));
74 static struct name_val
const month_names
[] = {
75 {"jan",0}, {"feb",1}, {"mar",2}, {"apr",3}, {"may",4}, {"jun",5},
76 {"jul",6}, {"aug",7}, {"sep",8}, {"oct",9}, {"nov",10}, {"dec",11},
80 static struct name_val
const weekday_names
[] = {
81 {"sun",0}, {"mon",1}, {"tue",2}, {"wed",3}, {"thu",4}, {"fri",5}, {"sat",6},
85 #define hr60nonnegative(t) ((t)/100 * 60 + (t)%100)
86 #define hr60(t) ((t)<0 ? -hr60nonnegative(-(t)) : hr60nonnegative(t))
87 #define zs(t,s) {s, hr60(t)}
88 #define zd(t,s,d) zs(t, s), zs((t)+100, d)
90 static struct name_val
const zone_names
[] = {
91 zs(-1000, "hst"), /* Hawaii */
92 zd(-1000,"hast","hadt"),/* Hawaii-Aleutian */
93 zd(- 900,"akst","akdt"),/* Alaska */
94 zd(- 800, "pst", "pdt"),/* Pacific */
95 zd(- 700, "mst", "mdt"),/* Mountain */
96 zd(- 600, "cst", "cdt"),/* Central */
97 zd(- 500, "est", "edt"),/* Eastern */
98 zd(- 400, "ast", "adt"),/* Atlantic */
99 zd(- 330, "nst", "ndt"),/* Newfoundland */
100 zs( 000, "utc"), /* Coordinated Universal */
101 zs( 000, "cut"), /* " */
102 zs( 000, "ut"), /* Universal */
103 zs( 000, "z"), /* Zulu (required by ISO 8601) */
104 zd( 000, "gmt", "bst"),/* Greenwich Mean, British Summer */
105 zs( 000, "wet"), /* Western Europe */
106 zs( 100, "met"), /* Middle Europe */
107 zs( 100, "cet"), /* Central Europe */
108 zs( 200, "eet"), /* Eastern Europe */
109 zs( 530, "ist"), /* India */
110 zd( 900, "jst", "jdt"),/* Japan */
111 zd( 900, "kst", "kdt"),/* Korea */
112 zd( 1200,"nzst","nzdt"),/* New Zealand */
115 /* The following names are duplicates or are not well attested. */
116 zs(-1100, "sst"), /* Samoa */
117 zs(-1000, "tht"), /* Tahiti */
118 zs(- 930, "mqt"), /* Marquesas */
119 zs(- 900, "gbt"), /* Gambier */
120 zd(- 900, "yst", "ydt"),/* Yukon - name is no longer used */
121 zs(- 830, "pit"), /* Pitcairn */
122 zd(- 500, "cst", "cdt"),/* Cuba */
123 zd(- 500, "ast", "adt"),/* Acre */
124 zd(- 400, "wst", "wdt"),/* Western Brazil */
125 zd(- 400, "ast", "adt"),/* Andes */
126 zd(- 400, "cst", "cdt"),/* Chile */
127 zs(- 300, "wgt"), /* Western Greenland */
128 zd(- 300, "est", "edt"),/* Eastern South America */
129 zs(- 300, "mgt"), /* Middle Greenland */
130 zd(- 200, "fst", "fdt"),/* Fernando de Noronha */
131 zs(- 100, "egt"), /* Eastern Greenland */
132 zs(- 100, "aat"), /* Atlantic Africa */
133 zs(- 100, "act"), /* Azores and Canaries */
134 zs( 000, "wat"), /* West Africa */
135 zs( 100, "cat"), /* Central Africa */
136 zd( 100, "mez","mesz"),/* Mittel-Europaeische Zeit */
137 zs( 200, "sat"), /* South Africa */
138 zd( 200, "ist", "idt"),/* Israel */
139 zs( 300, "eat"), /* East Africa */
140 zd( 300, "ast", "adt"),/* Arabia */
141 zd( 300, "msk", "msd"),/* Moscow */
142 zd( 330, "ist", "idt"),/* Iran */
143 zs( 400, "gst"), /* Gulf */
144 zs( 400, "smt"), /* Seychelles & Mascarene */
145 zd( 400, "esk", "esd"),/* Yekaterinburg */
146 zd( 400, "bsk", "bsd"),/* Baku */
147 zs( 430, "aft"), /* Afghanistan */
148 zd( 500, "osk", "osd"),/* Omsk */
149 zs( 500, "pkt"), /* Pakistan */
150 zd( 500, "tsk", "tsd"),/* Tashkent */
151 zs( 545, "npt"), /* Nepal */
152 zs( 600, "bgt"), /* Bangladesh */
153 zd( 600, "nsk", "nsd"),/* Novosibirsk */
154 zs( 630, "bmt"), /* Burma */
155 zs( 630, "cct"), /* Cocos */
156 zs( 700, "ict"), /* Indochina */
157 zs( 700, "jvt"), /* Java */
158 zd( 700, "isk", "isd"),/* Irkutsk */
159 zs( 800, "hkt"), /* Hong Kong */
160 zs( 800, "pst"), /* Philippines */
161 zs( 800, "sgt"), /* Singapore */
162 zd( 800, "cst", "cdt"),/* China */
163 zd( 800, "ust", "udt"),/* Ulan Bator */
164 zd( 800, "wst", "wst"),/* Western Australia */
165 zd( 800, "ysk", "ysd"),/* Yakutsk */
166 zs( 900, "blt"), /* Belau */
167 zs( 900, "mlt"), /* Moluccas */
168 zd( 900, "vsk", "vsd"),/* Vladivostok */
169 zd( 930, "cst", "cst"),/* Central Australia */
170 zs( 1000, "gst"), /* Guam */
171 zd( 1000, "gsk", "gsd"),/* Magadan */
172 zd( 1000, "est", "est"),/* Eastern Australia */
173 zd( 1100,"lhst","lhst"),/* Lord Howe */
174 zd( 1100, "psk", "psd"),/* Petropavlovsk-Kamchatski */
175 zs( 1100,"ncst"), /* New Caledonia */
176 zs( 1130,"nrft"), /* Norfolk */
177 zd( 1200, "ask", "asd"),/* Anadyr */
178 zs( 1245,"nz-chat"), /* Chatham */
179 zs( 1300, "tgt"), /* Tongatapu */
187 struct name_val
const table
[];
188 /* Look for a prefix of S in TABLE, returning val for first matching entry. */
191 char buf
[NAME_LENGTH_MAXIMUM
];
193 for (j
= 0; j
< NAME_LENGTH_MAXIMUM
; j
++) {
194 unsigned char c
= *s
++;
195 buf
[j
] = isupper (c
) ? tolower (c
) : c
;
199 for (; table
[0].name
[0]; table
++)
200 for (j
= 0; buf
[j
] == table
[0].name
[j
]; )
201 if (++j
== NAME_LENGTH_MAXIMUM
|| !table
[0].name
[j
])
209 undefine (t
) struct partime
*t
;
210 /* Set *T to ``undefined'' values. */
212 t
->tm
.tm_sec
= t
->tm
.tm_min
= t
->tm
.tm_hour
= t
->tm
.tm_mday
= t
->tm
.tm_mon
213 = t
->tm
.tm_year
= t
->tm
.tm_wday
= t
->tm
.tm_yday
214 = t
->ymodulus
= t
->yweek
216 t
->zone
= TM_UNDEFINED_ZONE
;
220 * Array of patterns to look for in a date string.
221 * Order is important: we look for the first matching pattern
222 * whose values do not contradict values that we already know about.
223 * See `parse_pattern_letter' below for the meaning of the pattern codes.
225 static char const * const patterns
[] = {
227 * These traditional patterns must come first,
228 * to prevent an ISO 8601 format from misinterpreting their prefixes.
230 "E_n_y", "x", /* RFC 822 */
231 "E_n", "n_E", "n", "t:m:s_A", "t:m_A", "t_A", /* traditional */
232 "y/N/D$", /* traditional RCS */
234 /* ISO 8601:1988 formats, generalized a bit. */
235 "y-N-D$", "4ND$", "Y-N$",
236 "RND$", "-R=N$", "-R$", "--N=D$", "N=DT",
237 "--N$", "---D$", "DT",
238 "Y-d$", "4d$", "R=d$", "-d$", "dT",
239 "y-W-X", "yWX", "y=W",
240 "-r-W-X", "r-W-XT", "-rWX", "rWXT", "-W=X", "W=XT", "-W",
241 "-w-X", "w-XT", "---X$", "XT", "4$",
243 "h:m:s$", "hms$", "h:m$", "hm$", "h$", "-m:s$", "-ms$", "-m$", "--s$",
250 parse_prefix (str
, t
, pi
) char const *str
; struct partime
*t
; int *pi
;
252 * Parse an initial prefix of STR, setting *T accordingly.
253 * Return the first character after the prefix, or 0 if it couldn't be parsed.
254 * Start with pattern *PI; if success, set *PI to the next pattern to try.
255 * Set *PI to -1 if we know there are no more patterns to try;
256 * if *PI is initially negative, give up immediately.
266 /* Remove initial noise. */
267 while (!isalnum (c
= *str
) && c
!= '-' && c
!= '+') {
276 /* Try a pattern until one succeeds. */
277 while ((pat
= patterns
[i
++]) != 0) {
285 } while ((s
= parse_pattern_letter (s
, c
, t
)) != 0);
292 parse_fixed (s
, digits
, res
) char const *s
; int digits
, *res
;
294 * Parse an initial prefix of S of length DIGITS; it must be a number.
295 * Store the parsed number into *RES.
296 * Return the first character after the prefix, or 0 if it couldn't be parsed.
300 char const *lim
= s
+ digits
;
302 unsigned d
= *s
++ - '0';
312 parse_ranged (s
, digits
, lo
, hi
, res
) char const *s
; int digits
, lo
, hi
, *res
;
314 * Parse an initial prefix of S of length DIGITS;
315 * it must be a number in the range LO through HI.
316 * Store the parsed number into *RES.
317 * Return the first character after the prefix, or 0 if it couldn't be parsed.
320 s
= parse_fixed (s
, digits
, res
);
321 return s
&& lo
<=*res
&& *res
<=hi
? s
: 0;
325 parse_decimal (s
, digits
, lo
, hi
, resolution
, res
, fres
)
327 int digits
, lo
, hi
, resolution
, *res
, *fres
;
329 * Parse an initial prefix of S of length DIGITS;
330 * it must be a number in the range LO through HI
331 * and it may be followed by a fraction that is to be computed using RESOLUTION.
332 * Store the parsed number into *RES; store the fraction times RESOLUTION,
333 * rounded to the nearest integer, into *FRES.
334 * Return the first character after the prefix, or 0 if it couldn't be parsed.
337 s
= parse_fixed (s
, digits
, res
);
338 if (s
&& lo
<=*res
&& *res
<=hi
) {
340 if ((s
[0]==',' || s
[0]=='.') && isdigit ((unsigned char) s
[1])) {
341 char const *s1
= ++s
;
342 int num10
= 0, denom10
= 10, product
;
343 while (isdigit ((unsigned char) *++s
))
345 s
= parse_fixed (s1
, s
- s1
, &num10
);
346 product
= num10
*resolution
;
347 f
= (product
+ (denom10
>>1)) / denom10
;
348 f
-= f
& (product
%denom10
== denom10
>>1); /* round to even */
349 if (f
< 0 || product
/resolution
!= num10
)
350 return 0; /* overflow */
359 parzone (s
, zone
) char const *s
; long *zone
;
361 * Parse an initial prefix of S; it must denote a time zone.
362 * Set *ZONE to the number of seconds east of GMT,
363 * or to TM_LOCAL_ZONE if it is the local time zone.
364 * Return the first character after the prefix, or 0 if it couldn't be parsed.
369 int minutesEastOfUTC
;
373 * The formats are LT, n, n DST, nDST, no, o
374 * where n is a time zone name
375 * and o is a time zone offset of the form [-+]hh[:mm[:ss]].
383 minutesEastOfUTC
= lookup (s
, zone_names
);
384 if (minutesEastOfUTC
== -1)
387 /* Don't bother to check rest of spelling. */
388 while (isalpha ((unsigned char) *s
))
391 /* Don't modify LT. */
392 if (minutesEastOfUTC
== 1) {
393 *zone
= TM_LOCAL_ZONE
;
397 z
= minutesEastOfUTC
* 60L;
399 /* Look for trailing " DST". */
401 (s
[-1]=='T' || s
[-1]=='t') &&
402 (s
[-2]=='S' || s
[-2]=='s') &&
403 (s
[-3]=='D' || s
[-3]=='t')
406 while (isspace ((unsigned char) *s
))
409 (s
[0]=='D' || s
[0]=='d') &&
410 (s
[1]=='S' || s
[1]=='s') &&
411 (s
[2]=='T' || s
[2]=='t')
420 case '-': case '+': break;
421 default: return (char *) s
;
426 if (!(s
= parse_ranged (s
, 2, 0, 23, &hh
)))
431 if (isdigit ((unsigned char) *s
)) {
432 if (!(s
= parse_ranged (s
, 2, 0, 59, &mm
)))
434 if (*s
==':' && s
[-3]==':' && isdigit ((unsigned char) s
[1])) {
435 if (!(s
= parse_ranged (s
+ 1, 2, 0, 59, &ss
)))
439 if (isdigit ((unsigned char) *s
))
441 offset
= (hh
*60 + mm
)*60L + ss
;
442 *zone
= z
+ (sign
=='-' ? -offset
: offset
);
444 * ?? Are fractions allowed here?
445 * If so, they're not implemented.
451 parse_pattern_letter (s
, c
, t
) char const *s
; int c
; struct partime
*t
;
453 * Parse an initial prefix of S, matching the pattern whose code is C.
454 * Set *T accordingly.
455 * Return the first character after the prefix, or 0 if it couldn't be parsed.
459 case '$': /* The next character must be a non-digit. */
460 if (isdigit ((unsigned char) *s
))
464 case '-': case '/': case ':':
465 /* These characters stand for themselves. */
470 case '4': /* 4-digit year */
471 s
= parse_fixed (s
, 4, &t
->tm
.tm_year
);
474 case '=': /* optional '-' */
478 case 'A': /* AM or PM */
480 * This matches the regular expression [AaPp][Mm]?.
481 * It must not be followed by a letter or digit;
482 * otherwise it would match prefixes of strings like "PST".
486 if (t
->tm
.tm_hour
== 12)
491 if (t
->tm
.tm_hour
!= 12)
498 case 'M': case 'm': s
++; break;
504 case 'D': /* day of month [01-31] */
505 s
= parse_ranged (s
, 2, 1, 31, &t
->tm
.tm_mday
);
508 case 'd': /* day of year [001-366] */
509 s
= parse_ranged (s
, 3, 1, 366, &t
->tm
.tm_yday
);
513 case 'E': /* extended day of month [1-9, 01-31] */
514 s
= parse_ranged (s
, (
515 isdigit ((unsigned char) s
[0]) &&
516 isdigit ((unsigned char) s
[1])
517 ) + 1, 1, 31, &t
->tm
.tm_mday
);
520 case 'h': /* hour [00-23 followed by optional fraction] */
523 s
= parse_decimal (s
, 2, 0, 23, 60*60, &t
->tm
.tm_hour
, &frac
);
524 t
->tm
.tm_min
= frac
/ 60;
525 t
->tm
.tm_sec
= frac
% 60;
529 case 'm': /* minute [00-59 followed by optional fraction] */
530 s
= parse_decimal (s
, 2, 0, 59, 60, &t
->tm
.tm_min
, &t
->tm
.tm_sec
);
533 case 'n': /* month name [e.g. "Jan"] */
534 if (!TM_DEFINED (t
->tm
.tm_mon
= lookup (s
, month_names
)))
536 /* Don't bother to check rest of spelling. */
537 while (isalpha ((unsigned char) *s
))
541 case 'N': /* month [01-12] */
542 s
= parse_ranged (s
, 2, 1, 12, &t
->tm
.tm_mon
);
546 case 'r': /* year % 10 (remainder in origin-0 decade) [0-9] */
547 s
= parse_fixed (s
, 1, &t
->tm
.tm_year
);
552 case 'R': /* year % 100 (remainder in origin-0 century) [00-99] */
553 s
= parse_fixed (s
, 2, &t
->tm
.tm_year
);
557 case 's': /* second [00-60 followed by optional fraction] */
560 s
= parse_decimal (s
, 2, 0, 60, 1, &t
->tm
.tm_sec
, &frac
);
561 t
->tm
.tm_sec
+= frac
;
565 case 'T': /* 'T' or 't' */
567 case 'T': case 't': break;
572 case 't': /* traditional hour [1-9 or 01-12] */
573 s
= parse_ranged (s
, (
574 isdigit ((unsigned char) s
[0]) && isdigit ((unsigned char) s
[1])
575 ) + 1, 1, 12, &t
->tm
.tm_hour
);
578 case 'w': /* 'W' or 'w' only (stands for current week) */
580 case 'W': case 'w': break;
585 case 'W': /* 'W' or 'w', followed by a week of year [00-53] */
587 case 'W': case 'w': break;
590 s
= parse_ranged (s
, 2, 0, 53, &t
->yweek
);
593 case 'X': /* weekday (1=Mon ... 7=Sun) [1-7] */
594 s
= parse_ranged (s
, 1, 1, 7, &t
->tm
.tm_wday
);
598 case 'x': /* weekday name [e.g. "Sun"] */
599 if (!TM_DEFINED (t
->tm
.tm_wday
= lookup (s
, weekday_names
)))
601 /* Don't bother to check rest of spelling. */
602 while (isalpha ((unsigned char) *s
))
606 case 'y': /* either R or Y */
608 isdigit ((unsigned char) s
[0]) &&
609 isdigit ((unsigned char) s
[1]) &&
610 !isdigit ((unsigned char) s
[2])
614 case 'Y': /* year in full [4 or more digits] */
617 while (isdigit ((unsigned char) s
[len
]))
621 s
= parse_fixed (s
, len
, &t
->tm
.tm_year
);
625 case 'Z': /* time zone */
626 s
= parzone (s
, &t
->zone
);
629 case '_': /* possibly empty sequence of non-alphanumerics */
630 while (!isalnum (*s
) && *s
)
634 default: /* bad pattern */
641 merge_partime (t
, u
) struct partime
*t
; struct partime
const *u
;
643 * If there is no conflict, merge into *T the additional information in *U
644 * and return 0. Otherwise do nothing and return -1.
647 # define conflict(a,b) ((a) != (b) && TM_DEFINED (a) && TM_DEFINED (b))
649 conflict (t
->tm
.tm_sec
, u
->tm
.tm_sec
) ||
650 conflict (t
->tm
.tm_min
, u
->tm
.tm_min
) ||
651 conflict (t
->tm
.tm_hour
, u
->tm
.tm_hour
) ||
652 conflict (t
->tm
.tm_mday
, u
->tm
.tm_mday
) ||
653 conflict (t
->tm
.tm_mon
, u
->tm
.tm_mon
) ||
654 conflict (t
->tm
.tm_year
, u
->tm
.tm_year
) ||
655 conflict (t
->tm
.tm_wday
, u
->tm
.tm_yday
) ||
656 conflict (t
->ymodulus
, u
->ymodulus
) ||
657 conflict (t
->yweek
, u
->yweek
) ||
659 t
->zone
!= u
->zone
&&
660 t
->zone
!= TM_UNDEFINED_ZONE
&&
661 u
->zone
!= TM_UNDEFINED_ZONE
666 # define merge_(a,b) if (TM_DEFINED (b)) (a) = (b);
667 merge_ (t
->tm
.tm_sec
, u
->tm
.tm_sec
)
668 merge_ (t
->tm
.tm_min
, u
->tm
.tm_min
)
669 merge_ (t
->tm
.tm_hour
, u
->tm
.tm_hour
)
670 merge_ (t
->tm
.tm_mday
, u
->tm
.tm_mday
)
671 merge_ (t
->tm
.tm_mon
, u
->tm
.tm_mon
)
672 merge_ (t
->tm
.tm_year
, u
->tm
.tm_year
)
673 merge_ (t
->tm
.tm_wday
, u
->tm
.tm_yday
)
674 merge_ (t
->ymodulus
, u
->ymodulus
)
675 merge_ (t
->yweek
, u
->yweek
)
677 if (u
->zone
!= TM_UNDEFINED_ZONE
) t
->zone
= u
->zone
;
682 partime (s
, t
) char const *s
; struct partime
*t
;
684 * Parse a date/time prefix of S, putting the parsed result into *T.
685 * Return the first character after the prefix.
686 * The prefix may contain no useful information;
687 * in that case, *T will contain only undefined values.
697 if (!(s1
= parse_prefix (s
, &p
, &i
)))
699 } while (merge_partime (t
, &p
) != 0);