Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / broken-date-parser.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  *  Authors: Jeffrey Stedfast <fejj@ximian.com>
4  *
5  *  Copyright 2000 Ximian, Inc. (www.ximian.com)
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of version 2 of the GNU Lesser General Public
9  * License as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  *
21  */
22
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <ctype.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <time.h>
33 #include <sys/time.h>
34
35 #include <glib.h>
36
37 #include <libedataserver/e-time-utils.h>
38
39 #include "broken-date-parser.h"
40
41 #define d(x)
42
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 const unsigned char 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 shit? */
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 const char tm_months[][4] = {
101         "Jan", "Feb", "Mar", "Apr", "May", "Jun",
102         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
103 };
104
105 static const char tm_days[][4] = {
106         "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
107 };
108
109
110 struct _date_token {
111         struct _date_token *next;
112         const unsigned char *start;
113         unsigned int len;
114         unsigned int mask;
115 };
116
117 /* This is where it gets ugly... */
118 static struct _date_token *
119 datetok (const char *date)
120 {
121         struct _date_token *tokens = NULL, *token, *tail = (struct _date_token *) &tokens;
122         const unsigned char *start, *end;
123         unsigned int mask;
124         
125         start = date;
126         while (*start) {
127                 /* kill leading whitespace */
128                 while (*start && isspace ((int) *start))
129                         start++;
130                 
131                 if (*start == '\0')
132                         break;
133                 
134                 mask = datetok_table[*start];
135                 
136                 /* find the end of this token */
137                 end = start + 1;
138                 while (*end && !strchr ("-/,\t\r\n ", *end))
139                         mask |= datetok_table[*end++];
140                 
141                 if (end != start) {
142                         token = g_malloc (sizeof (struct _date_token));
143                         token->next = NULL;
144                         token->start = start;
145                         token->len = end - start;
146                         token->mask = mask;
147                         
148                         tail->next = token;
149                         tail = token;
150                 }
151                 
152                 if (*end)
153                         start = end + 1;
154                 else
155                         break;
156         }
157         
158         return tokens;
159 }
160
161 static int
162 decode_int (const unsigned char *in, unsigned int inlen)
163 {
164         register const unsigned char *inptr;
165         const unsigned char *inend;
166         int sign = 1, val = 0;
167         
168         inptr = in;
169         inend = in + inlen;
170         
171         if (*inptr == '-') {
172                 sign = -1;
173                 inptr++;
174         } else if (*inptr == '+')
175                 inptr++;
176         
177         for ( ; inptr < inend; inptr++) {
178                 if (!isdigit ((int) *inptr))
179                         return  -1;
180                 else
181                         val = (val * 10) + (*inptr - '0');
182         }
183         
184         val *= sign;
185         
186         return val;
187 }
188
189 static int
190 get_wday (const unsigned char *in, unsigned int inlen)
191 {
192         int wday;
193         
194         if (inlen < 3)
195                 return -1;
196         
197         for (wday = 0; wday < 7; wday++)
198                 if (!g_ascii_strncasecmp (in, tm_days[wday], 3))
199                         return wday;
200         
201         return -1;  /* unknown week day */
202 }
203
204 static int
205 get_mday (const unsigned char *in, unsigned int inlen)
206 {
207         int mday;
208         
209         mday = decode_int (in, inlen);
210         
211         if (mday < 0 || mday > 31)
212                 mday = -1;
213         
214         return mday;
215 }
216
217 static int
218 get_month (const unsigned char *in, unsigned int inlen)
219 {
220         int i;
221         
222         if (inlen < 3)
223                 return -1;
224         
225         for (i = 0; i < 12; i++)
226                 if (!g_ascii_strncasecmp (in, tm_months[i], 3))
227                         return i;
228         
229         return -1;  /* unknown month */
230 }
231
232 static int
233 get_year (const unsigned char *in, unsigned int inlen)
234 {
235         int year;
236         
237         year = decode_int (in, inlen);
238         if (year == -1)
239                 return -1;
240         
241         if (year < 100)
242                 year += (year < 70) ? 2000 : 1900;
243         
244         if (year < 1969)
245                 return -1;
246         
247         return year;
248 }
249
250 static gboolean
251 get_time (const unsigned char *in, unsigned int inlen, int *hour, int *min, int *sec)
252 {
253         register const unsigned char *inptr;
254         const unsigned char *inend;
255         int *val, colons = 0;
256         
257         *hour = *min = *sec = 0;
258         
259         inend = in + inlen;
260         val = hour;
261         for (inptr = in; inptr < inend; inptr++) {
262                 if (*inptr == ':') {
263                         colons++;
264                         switch (colons) {
265                         case 1:
266                                 val = min;
267                                 break;
268                         case 2:
269                                 val = sec;
270                                 break;
271                         default:
272                                 return FALSE;
273                         }
274                 } else if (!isdigit ((int) *inptr))
275                         return FALSE;
276                 else
277                         *val = (*val * 10) + (*inptr - '0');
278         }
279         
280         return TRUE;
281 }
282
283 static int
284 get_tzone (struct _date_token **token)
285 {
286         const unsigned char *inptr, *inend;
287         unsigned int inlen;
288         int i, t;
289         
290         for (i = 0; *token && i < 2; *token = (*token)->next, i++) {
291                 inptr = (*token)->start;
292                 inlen = (*token)->len;
293                 inend = inptr + inlen;
294                 
295                 if (*inptr == '+' || *inptr == '-') {
296                         t = decode_int (inptr, inlen);
297                         if (t < -1200 || t > 1400)
298                                 return -1;
299                         
300                         return t;
301                 } else {
302                         if (*inptr == '(') {
303                                 inptr++;
304                                 if (*(inend - 1) == ')')
305                                         inlen -= 2;
306                                 else
307                                         inlen--;
308                         }
309                         
310                         for (t = 0; t < 15; t++) {
311                                 unsigned int len = strlen (tz_offsets[t].name);
312                                 
313                                 if (len != inlen)
314                                         continue;
315                                 
316                                 if (!strncmp (inptr, tz_offsets[t].name, len))
317                                         return tz_offsets[t].offset;
318                         }
319                 }
320         }
321         
322         return -1;
323 }
324
325 /* This is where things get interesting... ;-) */
326
327 #define date_token_mask(t)  (((struct _date_token *) t)->mask)
328 #define is_numeric(t)       ((date_token_mask (t) & DATE_TOKEN_NON_NUMERIC) == 0)
329 #define is_weekday(t)       ((date_token_mask (t) & DATE_TOKEN_NON_WEEKDAY) == 0)
330 #define is_month(t)         ((date_token_mask (t) & DATE_TOKEN_NON_MONTH) == 0)
331 #define is_time(t)          (((date_token_mask (t) & DATE_TOKEN_NON_TIME) == 0) && (date_token_mask (t) & DATE_TOKEN_HAS_COLON))
332 #define is_tzone_alpha(t)   ((date_token_mask (t) & DATE_TOKEN_NON_TIMEZONE_ALPHA) == 0)
333 #define is_tzone_numeric(t) (((date_token_mask (t) & DATE_TOKEN_NON_TIMEZONE_NUMERIC) == 0) && (date_token_mask (t) & DATE_TOKEN_HAS_SIGN))
334 #define is_tzone(t)         (is_tzone_alpha (t) || is_tzone_numeric (t))
335
336 static time_t
337 decode_broken_date (struct _date_token *tokens, int *tzone)
338 {
339         gboolean got_wday, got_month, got_tzone;
340         int hour, min, sec, offset, n;
341         struct _date_token *token;
342         struct tm tm;
343         time_t time;
344         
345         memset ((void *) &tm, 0, sizeof (struct tm));
346         got_wday = got_month = got_tzone = FALSE;
347         offset = 0;
348         
349         token = tokens;
350         while (token) {
351                 if (is_weekday (token) && !got_wday) {
352                         if ((n = get_wday (token->start, token->len)) != -1) {
353                                 d(printf ("weekday; "));
354                                 got_wday = TRUE;
355                                 tm.tm_wday = n;
356                                 goto next_token;
357                         }
358                 }
359                 
360                 if (is_month (token) && !got_month) {
361                         if ((n = get_month (token->start, token->len)) != -1) {
362                                 d(printf ("month; "));
363                                 got_month = TRUE;
364                                 tm.tm_mon = n;
365                                 goto next_token;
366                         }
367                 }
368                 
369                 if (is_time (token) && !tm.tm_hour && !tm.tm_min && !tm.tm_sec) {
370                         if (get_time (token->start, token->len, &hour, &min, &sec)) {
371                                 d(printf ("time; "));
372                                 tm.tm_hour = hour;
373                                 tm.tm_min = min;
374                                 tm.tm_sec = sec;
375                                 goto next_token;
376                         }
377                 }
378                 
379                 if (is_tzone (token) && !got_tzone) {
380                         struct _date_token *t = token;
381                         
382                         if ((n = get_tzone (&t)) != -1) {
383                                 d(printf ("tzone; "));
384                                 got_tzone = TRUE;
385                                 offset = n;
386                                 goto next_token;
387                         }
388                 }
389                 
390                 if (is_numeric (token)) {
391                         if (token->len == 4 && !tm.tm_year) {
392                                 if ((n = get_year (token->start, token->len)) != -1) {
393                                         d(printf ("year; "));
394                                         tm.tm_year = n - 1900;
395                                         goto next_token;
396                                 }
397                         } else {
398                                 if (!got_month && !got_wday && token->next && is_numeric (token->next)) {
399                                         d(printf ("mon; "));
400                                         n = decode_int (token->start, token->len);
401                                         got_month = TRUE;
402                                         tm.tm_mon = n - 1;
403                                         goto next_token;
404                                 } else if (!tm.tm_mday && (n = get_mday (token->start, token->len)) != -1) {
405                                         d(printf ("mday; "));
406                                         tm.tm_mday = n;
407                                         goto next_token;
408                                 } else if (!tm.tm_year) {
409                                         d(printf ("2-digit year; "));
410                                         n = get_year (token->start, token->len);
411                                         tm.tm_year = n - 1900;
412                                         goto next_token;
413                                 }
414                         }
415                 }
416                 
417                 d(printf ("???; "));
418                 
419         next_token:
420                 
421                 token = token->next;
422         }
423         
424         d(printf ("\n"));
425         
426         time = e_mktime_utc (&tm);
427         
428         /* time is now GMT of the time we want, but not offset by the timezone ... */
429         
430         /* this should convert the time to the GMT equiv time */
431         time -= ((offset / 100) * 60 * 60) + (offset % 100) * 60;
432         
433         if (tzone)
434                 *tzone = offset;
435         
436         return time;
437 }
438
439
440 /**
441  * parse_broken_date:
442  * @datestr: input date string
443  * @saveoffset:
444  *
445  * Decodes the rfc822/broken date string and saves the GMT offset into
446  * @saveoffset if non-NULL.
447  *
448  * Returns the time_t representation of the date string specified by
449  * @in. If 'saveoffset' is non-NULL, the value of the timezone offset
450  * will be stored.
451  **/
452 time_t
453 parse_broken_date (const char *datestr, int *saveoffset)
454 {
455         struct _date_token *token, *tokens;
456         time_t date;
457         
458         tokens = datetok (datestr);
459         
460         date = decode_broken_date (tokens, saveoffset);
461         
462         /* cleanup */
463         while (tokens) {
464                 token = tokens;
465                 tokens = tokens->next;
466                 g_free (token);
467         }
468         
469         return date;
470 }
471
472
473
474
475
476 #ifdef DATETOK_STANDALONE
477
478 static void
479 table_init ()
480 {
481         int i;
482         
483         memset (datetok_table, 0, sizeof (datetok_table));
484         
485         for (i = 0; i < 256; i++) {
486                 if (!strchr (NUMERIC_CHARS, i))
487                         datetok_table[i] |= DATE_TOKEN_NON_NUMERIC;
488                 
489                 if (!strchr (WEEKDAY_CHARS, i))
490                         datetok_table[i] |= DATE_TOKEN_NON_WEEKDAY;
491                 
492                 if (!strchr (MONTH_CHARS, i))
493                         datetok_table[i] |= DATE_TOKEN_NON_MONTH;
494                 
495                 if (!strchr (TIME_CHARS, i))
496                         datetok_table[i] |= DATE_TOKEN_NON_TIME;
497                 
498                 if (!strchr (TIMEZONE_ALPHA_CHARS, i))
499                         datetok_table[i] |= DATE_TOKEN_NON_TIMEZONE_ALPHA;
500                 
501                 if (!strchr (TIMEZONE_NUMERIC_CHARS, i))
502                         datetok_table[i] |= DATE_TOKEN_NON_TIMEZONE_NUMERIC;
503                 
504                 if (((char) i) == ':')
505                         datetok_table[i] |= DATE_TOKEN_HAS_COLON;
506                 
507                 if (strchr ("+-", i))
508                         datetok_table[i] |= DATE_TOKEN_HAS_SIGN;
509         }
510         
511         printf ("static const unsigned int datetok_table[256] = {");
512         for (i = 0; i < 256; i++) {
513                 if (i % 16 == 0)
514                         printf ("\n\t");
515                 printf ("%3d,", datetok_table[i]);
516         }
517         printf ("\n};\n");
518 }
519
520
521 int main (int argc, char **argv)
522 {
523         time_t date;
524         int offset;
525         
526         /*table_init ();*/
527         
528         date = parse_broken_date (argv[1], &offset);
529         printf ("%d; %d\n", date, offset);
530         
531         return 0;
532 }
533
534 #endif /* DATETOK_STANDALONE */