1 /*
2    Date manipulation routines
3    Copyright (C) 1999-2004, Joe Orton <joe@manyfish.co.uk>
4 
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public
7    License as published by the Free Software Foundation; either
8    version 2 of the License, or (at your option) any later version.
9 
10    This library 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 GNU
13    Library General Public License for more details.
14 
15    You should have received a copy of the GNU Library General Public
16    License along with this library; if not, write to the Free
17    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
18    MA 02111-1307, USA
19 
20 */
21 
22 #include "config.h"
23 
24 #include <sys/types.h>
25 
26 #include <time.h>
27 #ifdef HAVE_STDLIB_H
28 #include <stdlib.h>
29 #endif
30 #include <stdio.h>
31 
32 #ifdef HAVE_STRING_H
33 #include <string.h>
34 #endif
35 
36 #include "ne_alloc.h"
37 #include "ne_dates.h"
38 #include "ne_utils.h"
39 
40 /* Generic date manipulation routines. */
41 
42 /* ISO8601: 2001-01-01T12:30:00Z */
43 #define ISO8601_FORMAT_Z "%04d-%02d-%02dT%02d:%02d:%lfZ"
44 #define ISO8601_FORMAT_M "%04d-%02d-%02dT%02d:%02d:%lf-%02d:%02d"
45 #define ISO8601_FORMAT_P "%04d-%02d-%02dT%02d:%02d:%lf+%02d:%02d"
46 
47 /* RFC1123: Sun, 06 Nov 1994 08:49:37 GMT */
48 #define RFC1123_FORMAT "%3s, %02d %3s %4d %02d:%02d:%02d GMT"
49 /* RFC850:  Sunday, 06-Nov-94 08:49:37 GMT */
50 #define RFC1036_FORMAT "%10s %2d-%3s-%2d %2d:%2d:%2d GMT"
51 /* asctime: Wed Jun 30 21:49:08 1993 */
52 #define ASCTIME_FORMAT "%3s %3s %2d %2d:%2d:%2d %4d"
53 
54 static const char *rfc1123_weekdays[7] = {
55     "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
56 };
57 static const char *short_months[12] = {
58     "Jan", "Feb", "Mar", "Apr", "May", "Jun",
59     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
60 };
61 
62 #if defined(HAVE_STRUCT_TM_TM_GMTOFF)
63 #define GMTOFF(t) ((t).tm_gmtoff)
64 #else
65 /* FIXME: work out the offset anyway. */
66 #define GMTOFF(t) (0)
67 #endif
68 
69 /* Returns the time/date GMT, in RFC1123-type format: eg
70  *  Sun, 06 Nov 1994 08:49:37 GMT. */
ne_rfc1123_date(time_t anytime)71 char *ne_rfc1123_date(time_t anytime) {
72     struct tm *gmt;
73     char *ret;
74     gmt = gmtime(&anytime);
75     if (gmt == NULL)
76 	return NULL;
77     ret = ne_malloc(29 + 1); /* dates are 29 chars long */
78 /*  it goes: Sun, 06 Nov 1994 08:49:37 GMT */
79     ne_snprintf(ret, 30, RFC1123_FORMAT,
80 		rfc1123_weekdays[gmt->tm_wday], gmt->tm_mday,
81 		short_months[gmt->tm_mon], 1900 + gmt->tm_year,
82 		gmt->tm_hour, gmt->tm_min, gmt->tm_sec);
83 
84     return ret;
85 }
86 
87 /* Takes an ISO-8601-formatted date string and returns the time_t.
88  * Returns (time_t)-1 if the parse fails. */
ne_iso8601_parse(const char * date)89 time_t ne_iso8601_parse(const char *date)
90 {
91     struct tm gmt = {0};
92     int off_hour, off_min;
93     double sec;
94     off_t fix;
95     int n;
96 
97     /*  it goes: ISO8601: 2001-01-01T12:30:00+03:30 */
98     if ((n = sscanf(date, ISO8601_FORMAT_P,
99 		    &gmt.tm_year, &gmt.tm_mon, &gmt.tm_mday,
100 		    &gmt.tm_hour, &gmt.tm_min, &sec,
101 		    &off_hour, &off_min)) == 8) {
102       gmt.tm_sec = (int)sec;
103       fix = - off_hour * 3600 - off_min * 60;
104     }
105     /*  it goes: ISO8601: 2001-01-01T12:30:00-03:30 */
106     else if ((n = sscanf(date, ISO8601_FORMAT_M,
107 			 &gmt.tm_year, &gmt.tm_mon, &gmt.tm_mday,
108 			 &gmt.tm_hour, &gmt.tm_min, &sec,
109 			 &off_hour, &off_min)) == 8) {
110       gmt.tm_sec = (int)sec;
111       fix = off_hour * 3600 + off_min * 60;
112     }
113     /*  it goes: ISO8601: 2001-01-01T12:30:00Z */
114     else if ((n = sscanf(date, ISO8601_FORMAT_Z,
115 			 &gmt.tm_year, &gmt.tm_mon, &gmt.tm_mday,
116 			 &gmt.tm_hour, &gmt.tm_min, &sec)) == 6) {
117       gmt.tm_sec = (int)sec;
118       fix = 0;
119     }
120     else {
121       return (time_t)-1;
122     }
123 
124     gmt.tm_year -= 1900;
125     gmt.tm_isdst = -1;
126     gmt.tm_mon--;
127 
128     return mktime(&gmt) + fix + GMTOFF(gmt);
129 }
130 
131 /* Takes an RFC1123-formatted date string and returns the time_t.
132  * Returns (time_t)-1 if the parse fails. */
ne_rfc1123_parse(const char * date)133 time_t ne_rfc1123_parse(const char *date)
134 {
135     struct tm gmt = {0};
136     char wkday[4], mon[4];
137     int n;
138 /*  it goes: Sun, 06 Nov 1994 08:49:37 GMT */
139     n = sscanf(date, RFC1123_FORMAT,
140 	    wkday, &gmt.tm_mday, mon, &gmt.tm_year, &gmt.tm_hour,
141 	    &gmt.tm_min, &gmt.tm_sec);
142     /* Is it portable to check n==7 here? */
143     gmt.tm_year -= 1900;
144     for (n=0; n<12; n++)
145 	if (strcmp(mon, short_months[n]) == 0)
146 	    break;
147     /* tm_mon comes out as 12 if the month is corrupt, which is desired,
148      * since the mktime will then fail */
149     gmt.tm_mon = n;
150     gmt.tm_isdst = -1;
151     return mktime(&gmt) + GMTOFF(gmt);
152 }
153 
154 /* Takes a string containing a RFC1036-style date and returns the time_t */
ne_rfc1036_parse(const char * date)155 time_t ne_rfc1036_parse(const char *date)
156 {
157     struct tm gmt = {0};
158     int n;
159     char wkday[11], mon[4];
160     /* RFC850/1036 style dates: Sunday, 06-Nov-94 08:49:37 GMT */
161     n = sscanf(date, RFC1036_FORMAT,
162 		wkday, &gmt.tm_mday, mon, &gmt.tm_year,
163 		&gmt.tm_hour, &gmt.tm_min, &gmt.tm_sec);
164     if (n != 7) {
165 	return (time_t)-1;
166     }
167 
168     /* portable to check n here? */
169     for (n=0; n<12; n++)
170 	if (strcmp(mon, short_months[n]) == 0)
171 	    break;
172     /* tm_mon comes out as 12 if the month is corrupt, which is desired,
173      * since the mktime will then fail */
174 
175     /* Defeat Y2K bug. */
176     if (gmt.tm_year < 50)
177 	gmt.tm_year += 100;
178 
179     gmt.tm_mon = n;
180     gmt.tm_isdst = -1;
181     return mktime(&gmt) + GMTOFF(gmt);
182 }
183 
184 
185 /* (as)ctime dates are like:
186  *    Wed Jun 30 21:49:08 1993
187  */
ne_asctime_parse(const char * date)188 time_t ne_asctime_parse(const char *date)
189 {
190     struct tm gmt = {0};
191     int n;
192     char wkday[4], mon[4];
193     n = sscanf(date, ASCTIME_FORMAT,
194 		wkday, mon, &gmt.tm_mday,
195 		&gmt.tm_hour, &gmt.tm_min, &gmt.tm_sec,
196 		&gmt.tm_year);
197     /* portable to check n here? */
198     for (n=0; n<12; n++)
199 	if (strcmp(mon, short_months[n]) == 0)
200 	    break;
201     /* tm_mon comes out as 12 if the month is corrupt, which is desired,
202      * since the mktime will then fail */
203     gmt.tm_mon = n;
204     gmt.tm_isdst = -1;
205     return mktime(&gmt) + GMTOFF(gmt);
206 }
207 
208 /* HTTP-date parser */
ne_httpdate_parse(const char * date)209 time_t ne_httpdate_parse(const char *date)
210 {
211     time_t tmp;
212     tmp = ne_rfc1123_parse(date);
213     if (tmp == -1) {
214         tmp = ne_rfc1036_parse(date);
215 	if (tmp == -1)
216 	    tmp = ne_asctime_parse(date);
217     }
218     return tmp;
219 }
220