111894Speter/* Parse a string, yielding a struct partime that describes it. */ 29Sjkh 311894Speter/* Copyright 1993, 1994, 1995 Paul Eggert 411894Speter Distributed under license by the Free Software Foundation, Inc. 59Sjkh 611894SpeterThis file is part of RCS. 79Sjkh 811894SpeterRCS is free software; you can redistribute it and/or modify 911894Speterit under the terms of the GNU General Public License as published by 1011894Speterthe Free Software Foundation; either version 2, or (at your option) 1111894Speterany later version. 129Sjkh 1311894SpeterRCS is distributed in the hope that it will be useful, 1411894Speterbut WITHOUT ANY WARRANTY; without even the implied warranty of 1511894SpeterMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1611894SpeterGNU General Public License for more details. 179Sjkh 1811894SpeterYou should have received a copy of the GNU General Public License 1911894Speteralong with RCS; see the file COPYING. 2011894SpeterIf not, write to the Free Software Foundation, 2111894Speter59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 229Sjkh 2311894SpeterReport problems and direct all questions to: 249Sjkh 2511894Speter rcs-bugs@cs.purdue.edu 269Sjkh 2711894Speter*/ 289Sjkh 2911894Speter#if has_conf_h 3011894Speter# include "conf.h" 3111894Speter#else 3211894Speter# ifdef __STDC__ 3311894Speter# define P(x) x 3411894Speter# else 3511894Speter# define const 3611894Speter# define P(x) () 3711894Speter# endif 3811894Speter# include <limits.h> 3911894Speter# include <time.h> 4011894Speter#endif 419Sjkh 4211894Speter#include <ctype.h> 4311894Speter#undef isdigit 4411894Speter#define isdigit(c) (((unsigned)(c)-'0') <= 9) /* faster than stock */ 459Sjkh 4611894Speter#include "partime.h" 479Sjkh 4811894Speterchar const partimeId[] 4950472Speter = "$FreeBSD$"; 509Sjkh 519Sjkh 5211894Speter/* Lookup tables for names of months, weekdays, time zones. */ 539Sjkh 5411894Speter#define NAME_LENGTH_MAXIMUM 4 559Sjkh 5611894Speterstruct name_val { 5711894Speter char name[NAME_LENGTH_MAXIMUM]; 5811894Speter int val; 599Sjkh}; 609Sjkh 6111894Speter 6211894Speterstatic char const *parse_decimal P((char const*,int,int,int,int,int*,int*)); 6311894Speterstatic char const *parse_fixed P((char const*,int,int*)); 6411894Speterstatic char const *parse_pattern_letter P((char const*,int,struct partime*)); 6511894Speterstatic char const *parse_prefix P((char const*,struct partime*,int*)); 6611894Speterstatic char const *parse_ranged P((char const*,int,int,int,int*)); 6711894Speterstatic int lookup P((char const*,struct name_val const[])); 6811894Speterstatic int merge_partime P((struct partime*, struct partime const*)); 6911894Speterstatic void undefine P((struct partime*)); 7011894Speter 7111894Speter 7211894Speterstatic struct name_val const month_names[] = { 7311894Speter {"jan",0}, {"feb",1}, {"mar",2}, {"apr",3}, {"may",4}, {"jun",5}, 7411894Speter {"jul",6}, {"aug",7}, {"sep",8}, {"oct",9}, {"nov",10}, {"dec",11}, 7511894Speter {"", TM_UNDEFINED} 769Sjkh}; 779Sjkh 7811894Speterstatic struct name_val const weekday_names[] = { 7911894Speter {"sun",0}, {"mon",1}, {"tue",2}, {"wed",3}, {"thu",4}, {"fri",5}, {"sat",6}, 8011894Speter {"", TM_UNDEFINED} 8111894Speter}; 829Sjkh 8311894Speter#define hr60nonnegative(t) ((t)/100 * 60 + (t)%100) 8411894Speter#define hr60(t) ((t)<0 ? -hr60nonnegative(-(t)) : hr60nonnegative(t)) 8511894Speter#define zs(t,s) {s, hr60(t)} 8611894Speter#define zd(t,s,d) zs(t, s), zs((t)+100, d) 8711894Speter 8811894Speterstatic struct name_val const zone_names[] = { 8911894Speter zs(-1000, "hst"), /* Hawaii */ 9011894Speter zd(-1000,"hast","hadt"),/* Hawaii-Aleutian */ 9111894Speter zd(- 900,"akst","akdt"),/* Alaska */ 9211894Speter zd(- 800, "pst", "pdt"),/* Pacific */ 9311894Speter zd(- 700, "mst", "mdt"),/* Mountain */ 9411894Speter zd(- 600, "cst", "cdt"),/* Central */ 9511894Speter zd(- 500, "est", "edt"),/* Eastern */ 9611894Speter zd(- 400, "ast", "adt"),/* Atlantic */ 9711894Speter zd(- 330, "nst", "ndt"),/* Newfoundland */ 9811894Speter zs( 000, "utc"), /* Coordinated Universal */ 9911894Speter zs( 000, "cut"), /* " */ 10011894Speter zs( 000, "ut"), /* Universal */ 10111894Speter zs( 000, "z"), /* Zulu (required by ISO 8601) */ 10211894Speter zd( 000, "gmt", "bst"),/* Greenwich Mean, British Summer */ 10311894Speter zs( 000, "wet"), /* Western Europe */ 10411894Speter zs( 100, "met"), /* Middle Europe */ 10511894Speter zs( 100, "cet"), /* Central Europe */ 10611894Speter zs( 200, "eet"), /* Eastern Europe */ 10711894Speter zs( 530, "ist"), /* India */ 10811894Speter zd( 900, "jst", "jdt"),/* Japan */ 10911894Speter zd( 900, "kst", "kdt"),/* Korea */ 11011894Speter zd( 1200,"nzst","nzdt"),/* New Zealand */ 11111894Speter { "lt", 1 }, 11211894Speter#if 0 11311894Speter /* The following names are duplicates or are not well attested. */ 11411894Speter zs(-1100, "sst"), /* Samoa */ 11511894Speter zs(-1000, "tht"), /* Tahiti */ 11611894Speter zs(- 930, "mqt"), /* Marquesas */ 11711894Speter zs(- 900, "gbt"), /* Gambier */ 11811894Speter zd(- 900, "yst", "ydt"),/* Yukon - name is no longer used */ 11911894Speter zs(- 830, "pit"), /* Pitcairn */ 12011894Speter zd(- 500, "cst", "cdt"),/* Cuba */ 12111894Speter zd(- 500, "ast", "adt"),/* Acre */ 12211894Speter zd(- 400, "wst", "wdt"),/* Western Brazil */ 12311894Speter zd(- 400, "ast", "adt"),/* Andes */ 12411894Speter zd(- 400, "cst", "cdt"),/* Chile */ 12511894Speter zs(- 300, "wgt"), /* Western Greenland */ 12611894Speter zd(- 300, "est", "edt"),/* Eastern South America */ 12711894Speter zs(- 300, "mgt"), /* Middle Greenland */ 12811894Speter zd(- 200, "fst", "fdt"),/* Fernando de Noronha */ 12911894Speter zs(- 100, "egt"), /* Eastern Greenland */ 13011894Speter zs(- 100, "aat"), /* Atlantic Africa */ 13111894Speter zs(- 100, "act"), /* Azores and Canaries */ 13211894Speter zs( 000, "wat"), /* West Africa */ 13311894Speter zs( 100, "cat"), /* Central Africa */ 13411894Speter zd( 100, "mez","mesz"),/* Mittel-Europaeische Zeit */ 13511894Speter zs( 200, "sat"), /* South Africa */ 13611894Speter zd( 200, "ist", "idt"),/* Israel */ 13711894Speter zs( 300, "eat"), /* East Africa */ 13811894Speter zd( 300, "ast", "adt"),/* Arabia */ 13911894Speter zd( 300, "msk", "msd"),/* Moscow */ 14011894Speter zd( 330, "ist", "idt"),/* Iran */ 14111894Speter zs( 400, "gst"), /* Gulf */ 14211894Speter zs( 400, "smt"), /* Seychelles & Mascarene */ 14311894Speter zd( 400, "esk", "esd"),/* Yekaterinburg */ 14411894Speter zd( 400, "bsk", "bsd"),/* Baku */ 14511894Speter zs( 430, "aft"), /* Afghanistan */ 14611894Speter zd( 500, "osk", "osd"),/* Omsk */ 14711894Speter zs( 500, "pkt"), /* Pakistan */ 14811894Speter zd( 500, "tsk", "tsd"),/* Tashkent */ 14911894Speter zs( 545, "npt"), /* Nepal */ 15011894Speter zs( 600, "bgt"), /* Bangladesh */ 15111894Speter zd( 600, "nsk", "nsd"),/* Novosibirsk */ 15211894Speter zs( 630, "bmt"), /* Burma */ 15311894Speter zs( 630, "cct"), /* Cocos */ 15411894Speter zs( 700, "ict"), /* Indochina */ 15511894Speter zs( 700, "jvt"), /* Java */ 15611894Speter zd( 700, "isk", "isd"),/* Irkutsk */ 15711894Speter zs( 800, "hkt"), /* Hong Kong */ 15811894Speter zs( 800, "pst"), /* Philippines */ 15911894Speter zs( 800, "sgt"), /* Singapore */ 16011894Speter zd( 800, "cst", "cdt"),/* China */ 16111894Speter zd( 800, "ust", "udt"),/* Ulan Bator */ 16211894Speter zd( 800, "wst", "wst"),/* Western Australia */ 16311894Speter zd( 800, "ysk", "ysd"),/* Yakutsk */ 16411894Speter zs( 900, "blt"), /* Belau */ 16511894Speter zs( 900, "mlt"), /* Moluccas */ 16611894Speter zd( 900, "vsk", "vsd"),/* Vladivostok */ 16711894Speter zd( 930, "cst", "cst"),/* Central Australia */ 16811894Speter zs( 1000, "gst"), /* Guam */ 16911894Speter zd( 1000, "gsk", "gsd"),/* Magadan */ 17011894Speter zd( 1000, "est", "est"),/* Eastern Australia */ 17111894Speter zd( 1100,"lhst","lhst"),/* Lord Howe */ 17211894Speter zd( 1100, "psk", "psd"),/* Petropavlovsk-Kamchatski */ 17311894Speter zs( 1100,"ncst"), /* New Caledonia */ 17411894Speter zs( 1130,"nrft"), /* Norfolk */ 17511894Speter zd( 1200, "ask", "asd"),/* Anadyr */ 17611894Speter zs( 1245,"nz-chat"), /* Chatham */ 17711894Speter zs( 1300, "tgt"), /* Tongatapu */ 17811894Speter#endif 17911894Speter {"", -1} 18011894Speter}; 18111894Speter 1829Sjkh static int 18311894Speterlookup (s, table) 18411894Speter char const *s; 18511894Speter struct name_val const table[]; 18611894Speter/* Look for a prefix of S in TABLE, returning val for first matching entry. */ 1879Sjkh{ 18811894Speter int j; 18911894Speter char buf[NAME_LENGTH_MAXIMUM]; 19011894Speter 19111894Speter for (j = 0; j < NAME_LENGTH_MAXIMUM; j++) { 19211894Speter unsigned char c = *s++; 19311894Speter buf[j] = isupper (c) ? tolower (c) : c; 19411894Speter if (!isalpha (c)) 19511894Speter break; 1969Sjkh } 19711894Speter for (; table[0].name[0]; table++) 19811894Speter for (j = 0; buf[j] == table[0].name[j]; ) 19911894Speter if (++j == NAME_LENGTH_MAXIMUM || !table[0].name[j]) 20011894Speter goto done; 20111894Speter done: 20211894Speter return table[0].val; 20311894Speter} 20411894Speter 20511894Speter 20611894Speter static void 20711894Speterundefine (t) struct partime *t; 20811894Speter/* Set *T to ``undefined'' values. */ 20911894Speter{ 21011894Speter t->tm.tm_sec = t->tm.tm_min = t->tm.tm_hour = t->tm.tm_mday = t->tm.tm_mon 21111894Speter = t->tm.tm_year = t->tm.tm_wday = t->tm.tm_yday 21211894Speter = t->ymodulus = t->yweek 21311894Speter = TM_UNDEFINED; 21411894Speter t->zone = TM_UNDEFINED_ZONE; 21511894Speter} 21611894Speter 21711894Speter/* 21811894Speter* Array of patterns to look for in a date string. 21911894Speter* Order is important: we look for the first matching pattern 22011894Speter* whose values do not contradict values that we already know about. 22111894Speter* See `parse_pattern_letter' below for the meaning of the pattern codes. 22211894Speter*/ 22311894Speterstatic char const * const patterns[] = { 22411894Speter /* 22511894Speter * These traditional patterns must come first, 22611894Speter * to prevent an ISO 8601 format from misinterpreting their prefixes. 22711894Speter */ 22811894Speter "E_n_y", "x", /* RFC 822 */ 22911894Speter "E_n", "n_E", "n", "t:m:s_A", "t:m_A", "t_A", /* traditional */ 23011894Speter "y/N/D$", /* traditional RCS */ 23111894Speter 23211894Speter /* ISO 8601:1988 formats, generalized a bit. */ 23311894Speter "y-N-D$", "4ND$", "Y-N$", 23411894Speter "RND$", "-R=N$", "-R$", "--N=D$", "N=DT", 23511894Speter "--N$", "---D$", "DT", 23611894Speter "Y-d$", "4d$", "R=d$", "-d$", "dT", 23711894Speter "y-W-X", "yWX", "y=W", 23811894Speter "-r-W-X", "r-W-XT", "-rWX", "rWXT", "-W=X", "W=XT", "-W", 23911894Speter "-w-X", "w-XT", "---X$", "XT", "4$", 24011894Speter "T", 24111894Speter "h:m:s$", "hms$", "h:m$", "hm$", "h$", "-m:s$", "-ms$", "-m$", "--s$", 24211894Speter "Y", "Z", 24311894Speter 24411894Speter 0 24511894Speter}; 24611894Speter 24711894Speter static char const * 24811894Speterparse_prefix (str, t, pi) char const *str; struct partime *t; int *pi; 24911894Speter/* 25011894Speter* Parse an initial prefix of STR, setting *T accordingly. 25111894Speter* Return the first character after the prefix, or 0 if it couldn't be parsed. 25211894Speter* Start with pattern *PI; if success, set *PI to the next pattern to try. 25311894Speter* Set *PI to -1 if we know there are no more patterns to try; 25411894Speter* if *PI is initially negative, give up immediately. 25511894Speter*/ 25611894Speter{ 25711894Speter int i = *pi; 25811894Speter char const *pat; 25911894Speter unsigned char c; 26011894Speter 26111894Speter if (i < 0) 26211894Speter return 0; 26311894Speter 26411894Speter /* Remove initial noise. */ 26511894Speter while (!isalnum (c = *str) && c != '-' && c != '+') { 26611894Speter if (!c) { 26711894Speter undefine (t); 26811894Speter *pi = -1; 26911894Speter return str; 27011894Speter } 27111894Speter str++; 27211894Speter } 27311894Speter 27411894Speter /* Try a pattern until one succeeds. */ 27511894Speter while ((pat = patterns[i++]) != 0) { 27611894Speter char const *s = str; 27711894Speter undefine (t); 27811894Speter do { 27911894Speter if (!(c = *pat++)) { 28011894Speter *pi = i; 28111894Speter return s; 28211894Speter } 28311894Speter } while ((s = parse_pattern_letter (s, c, t)) != 0); 28411894Speter } 28511894Speter 2869Sjkh return 0; 2879Sjkh} 2889Sjkh 28911894Speter static char const * 29011894Speterparse_fixed (s, digits, res) char const *s; int digits, *res; 29111894Speter/* 29211894Speter* Parse an initial prefix of S of length DIGITS; it must be a number. 29311894Speter* Store the parsed number into *RES. 29411894Speter* Return the first character after the prefix, or 0 if it couldn't be parsed. 29511894Speter*/ 2969Sjkh{ 29711894Speter int n = 0; 29811894Speter char const *lim = s + digits; 29911894Speter while (s < lim) { 30011894Speter unsigned d = *s++ - '0'; 30111894Speter if (9 < d) 30211894Speter return 0; 30311894Speter n = 10*n + d; 30411894Speter } 30511894Speter *res = n; 30611894Speter return s; 30711894Speter} 3089Sjkh 30911894Speter static char const * 31011894Speterparse_ranged (s, digits, lo, hi, res) char const *s; int digits, lo, hi, *res; 31111894Speter/* 31211894Speter* Parse an initial prefix of S of length DIGITS; 31311894Speter* it must be a number in the range LO through HI. 31411894Speter* Store the parsed number into *RES. 31511894Speter* Return the first character after the prefix, or 0 if it couldn't be parsed. 31611894Speter*/ 31711894Speter{ 31811894Speter s = parse_fixed (s, digits, res); 31911894Speter return s && lo<=*res && *res<=hi ? s : 0; 32011894Speter} 3219Sjkh 32211894Speter static char const * 32311894Speterparse_decimal (s, digits, lo, hi, resolution, res, fres) 32411894Speter char const *s; 32511894Speter int digits, lo, hi, resolution, *res, *fres; 32611894Speter/* 32711894Speter* Parse an initial prefix of S of length DIGITS; 32811894Speter* it must be a number in the range LO through HI 32911894Speter* and it may be followed by a fraction that is to be computed using RESOLUTION. 33011894Speter* Store the parsed number into *RES; store the fraction times RESOLUTION, 33111894Speter* rounded to the nearest integer, into *FRES. 33211894Speter* Return the first character after the prefix, or 0 if it couldn't be parsed. 33311894Speter*/ 33411894Speter{ 33511894Speter s = parse_fixed (s, digits, res); 33611894Speter if (s && lo<=*res && *res<=hi) { 33711894Speter int f = 0; 33811894Speter if ((s[0]==',' || s[0]=='.') && isdigit ((unsigned char) s[1])) { 33911894Speter char const *s1 = ++s; 34011894Speter int num10 = 0, denom10 = 10, product; 34111894Speter while (isdigit ((unsigned char) *++s)) 34211894Speter denom10 *= 10; 34311894Speter s = parse_fixed (s1, s - s1, &num10); 34411894Speter product = num10*resolution; 34511894Speter f = (product + (denom10>>1)) / denom10; 34611894Speter f -= f & (product%denom10 == denom10>>1); /* round to even */ 34711894Speter if (f < 0 || product/resolution != num10) 34811894Speter return 0; /* overflow */ 34911894Speter } 35011894Speter *fres = f; 35111894Speter return s; 35211894Speter } 35311894Speter return 0; 35411894Speter} 35511894Speter 35611894Speter char * 35711894Speterparzone (s, zone) char const *s; long *zone; 35811894Speter/* 35911894Speter* Parse an initial prefix of S; it must denote a time zone. 36011894Speter* Set *ZONE to the number of seconds east of GMT, 36111894Speter* or to TM_LOCAL_ZONE if it is the local time zone. 36211894Speter* Return the first character after the prefix, or 0 if it couldn't be parsed. 36311894Speter*/ 36411894Speter{ 36511894Speter char sign; 36611894Speter int hh, mm, ss; 36711894Speter int minutesEastOfUTC; 36811894Speter long offset, z; 36911894Speter 37011894Speter /* 37111894Speter * The formats are LT, n, n DST, nDST, no, o 37211894Speter * where n is a time zone name 37311894Speter * and o is a time zone offset of the form [-+]hh[:mm[:ss]]. 37411894Speter */ 37511894Speter switch (*s) { 37611894Speter case '-': case '+': 37711894Speter z = 0; 3789Sjkh break; 37911894Speter 38011894Speter default: 38111894Speter minutesEastOfUTC = lookup (s, zone_names); 38211894Speter if (minutesEastOfUTC == -1) 3839Sjkh return 0; 38411894Speter 38511894Speter /* Don't bother to check rest of spelling. */ 38611894Speter while (isalpha ((unsigned char) *s)) 38711894Speter s++; 38811894Speter 38911894Speter /* Don't modify LT. */ 39011894Speter if (minutesEastOfUTC == 1) { 39111894Speter *zone = TM_LOCAL_ZONE; 39211894Speter return (char *) s; 39311894Speter } 39411894Speter 39511894Speter z = minutesEastOfUTC * 60L; 39611894Speter 39711894Speter /* Look for trailing " DST". */ 3989Sjkh if ( 39911894Speter (s[-1]=='T' || s[-1]=='t') && 40011894Speter (s[-2]=='S' || s[-2]=='s') && 40111894Speter (s[-3]=='D' || s[-3]=='t') 40211894Speter ) 40311894Speter goto trailing_dst; 40411894Speter while (isspace ((unsigned char) *s)) 40511894Speter s++; 40611894Speter if ( 40711894Speter (s[0]=='D' || s[0]=='d') && 40811894Speter (s[1]=='S' || s[1]=='s') && 40911894Speter (s[2]=='T' || s[2]=='t') 4109Sjkh ) { 41111894Speter s += 3; 41211894Speter trailing_dst: 41311894Speter *zone = z + 60*60; 41411894Speter return (char *) s; 4159Sjkh } 4169Sjkh 41711894Speter switch (*s) { 41811894Speter case '-': case '+': break; 41911894Speter default: return (char *) s; 42011894Speter } 42111894Speter } 42211894Speter sign = *s++; 42311894Speter 42411894Speter if (!(s = parse_ranged (s, 2, 0, 23, &hh))) 42511894Speter return 0; 42611894Speter mm = ss = 0; 42711894Speter if (*s == ':') 42811894Speter s++; 42911894Speter if (isdigit ((unsigned char) *s)) { 43011894Speter if (!(s = parse_ranged (s, 2, 0, 59, &mm))) 4319Sjkh return 0; 43211894Speter if (*s==':' && s[-3]==':' && isdigit ((unsigned char) s[1])) { 43311894Speter if (!(s = parse_ranged (s + 1, 2, 0, 59, &ss))) 43411894Speter return 0; 43511894Speter } 4369Sjkh } 43711894Speter if (isdigit ((unsigned char) *s)) 43811894Speter return 0; 43911894Speter offset = (hh*60 + mm)*60L + ss; 44011894Speter *zone = z + (sign=='-' ? -offset : offset); 44111894Speter /* 44211894Speter * ?? Are fractions allowed here? 44311894Speter * If so, they're not implemented. 44411894Speter */ 44511894Speter return (char *) s; 44611894Speter} 4479Sjkh 44811894Speter static char const * 44911894Speterparse_pattern_letter (s, c, t) char const *s; int c; struct partime *t; 45011894Speter/* 45111894Speter* Parse an initial prefix of S, matching the pattern whose code is C. 45211894Speter* Set *T accordingly. 45311894Speter* Return the first character after the prefix, or 0 if it couldn't be parsed. 45411894Speter*/ 45511894Speter{ 45611894Speter switch (c) { 45711894Speter case '$': /* The next character must be a non-digit. */ 45811894Speter if (isdigit ((unsigned char) *s)) 45911894Speter return 0; 46011894Speter break; 4619Sjkh 46211894Speter case '-': case '/': case ':': 46311894Speter /* These characters stand for themselves. */ 46411894Speter if (*s++ != c) 46511894Speter return 0; 46611894Speter break; 4679Sjkh 46811894Speter case '4': /* 4-digit year */ 46911894Speter s = parse_fixed (s, 4, &t->tm.tm_year); 47011894Speter break; 4719Sjkh 47211894Speter case '=': /* optional '-' */ 47311894Speter s += *s == '-'; 47411894Speter break; 4759Sjkh 47611894Speter case 'A': /* AM or PM */ 47711894Speter /* 47811894Speter * This matches the regular expression [AaPp][Mm]?. 47911894Speter * It must not be followed by a letter or digit; 48011894Speter * otherwise it would match prefixes of strings like "PST". 48111894Speter */ 48211894Speter switch (*s++) { 48311894Speter case 'A': case 'a': 48411894Speter if (t->tm.tm_hour == 12) 48511894Speter t->tm.tm_hour = 0; 48611894Speter break; 4879Sjkh 48811894Speter case 'P': case 'p': 48911894Speter if (t->tm.tm_hour != 12) 49011894Speter t->tm.tm_hour += 12; 49111894Speter break; 4929Sjkh 49311894Speter default: return 0; 49411894Speter } 49511894Speter switch (*s) { 49611894Speter case 'M': case 'm': s++; break; 49711894Speter } 49811894Speter if (isalnum (*s)) 49911894Speter return 0; 50011894Speter break; 5019Sjkh 50211894Speter case 'D': /* day of month [01-31] */ 50311894Speter s = parse_ranged (s, 2, 1, 31, &t->tm.tm_mday); 50411894Speter break; 5059Sjkh 50611894Speter case 'd': /* day of year [001-366] */ 50711894Speter s = parse_ranged (s, 3, 1, 366, &t->tm.tm_yday); 50811894Speter t->tm.tm_yday--; 50911894Speter break; 5109Sjkh 51111894Speter case 'E': /* extended day of month [1-9, 01-31] */ 51211894Speter s = parse_ranged (s, ( 51311894Speter isdigit ((unsigned char) s[0]) && 51411894Speter isdigit ((unsigned char) s[1]) 51511894Speter ) + 1, 1, 31, &t->tm.tm_mday); 51611894Speter break; 5179Sjkh 51811894Speter case 'h': /* hour [00-23 followed by optional fraction] */ 51911894Speter { 52011894Speter int frac; 52111894Speter s = parse_decimal (s, 2, 0, 23, 60*60, &t->tm.tm_hour, &frac); 52211894Speter t->tm.tm_min = frac / 60; 52311894Speter t->tm.tm_sec = frac % 60; 52411894Speter } 52511894Speter break; 5269Sjkh 52711894Speter case 'm': /* minute [00-59 followed by optional fraction] */ 52811894Speter s = parse_decimal (s, 2, 0, 59, 60, &t->tm.tm_min, &t->tm.tm_sec); 52911894Speter break; 5309Sjkh 53111894Speter case 'n': /* month name [e.g. "Jan"] */ 53211894Speter if (!TM_DEFINED (t->tm.tm_mon = lookup (s, month_names))) 53311894Speter return 0; 53411894Speter /* Don't bother to check rest of spelling. */ 53511894Speter while (isalpha ((unsigned char) *s)) 53611894Speter s++; 53711894Speter break; 5389Sjkh 53911894Speter case 'N': /* month [01-12] */ 54011894Speter s = parse_ranged (s, 2, 1, 12, &t->tm.tm_mon); 54111894Speter t->tm.tm_mon--; 54211894Speter break; 5439Sjkh 54411894Speter case 'r': /* year % 10 (remainder in origin-0 decade) [0-9] */ 54511894Speter s = parse_fixed (s, 1, &t->tm.tm_year); 54611894Speter t->ymodulus = 10; 54711894Speter break; 5489Sjkh 54911894Speter case_R: 55011894Speter case 'R': /* year % 100 (remainder in origin-0 century) [00-99] */ 55111894Speter s = parse_fixed (s, 2, &t->tm.tm_year); 55211894Speter t->ymodulus = 100; 55311894Speter break; 55411894Speter 55511894Speter case 's': /* second [00-60 followed by optional fraction] */ 55611894Speter { 55711894Speter int frac; 55811894Speter s = parse_decimal (s, 2, 0, 60, 1, &t->tm.tm_sec, &frac); 55911894Speter t->tm.tm_sec += frac; 56011894Speter } 56111894Speter break; 56211894Speter 56311894Speter case 'T': /* 'T' or 't' */ 56411894Speter switch (*s++) { 56511894Speter case 'T': case 't': break; 56611894Speter default: return 0; 56711894Speter } 56811894Speter break; 56911894Speter 57011894Speter case 't': /* traditional hour [1-9 or 01-12] */ 57111894Speter s = parse_ranged (s, ( 57211894Speter isdigit ((unsigned char) s[0]) && isdigit ((unsigned char) s[1]) 57311894Speter ) + 1, 1, 12, &t->tm.tm_hour); 57411894Speter break; 57511894Speter 57611894Speter case 'w': /* 'W' or 'w' only (stands for current week) */ 57711894Speter switch (*s++) { 57811894Speter case 'W': case 'w': break; 57911894Speter default: return 0; 58011894Speter } 58111894Speter break; 58211894Speter 58311894Speter case 'W': /* 'W' or 'w', followed by a week of year [00-53] */ 58411894Speter switch (*s++) { 58511894Speter case 'W': case 'w': break; 58611894Speter default: return 0; 58711894Speter } 58811894Speter s = parse_ranged (s, 2, 0, 53, &t->yweek); 58911894Speter break; 59011894Speter 59111894Speter case 'X': /* weekday (1=Mon ... 7=Sun) [1-7] */ 59211894Speter s = parse_ranged (s, 1, 1, 7, &t->tm.tm_wday); 59311894Speter t->tm.tm_wday--; 59411894Speter break; 59511894Speter 59611894Speter case 'x': /* weekday name [e.g. "Sun"] */ 59711894Speter if (!TM_DEFINED (t->tm.tm_wday = lookup (s, weekday_names))) 5989Sjkh return 0; 59911894Speter /* Don't bother to check rest of spelling. */ 60011894Speter while (isalpha ((unsigned char) *s)) 60111894Speter s++; 60211894Speter break; 60311894Speter 60411894Speter case 'y': /* either R or Y */ 60511894Speter if ( 60611894Speter isdigit ((unsigned char) s[0]) && 60711894Speter isdigit ((unsigned char) s[1]) && 60811894Speter !isdigit ((unsigned char) s[2]) 60911894Speter ) 61011894Speter goto case_R; 61111894Speter /* fall into */ 61211894Speter case 'Y': /* year in full [4 or more digits] */ 61311894Speter { 61411894Speter int len = 0; 61511894Speter while (isdigit ((unsigned char) s[len])) 61611894Speter len++; 61711894Speter if (len < 4) 61811894Speter return 0; 61911894Speter s = parse_fixed (s, len, &t->tm.tm_year); 6209Sjkh } 62111894Speter break; 6229Sjkh 62311894Speter case 'Z': /* time zone */ 62411894Speter s = parzone (s, &t->zone); 62511894Speter break; 6269Sjkh 62711894Speter case '_': /* possibly empty sequence of non-alphanumerics */ 62811894Speter while (!isalnum (*s) && *s) 62911894Speter s++; 63011894Speter break; 63111894Speter 63211894Speter default: /* bad pattern */ 63311894Speter return 0; 63411894Speter } 63511894Speter return s; 6369Sjkh} 6379Sjkh 6389Sjkh static int 63911894Spetermerge_partime (t, u) struct partime *t; struct partime const *u; 64011894Speter/* 64111894Speter* If there is no conflict, merge into *T the additional information in *U 64211894Speter* and return 0. Otherwise do nothing and return -1. 64311894Speter*/ 6449Sjkh{ 64511894Speter# define conflict(a,b) ((a) != (b) && TM_DEFINED (a) && TM_DEFINED (b)) 64611894Speter if ( 64711894Speter conflict (t->tm.tm_sec, u->tm.tm_sec) || 64811894Speter conflict (t->tm.tm_min, u->tm.tm_min) || 64911894Speter conflict (t->tm.tm_hour, u->tm.tm_hour) || 65011894Speter conflict (t->tm.tm_mday, u->tm.tm_mday) || 65111894Speter conflict (t->tm.tm_mon, u->tm.tm_mon) || 65211894Speter conflict (t->tm.tm_year, u->tm.tm_year) || 65311894Speter conflict (t->tm.tm_wday, u->tm.tm_yday) || 65411894Speter conflict (t->ymodulus, u->ymodulus) || 65511894Speter conflict (t->yweek, u->yweek) || 65611894Speter ( 65711894Speter t->zone != u->zone && 65811894Speter t->zone != TM_UNDEFINED_ZONE && 65911894Speter u->zone != TM_UNDEFINED_ZONE 66011894Speter ) 66111894Speter ) 66211894Speter return -1; 66311894Speter# undef conflict 66411894Speter# define merge_(a,b) if (TM_DEFINED (b)) (a) = (b); 66511894Speter merge_ (t->tm.tm_sec, u->tm.tm_sec) 66611894Speter merge_ (t->tm.tm_min, u->tm.tm_min) 66711894Speter merge_ (t->tm.tm_hour, u->tm.tm_hour) 66811894Speter merge_ (t->tm.tm_mday, u->tm.tm_mday) 66911894Speter merge_ (t->tm.tm_mon, u->tm.tm_mon) 67011894Speter merge_ (t->tm.tm_year, u->tm.tm_year) 67111894Speter merge_ (t->tm.tm_wday, u->tm.tm_yday) 67211894Speter merge_ (t->ymodulus, u->ymodulus) 67311894Speter merge_ (t->yweek, u->yweek) 67411894Speter# undef merge_ 67511894Speter if (u->zone != TM_UNDEFINED_ZONE) t->zone = u->zone; 67611894Speter return 0; 6779Sjkh} 6789Sjkh 67911894Speter char * 68011894Speterpartime (s, t) char const *s; struct partime *t; 68111894Speter/* 68211894Speter* Parse a date/time prefix of S, putting the parsed result into *T. 68311894Speter* Return the first character after the prefix. 68411894Speter* The prefix may contain no useful information; 68511894Speter* in that case, *T will contain only undefined values. 68611894Speter*/ 6879Sjkh{ 68811894Speter struct partime p; 6899Sjkh 69011894Speter undefine (t); 69111894Speter while (*s) { 69211894Speter int i = 0; 69311894Speter char const *s1; 69411894Speter do { 69511894Speter if (!(s1 = parse_prefix (s, &p, &i))) 69611894Speter return (char *) s; 69711894Speter } while (merge_partime (t, &p) != 0); 69811894Speter s = s1; 69911894Speter } 70011894Speter return (char *) s; 7019Sjkh} 702