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