6 * Manipulate character strings representing dates.
10 * #include <parsedate.h>
13 * struct parseddate *pd;
15 * pd = parsedate (date);
17 * compute_unixtime (pd);
19 * break_down_unixtime (pd);
21 * date = mail_date_string (pd);
23 * date = uucp_date_string (pd);
27 * The returned value from "parsedate", "mail_date_string", or
28 * "uucp_date_string" points to static data whose contents are
29 * overwritten by the next call to the same routine.
31 * "compute_unixtime" is implicitly called by "parsedate".
35 * struct parseddate *parsedate (date) char *date;
36 * Parse a character string representing a date and time into
37 * individual values in a "struct parseddate" data structure.
39 * compute_unixtime (pd) struct parseddate *pd;
40 * Given a mostly filled-in "struct parseddate", compute the day
41 * of the week and the internal UNIX representation of the date.
43 * break_down_unixtime (pd) struct parseddate *pd;
44 * Compute the date and time corresponding to the "unixtime" and
45 * "zone" values in a "struct parseddate".
47 * char *mail_date_string (pd) struct parseddate *pd;
48 * Generate a character string representing a date and time in
49 * the RFC822 (ARPANET mail standard) format.
51 * char *uucp_date_string (pd) struct parseddate *pd;
52 * Generate a character string representing a date and time in
53 * the UUCP mail format.
62 #include "parsedate.h"
64 static char *RCSID = "$Id: parsedate.c,v 1.2 2002-09-28 06:58:45 arjen Exp $";
68 void compute_unixtime (register struct parseddate *pd);
70 /* Number of seconds in various time intervals. */
71 #define SEC_PER_MIN 60
72 #define SEC_PER_HOUR (60*SEC_PER_MIN)
73 #define SEC_PER_DAY (24*SEC_PER_HOUR)
74 #define SEC_PER_YEAR (365*SEC_PER_DAY)
76 /* Number of days in each month. */
77 static int monthsize[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
79 /* Three-letter abbreviations of month and day names. */
80 static char monthname[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
81 static char dayname[] = "SunMonTueWedThuFriSat";
83 /* struct parseddate *parsedate (date) char *date;
84 * Analyze a character string representing a date and time. The
85 * returned value points to a data structure with the desired
86 * information. (NOTE: The returned value points to static data
87 * whose contents are overwritten by each call.)
89 struct parseddate * parsedate (char *date)
91 register int year_save;
92 extern struct parseddate yyans;
95 /* Initialize the returned-value structure. */
106 yyans.c_weekday = -1;
109 /* Parse the argument string. */
112 if (yyparse () != 0 && yyans.error == NULL)
113 yyans.error = yyinbuf;
115 /* Validate the day of the month, compute/validate the day of the
116 * week, and compute the internal UNIX form of the time. See if
117 * "compute_unixtime" found fault with the year or the day of the
118 * month. (Note that we have to remember the original "year" value
119 * because it might legitimately have been -1 to begin with.)
121 year_save = yyans.year; compute_unixtime (&yyans);
122 if (yyans.error == NULL
123 && (yyans.year != year_save
124 || (yyans.month > 0 && yyans.day < 0)
125 || (yyans.month < 0 && yyans.day > 0)))
126 yyans.error = yyinbuf;
131 /* compute_unixtime (pd) struct parseddate *pd;
132 * Given a mostly filled-in "struct parseddate", compute the day of
133 * the week and the internal UNIX representation of the date.
135 * A year before 1600 will be rejected and replaced with -1. A
136 * date from 1600 on which falls outside the range representable in
137 * internal UNIX form will still have the correct day of the week
140 * The day of the week is always computed on the assumption that the
141 * Gregorian calendar is in use. Days of the week for dates in the
142 * far future may turn out to be incorrect if any changes are made
143 * to the calendar between now and then.
145 void compute_unixtime (register struct parseddate *pd)
147 register int weekday, n, l, a;
149 /* Validate the year. */
150 if (pd->year >= 0 && pd->year < 1600) pd->year = -1;
152 /* Validate the day of the month. Also calculate the number of days
153 * in February (even if this is not February, we will need the num-
154 * ber of days in February later on when computing the UNIX time).
157 { if (pd->year < 0) monthsize[2] = 29;
158 else if (pd->year % 4 != 0) monthsize[2] = 28;
159 else if (pd->year % 100 != 0) monthsize[2] = 29;
160 else if (pd->year % 400 != 0) monthsize[2] = 28;
161 else monthsize[2] = 29;
162 if (pd->day <= 0 || pd->day > monthsize[pd->month])
166 /* Compute the day of the week. The next several lines constitute a
167 * perpetual-calendar formula. Note, of course, that the "claimed"
168 * day of the week (pd->c_weekday) is ignored here.
170 if (pd->year > 0 && pd->month > 0 && pd->day > 0)
171 { if (pd->month >= 3) n = pd->year / 100,
173 else n = (pd->year-1) / 100,
174 l = (pd->year-1) % 100;
175 a = (26 * ((pd->month+9)%12 + 1) - 2) / 10;
176 weekday = (a+(l>>2)+(n>>2)+l-(n+n)+pd->day);
177 while (weekday < 0) weekday += 7;
178 pd->weekday = weekday % 7;
181 /* Figure out the internal UNIX form of the date. */
182 if (pd->year >= 1969 && pd->year <= 2038
183 && pd->month > 0 && pd->day > 0
184 && pd->hour >= 0 && pd->minute >= 0
185 && pd->zone != -1 && pd->zone > -1440 && pd->zone < 1440)
187 SEC_PER_YEAR * (pd->year - 1970)
188 + SEC_PER_DAY * ((pd->year - 1969) / 4)
189 /* month is taken care of later */
190 + SEC_PER_DAY * (pd->day - 1)
191 + SEC_PER_HOUR * pd->hour
192 + SEC_PER_MIN * pd->minute
193 /* seconds are taken care of later */
194 - SEC_PER_MIN * pd->zone;
196 pd->unixtime += pd->second;
197 for (n = pd->month - 1; n > 0; n--)
198 pd->unixtime += SEC_PER_DAY * monthsize[n];
199 if (pd->unixtime < 0) pd->unixtime = -1;
201 else pd->unixtime = -1;
204 /* break_down_unixtime (pd) struct parseddate *pd;
205 * Given the "unixtime" and "zone" fields of a "struct parseddate",
206 * compute the values of the "year", "month", "day", "hour", "min-
207 * ute", "second", and "weekday" fields. The "dst" and "error"
208 * fields of the structure are not used or modified.
210 void break_down_unixtime (pd)
211 register struct parseddate *pd;
212 { register unsigned long timevalue;
215 /* Validate the "unixtime" and "zone" fields. */
217 || pd->zone == -1 || pd->zone <= -1440 || pd->zone >= 1440)
218 { /* Sorry, can't do it. */
219 pd->year = -1; pd->month = -1; pd->day = -1;
220 pd->hour = -1; pd->minute = -1; pd->second = -1;
225 /* Even though "pd->unixtime" must be non-negative, and thus cannot
226 * indicate a time earlier than 1970, a negative "pd->zone" could
227 * cause the local date to be Wednesday, 31 December 1969. Such a
228 * date requires special handling.
230 * A local date earlier than 31 December 1969 is impossible because
231 * "pd->zone" must represent a time-zone shift of less than a day.
233 if (pd->zone < 0 && pd->unixtime + SEC_PER_MIN * pd->zone < 0)
234 { pd->year = 1969; pd->month = 12; pd->day = 31;
235 pd->weekday = 3; /* Wednesday */
236 timevalue = pd->unixtime + SEC_PER_MIN * pd->zone + SEC_PER_DAY;
237 /* Note: 0 <= timevalue < SEC_PER_DAY */
238 pd->hour = timevalue / SEC_PER_HOUR;
239 pd->minute = (timevalue % SEC_PER_HOUR) / SEC_PER_MIN;
240 pd->second = timevalue % SEC_PER_MIN;
244 /* Handle the general case (local time is 1970 or later). */
245 timevalue = pd->unixtime + SEC_PER_MIN * pd->zone;
247 /* day of the week (1 January 1970 was a Thursday) . . . */
248 pd->weekday = (timevalue/SEC_PER_DAY + 4 /* Thursday */) % 7;
250 /* year (note that the only possible century year here is 2000,
251 * a leap year -- hence no special tests for century years are
254 for (m = 1970; ; m++)
255 { n = (m%4==0) ? SEC_PER_YEAR+SEC_PER_DAY : SEC_PER_YEAR;
256 if (n > timevalue) break;
260 monthsize[2] = (m%4==0) ? 29 : 28;
264 { n = SEC_PER_DAY * monthsize[m];
265 if (n > timevalue) break;
270 /* day, hour, minute, and second . . . */
271 pd->day = (timevalue / SEC_PER_DAY) + 1;
272 pd->hour = (timevalue % SEC_PER_DAY) / SEC_PER_HOUR;
273 pd->minute = (timevalue % SEC_PER_HOUR) / SEC_PER_MIN;
274 pd->second = timevalue % SEC_PER_MIN;
277 /* char *mail_date_string (pd) struct parseddate *pd;
278 * Generate a character string representing a date and time in the
279 * RFC822 (ARPANET mail standard) format. A value of NULL is re-
280 * turned if "pd" does not contain all necessary data fields.
281 * (NOTE: The returned value points to static data whose contents
282 * are overwritten by each call.)
285 mail_date_string (pd)
286 register struct parseddate *pd;
288 static char answer[50];
290 /* Check the day of the month and compute the day of the week. */
291 compute_unixtime (pd);
293 /* Make sure all required fields are present. */
294 if (pd->year < 0 || pd->month < 0 || pd->day < 0
295 || pd->hour < 0 || pd->minute < 0
296 || pd->zone == -1 || pd->zone <= -1440 || pd->zone >= 1440)
297 return NULL; /* impossible to generate string */
299 /* Generate the answer string. */
301 "%.3s, %d %.3s %d %02d:%02d",
302 dayname + 3*pd->weekday,
303 pd->day, monthname + 3*(pd->month-1),
304 (pd->year >= 1960 && pd->year <= 1999)
305 ? pd->year - 1900 : pd->year,
306 pd->hour, pd->minute);
307 c = answer + strlen (answer);
308 if (pd->second >= 0) sprintf (c, ":%02d", pd->second), c += 3;
311 { /* NOTE: Only zone abbreviations in RFC822 are used here. */
312 case 0: strcpy (c, pd->dst ? "+0000" : "GMT"); break;
313 case -240: strcpy (c, pd->dst ? "EDT" : "-0400"); break;
314 case -300: strcpy (c, pd->dst ? "CDT" : "EST"); break;
315 case -360: strcpy (c, pd->dst ? "MDT" : "CST"); break;
316 case -420: strcpy (c, pd->dst ? "PDT" : "MST"); break;
317 case -480: strcpy (c, pd->dst ? "-0800" : "PST"); break;
320 sprintf (c, "+%02d%02d", pd->zone/60, pd->zone%60);
321 else sprintf (c, "-%02d%02d", -pd->zone/60, -pd->zone%60);
327 /* char *uucp_date_string (pd) struct parseddate *pd;
328 * Generate a character string representing a date and time in the
329 * UUCP mail format. A value of NULL is returned if "pd" does not
330 * contain all necessary data fields. (NOTE: The returned value
331 * points to static data whose contents are overwritten by each
335 uucp_date_string (pd)
336 register struct parseddate *pd;
338 static char answer[50];
340 /* Check the day of the month and compute the day of the week. */
341 compute_unixtime (pd);
343 /* Make sure all required fields are present. */
344 if (pd->year < 0 || pd->month < 0 || pd->day < 0
345 || pd->hour < 0 || pd->minute < 0
346 || pd->zone == -1 || pd->zone <= -1440 || pd->zone >= 1440)
347 return NULL; /* impossible to generate string */
349 /* Generate the answer string. */
351 "%.3s %.3s %d %02d:%02d",
352 dayname + 3*pd->weekday,
353 monthname + 3*(pd->month-1), pd->day,
354 pd->hour, pd->minute);
355 c = answer + strlen (answer);
356 if (pd->second >= 0) sprintf (c, ":%02d", pd->second), c += 3;
358 { /* NOTE: Only zone abbreviations in RFC822 are used here. */
359 case 0: strcpy (c, pd->dst ? "+0000" : "-GMT"); break;
360 case -240: strcpy (c, pd->dst ? "-EDT" : "-0400"); break;
361 case -300: strcpy (c, pd->dst ? "-CDT" : "-EST"); break;
362 case -360: strcpy (c, pd->dst ? "-MDT" : "-CST"); break;
363 case -420: strcpy (c, pd->dst ? "-PDT" : "-MST"); break;
364 case -480: strcpy (c, pd->dst ? "-0800" : "-PST"); break;
367 sprintf (c, "+%02d%02d", pd->zone/60, pd->zone%60);
368 else sprintf (c, "-%02d%02d", -pd->zone/60, -pd->zone%60);
370 c = answer + strlen (answer);
371 sprintf (c, " %d", pd->year);