f169d6167ecde5260c1575edf5b728eee96527fb
[notmuch] / date.c
1 /* date.c - Date-parsing utility for the notmuch mail system.
2  *
3  *  Copyright © 2000-2009 Jeffrey Stedfast
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see http://www.gnu.org/licenses/ 
17  */
18
19 /* This code was originally written by from Jeffrey Stedfast
20  * as part of his GMime library (http://spruce.sourceforge.net/gmime/)
21  *
22  * Carl Worth <cworth@cworth.org> imported it into notmuch and removed
23  * some glib-isms.
24  */
25
26 #include "notmuch-private.h"
27
28 #include <time.h>
29
30 #ifndef FALSE
31 #define FALSE 0
32 #endif
33
34 #ifndef TRUE
35 #define TRUE 1
36 #endif
37
38 #define d(x)
39
40 #define GMIME_FOLD_PREENCODED  (GMIME_FOLD_LEN / 2)
41
42 /* date parser macros */
43 #define NUMERIC_CHARS          "1234567890"
44 #define WEEKDAY_CHARS          "SundayMondayTuesdayWednesdayThursdayFridaySaturday"
45 #define MONTH_CHARS            "JanuaryFebruaryMarchAprilMayJuneJulyAugustSeptemberOctoberNovemberDecember"
46 #define TIMEZONE_ALPHA_CHARS   "UTCGMTESTEDTCSTCDTMSTPSTPDTZAMNY()"
47 #define TIMEZONE_NUMERIC_CHARS "-+1234567890"
48 #define TIME_CHARS             "1234567890:"
49
50 #define DATE_TOKEN_NON_NUMERIC          (1 << 0)
51 #define DATE_TOKEN_NON_WEEKDAY          (1 << 1)
52 #define DATE_TOKEN_NON_MONTH            (1 << 2)
53 #define DATE_TOKEN_NON_TIME             (1 << 3)
54 #define DATE_TOKEN_HAS_COLON            (1 << 4)
55 #define DATE_TOKEN_NON_TIMEZONE_ALPHA   (1 << 5)
56 #define DATE_TOKEN_NON_TIMEZONE_NUMERIC (1 << 6)
57 #define DATE_TOKEN_HAS_SIGN             (1 << 7)
58
59 static unsigned char gmime_datetok_table[256] = {
60         128,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
61         111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
62         111,111,111,111,111,111,111,111, 79, 79,111,175,111,175,111,111,
63          38, 38, 38, 38, 38, 38, 38, 38, 38, 38,119,111,111,111,111,111,
64         111, 75,111, 79, 75, 79,105, 79,111,111,107,111,111, 73, 75,107,
65          79,111,111, 73, 77, 79,111,109,111, 79, 79,111,111,111,111,111,
66         111,105,107,107,109,105,111,107,105,105,111,111,107,107,105,105,
67         107,111,105,105,105,105,107,111,111,105,111,111,111,111,111,111,
68         111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
69         111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
70         111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
71         111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
72         111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
73         111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
74         111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
75         111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
76 };
77
78 /* hrm, is there a library for this stuff? */
79 static struct {
80         char *name;
81         int offset;
82 } tz_offsets [] = {
83         { "UT", 0 },
84         { "GMT", 0 },
85         { "EST", -500 },        /* these are all US timezones.  bloody yanks */
86         { "EDT", -400 },
87         { "CST", -600 },
88         { "CDT", -500 },
89         { "MST", -700 },
90         { "MDT", -600 },
91         { "PST", -800 },
92         { "PDT", -700 },
93         { "Z", 0 },
94         { "A", -100 },
95         { "M", -1200 },
96         { "N", 100 },
97         { "Y", 1200 },
98 };
99
100 static char *tm_months[] = {
101         "Jan", "Feb", "Mar", "Apr", "May", "Jun",
102         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
103 };
104
105 static char *tm_days[] = {
106         "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
107 };
108
109 /* This is where it gets ugly... */
110
111 typedef struct _date_token {
112         struct _date_token *next;
113         unsigned char mask;
114         const char *start;
115         size_t len;
116 } date_token;
117
118 #define date_token_free(tok) g_slice_free (date_token, tok)
119 #define date_token_new() g_slice_new (date_token)
120
121 static date_token *
122 datetok (const char *date)
123 {
124         date_token *tokens = NULL, *token, *tail = (date_token *) &tokens;
125         const char *start, *end;
126         unsigned char mask;
127         
128         start = date;
129         while (*start) {
130                 /* kill leading whitespace */
131                 while (*start == ' ' || *start == '\t')
132                         start++;
133                 
134                 if (*start == '\0')
135                         break;
136                 
137                 mask = gmime_datetok_table[(unsigned char) *start];
138                 
139                 /* find the end of this token */
140                 end = start + 1;
141                 while (*end && !strchr ("-/,\t\r\n ", *end))
142                         mask |= gmime_datetok_table[(unsigned char) *end++];
143                 
144                 if (end != start) {
145                         token = date_token_new ();
146                         token->next = NULL;
147                         token->start = start;
148                         token->len = end - start;
149                         token->mask = mask;
150                         
151                         tail->next = token;
152                         tail = token;
153                 }
154                 
155                 if (*end)
156                         start = end + 1;
157                 else
158                         break;
159         }
160         
161         return tokens;
162 }
163
164 static int
165 decode_int (const char *in, size_t inlen)
166 {
167         register const char *inptr;
168         int sign = 1, val = 0;
169         const char *inend;
170         
171         inptr = in;
172         inend = in + inlen;
173         
174         if (*inptr == '-') {
175                 sign = -1;
176                 inptr++;
177         } else if (*inptr == '+')
178                 inptr++;
179         
180         for ( ; inptr < inend; inptr++) {
181                 if (!(*inptr >= '0' && *inptr <= '9'))
182                         return -1;
183                 else
184                         val = (val * 10) + (*inptr - '0');
185         }
186         
187         val *= sign;
188         
189         return val;
190 }
191
192 #if 0
193 static int
194 get_days_in_month (int month, int year)
195 {
196         switch (month) {
197         case 1:
198         case 3:
199         case 5:
200         case 7:
201         case 8:
202         case 10:
203         case 12:
204                 return 31;
205         case 4:
206         case 6:
207         case 9:
208         case 11:
209                 return 30;
210         case 2:
211                 if (g_date_is_leap_year (year))
212                         return 29;
213                 else
214                         return 28;
215         default:
216                 return 0;
217         }
218 }
219 #endif
220
221 static int
222 get_wday (const char *in, size_t inlen)
223 {
224         int wday;
225         
226         g_return_val_if_fail (in != NULL, -1);
227         
228         if (inlen < 3)
229                 return -1;
230         
231         for (wday = 0; wday < 7; wday++) {
232                 if (!g_ascii_strncasecmp (in, tm_days[wday], 3))
233                         return wday;
234         }
235         
236         return -1;  /* unknown week day */
237 }
238
239 static int
240 get_mday (const char *in, size_t inlen)
241 {
242         int mday;
243         
244         g_return_val_if_fail (in != NULL, -1);
245         
246         mday = decode_int (in, inlen);
247         
248         if (mday < 0 || mday > 31)
249                 mday = -1;
250         
251         return mday;
252 }
253
254 static int
255 get_month (const char *in, size_t inlen)
256 {
257         int i;
258         
259         g_return_val_if_fail (in != NULL, -1);
260         
261         if (inlen < 3)
262                 return -1;
263         
264         for (i = 0; i < 12; i++) {
265                 if (!g_ascii_strncasecmp (in, tm_months[i], 3))
266                         return i;
267         }
268         
269         return -1;  /* unknown month */
270 }
271
272 static int
273 get_year (const char *in, size_t inlen)
274 {
275         int year;
276         
277         g_return_val_if_fail (in != NULL, -1);
278         
279         if ((year = decode_int (in, inlen)) == -1)
280                 return -1;
281         
282         if (year < 100)
283                 year += (year < 70) ? 2000 : 1900;
284         
285         if (year < 1969)
286                 return -1;
287         
288         return year;
289 }
290
291 static gboolean
292 get_time (const char *in, size_t inlen, int *hour, int *min, int *sec)
293 {
294         register const char *inptr;
295         int *val, colons = 0;
296         const char *inend;
297         
298         *hour = *min = *sec = 0;
299         
300         inend = in + inlen;
301         val = hour;
302         for (inptr = in; inptr < inend; inptr++) {
303                 if (*inptr == ':') {
304                         colons++;
305                         switch (colons) {
306                         case 1:
307                                 val = min;
308                                 break;
309                         case 2:
310                                 val = sec;
311                                 break;
312                         default:
313                                 return FALSE;
314                         }
315                 } else if (!(*inptr >= '0' && *inptr <= '9'))
316                         return FALSE;
317                 else
318                         *val = (*val * 10) + (*inptr - '0');
319         }
320         
321         return TRUE;
322 }
323
324 static int
325 get_tzone (date_token **token)
326 {
327         const char *inptr, *inend;
328         size_t inlen;
329         int i, t;
330         
331         for (i = 0; *token && i < 2; *token = (*token)->next, i++) {
332                 inptr = (*token)->start;
333                 inlen = (*token)->len;
334                 inend = inptr + inlen;
335                 
336                 if (*inptr == '+' || *inptr == '-') {
337                         return decode_int (inptr, inlen);
338                 } else {
339                         if (*inptr == '(') {
340                                 inptr++;
341                                 if (*(inend - 1) == ')')
342                                         inlen -= 2;
343                                 else
344                                         inlen--;
345                         }
346                         
347                         for (t = 0; t < 15; t++) {
348                                 size_t len = strlen (tz_offsets[t].name);
349                                 
350                                 if (len != inlen)
351                                         continue;
352                                 
353                                 if (!strncmp (inptr, tz_offsets[t].name, len))
354                                         return tz_offsets[t].offset;
355                         }
356                 }
357         }
358         
359         return -1;
360 }
361
362 static time_t
363 mktime_utc (struct tm *tm)
364 {
365         time_t tt;
366         long tz;
367         
368         tm->tm_isdst = -1;
369         tt = mktime (tm);
370         
371 #if defined (G_OS_WIN32)
372         _get_timezone (&tz);
373         if (tm->tm_isdst > 0) {
374                 int dst;
375                 
376                 _get_dstbias (&dst);
377                 tz += dst;
378         }
379 #elif defined (HAVE_TM_GMTOFF)
380         tz = -tm->tm_gmtoff;
381 #elif defined (HAVE_TIMEZONE)
382         if (tm->tm_isdst > 0) {
383 #if defined (HAVE_ALTZONE)
384                 tz = altzone;
385 #else /* !defined (HAVE_ALTZONE) */
386                 tz = (timezone - 3600);
387 #endif
388         } else {
389                 tz = timezone;
390         }
391 #elif defined (HAVE__TIMEZONE)
392         tz = _timezone;
393 #else
394 #error Neither HAVE_TIMEZONE nor HAVE_TM_GMTOFF defined. Rerun autoheader, autoconf, etc.
395 #endif
396         
397         return tt - tz;
398 }
399
400 static time_t
401 parse_rfc822_date (date_token *tokens, int *tzone)
402 {
403         int hour, min, sec, offset, n;
404         date_token *token;
405         struct tm tm;
406         time_t t;
407         
408         g_return_val_if_fail (tokens != NULL, (time_t) 0);
409         
410         token = tokens;
411         
412         memset ((void *) &tm, 0, sizeof (struct tm));
413         
414         if ((n = get_wday (token->start, token->len)) != -1) {
415                 /* not all dates may have this... */
416                 tm.tm_wday = n;
417                 token = token->next;
418         }
419         
420         /* get the mday */
421         if (!token || (n = get_mday (token->start, token->len)) == -1)
422                 return (time_t) 0;
423         
424         tm.tm_mday = n;
425         token = token->next;
426         
427         /* get the month */
428         if (!token || (n = get_month (token->start, token->len)) == -1)
429                 return (time_t) 0;
430         
431         tm.tm_mon = n;
432         token = token->next;
433         
434         /* get the year */
435         if (!token || (n = get_year (token->start, token->len)) == -1)
436                 return (time_t) 0;
437         
438         tm.tm_year = n - 1900;
439         token = token->next;
440         
441         /* get the hour/min/sec */
442         if (!token || !get_time (token->start, token->len, &hour, &min, &sec))
443                 return (time_t) 0;
444         
445         tm.tm_hour = hour;
446         tm.tm_min = min;
447         tm.tm_sec = sec;
448         token = token->next;
449         
450         /* get the timezone */
451         if (!token || (n = get_tzone (&token)) == -1) {
452                 /* I guess we assume tz is GMT? */
453                 offset = 0;
454         } else {
455                 offset = n;
456         }
457         
458         t = mktime_utc (&tm);
459         
460         /* t is now GMT of the time we want, but not offset by the timezone ... */
461         
462         /* this should convert the time to the GMT equiv time */
463         t -= ((offset / 100) * 60 * 60) + (offset % 100) * 60;
464         
465         if (tzone)
466                 *tzone = offset;
467         
468         return t;
469 }
470
471
472 #define date_token_mask(t)  (((date_token *) t)->mask)
473 #define is_numeric(t)       ((date_token_mask (t) & DATE_TOKEN_NON_NUMERIC) == 0)
474 #define is_weekday(t)       ((date_token_mask (t) & DATE_TOKEN_NON_WEEKDAY) == 0)
475 #define is_month(t)         ((date_token_mask (t) & DATE_TOKEN_NON_MONTH) == 0)
476 #define is_time(t)          (((date_token_mask (t) & DATE_TOKEN_NON_TIME) == 0) && (date_token_mask (t) & DATE_TOKEN_HAS_COLON))
477 #define is_tzone_alpha(t)   ((date_token_mask (t) & DATE_TOKEN_NON_TIMEZONE_ALPHA) == 0)
478 #define is_tzone_numeric(t) (((date_token_mask (t) & DATE_TOKEN_NON_TIMEZONE_NUMERIC) == 0) && (date_token_mask (t) & DATE_TOKEN_HAS_SIGN))
479 #define is_tzone(t)         (is_tzone_alpha (t) || is_tzone_numeric (t))
480
481 static time_t
482 parse_broken_date (date_token *tokens, int *tzone)
483 {
484         gboolean got_wday, got_month, got_tzone;
485         int hour, min, sec, offset, n;
486         date_token *token;
487         struct tm tm;
488         time_t t;
489         
490         memset ((void *) &tm, 0, sizeof (struct tm));
491         got_wday = got_month = got_tzone = FALSE;
492         offset = 0;
493         
494         token = tokens;
495         while (token) {
496                 if (is_weekday (token) && !got_wday) {
497                         if ((n = get_wday (token->start, token->len)) != -1) {
498                                 d(printf ("weekday; "));
499                                 got_wday = TRUE;
500                                 tm.tm_wday = n;
501                                 goto next;
502                         }
503                 }
504                 
505                 if (is_month (token) && !got_month) {
506                         if ((n = get_month (token->start, token->len)) != -1) {
507                                 d(printf ("month; "));
508                                 got_month = TRUE;
509                                 tm.tm_mon = n;
510                                 goto next;
511                         }
512                 }
513                 
514                 if (is_time (token) && !tm.tm_hour && !tm.tm_min && !tm.tm_sec) {
515                         if (get_time (token->start, token->len, &hour, &min, &sec)) {
516                                 d(printf ("time; "));
517                                 tm.tm_hour = hour;
518                                 tm.tm_min = min;
519                                 tm.tm_sec = sec;
520                                 goto next;
521                         }
522                 }
523                 
524                 if (is_tzone (token) && !got_tzone) {
525                         date_token *t = token;
526                         
527                         if ((n = get_tzone (&t)) != -1) {
528                                 d(printf ("tzone; "));
529                                 got_tzone = TRUE;
530                                 offset = n;
531                                 goto next;
532                         }
533                 }
534                 
535                 if (is_numeric (token)) {
536                         if (token->len == 4 && !tm.tm_year) {
537                                 if ((n = get_year (token->start, token->len)) != -1) {
538                                         d(printf ("year; "));
539                                         tm.tm_year = n - 1900;
540                                         goto next;
541                                 }
542                         } else {
543                                 /* Note: assumes MM-DD-YY ordering if '0 < MM < 12' holds true */
544                                 if (!got_month && token->next && is_numeric (token->next)) {
545                                         if ((n = decode_int (token->start, token->len)) > 12) {
546                                                 goto mday;
547                                         } else if (n > 0) {
548                                                 d(printf ("mon; "));
549                                                 got_month = TRUE;
550                                                 tm.tm_mon = n - 1;
551                                         }
552                                         goto next;
553                                 } else if (!tm.tm_mday && (n = get_mday (token->start, token->len)) != -1) {
554                                 mday:
555                                         d(printf ("mday; "));
556                                         tm.tm_mday = n;
557                                         goto next;
558                                 } else if (!tm.tm_year) {
559                                         if ((n = get_year (token->start, token->len)) != -1) {
560                                                 d(printf ("2-digit year; "));
561                                                 tm.tm_year = n - 1900;
562                                         }
563                                         goto next;
564                                 }
565                         }
566                 }
567                 
568                 d(printf ("???; "));
569                 
570         next:
571                 
572                 token = token->next;
573         }
574         
575         d(printf ("\n"));
576         
577         t = mktime_utc (&tm);
578         
579         /* t is now GMT of the time we want, but not offset by the timezone ... */
580         
581         /* this should convert the time to the GMT equiv time */
582         t -= ((offset / 100) * 60 * 60) + (offset % 100) * 60;
583         
584         if (tzone)
585                 *tzone = offset;
586         
587         return t;
588 }
589
590 #if 0
591 static void
592 gmime_datetok_table_init (void)
593 {
594         int i;
595         
596         memset (gmime_datetok_table, 0, sizeof (gmime_datetok_table));
597         
598         for (i = 0; i < 256; i++) {
599                 if (!strchr (NUMERIC_CHARS, i))
600                         gmime_datetok_table[i] |= DATE_TOKEN_NON_NUMERIC;
601                 
602                 if (!strchr (WEEKDAY_CHARS, i))
603                         gmime_datetok_table[i] |= DATE_TOKEN_NON_WEEKDAY;
604                 
605                 if (!strchr (MONTH_CHARS, i))
606                         gmime_datetok_table[i] |= DATE_TOKEN_NON_MONTH;
607                 
608                 if (!strchr (TIME_CHARS, i))
609                         gmime_datetok_table[i] |= DATE_TOKEN_NON_TIME;
610                 
611                 if (!strchr (TIMEZONE_ALPHA_CHARS, i))
612                         gmime_datetok_table[i] |= DATE_TOKEN_NON_TIMEZONE_ALPHA;
613                 
614                 if (!strchr (TIMEZONE_NUMERIC_CHARS, i))
615                         gmime_datetok_table[i] |= DATE_TOKEN_NON_TIMEZONE_NUMERIC;
616                 
617                 if (((char) i) == ':')
618                         gmime_datetok_table[i] |= DATE_TOKEN_HAS_COLON;
619                 
620                 if (strchr ("+-", i))
621                         gmime_datetok_table[i] |= DATE_TOKEN_HAS_SIGN;
622         }
623         
624         printf ("static unsigned char gmime_datetok_table[256] = {");
625         for (i = 0; i < 256; i++) {
626                 if (i % 16 == 0)
627                         printf ("\n\t");
628                 printf ("%3d,", gmime_datetok_table[i]);
629         }
630         printf ("\n};\n");
631 }
632 #endif
633
634
635 /**
636  * g_mime_utils_header_decode_date:
637  * @str: input date string
638  * @tz_offset: timezone offset
639  *
640  * Decodes the rfc822 date string and saves the GMT offset into
641  * @tz_offset if non-NULL.
642  *
643  * Returns: the time_t representation of the date string specified by
644  * @str or (time_t) %0 on error. If @tz_offset is non-NULL, the value
645  * of the timezone offset will be stored.
646  **/
647 time_t
648 g_mime_utils_header_decode_date (const char *str, int *tz_offset)
649 {
650         date_token *token, *tokens;
651         time_t date;
652         
653         if (!(tokens = datetok (str))) {
654                 if (tz_offset)
655                         *tz_offset = 0;
656                 
657                 return (time_t) 0;
658         }
659         
660         if (!(date = parse_rfc822_date (tokens, tz_offset)))
661                 date = parse_broken_date (tokens, tz_offset);
662         
663         /* cleanup */
664         while (tokens) {
665                 token = tokens;
666                 tokens = tokens->next;
667                 date_token_free (token);
668         }
669         
670         return date;
671 }