1 //=============================================================================
3 // Contents: Date parsing function
4 // Maintainer: Doug Sauder <dwsauder@fwb.gulf.net>
5 // WWW: http://www.fwb.gulf.net/~dwsauder/mimepp.html
9 // Copyright (c) 1996, 1997 Douglas W. Sauder
10 // All rights reserved.
12 // IN NO EVENT SHALL DOUGLAS W. SAUDER BE LIABLE TO ANY PARTY FOR DIRECT,
13 // INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF
14 // THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF DOUGLAS W. SAUDER
15 // HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
17 // DOUGLAS W. SAUDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT
18 // NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
19 // PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS"
20 // BASIS, AND DOUGLAS W. SAUDER HAS NO OBLIGATION TO PROVIDE MAINTENANCE,
21 // SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
23 //=============================================================================
26 * For maximum code reuse, the functions in this file are written in C.
29 #include <mimelib/config.h>
30 #include <mimelib/debug.h>
35 static int CommentLength(const char *str
)
37 int ch
, pos
, level
, quoteNext
, done
, len
;
84 * ParseRfc822Date() -- Parse a date in RFC-822 (RFC-1123) format
86 * If the parsing succeeds:
87 * - tms is set to contain the year, month, day, hour, minute, and second
88 * - z is set to contain the time zone in minutes offset from UTC
90 * If the parsing fails:
92 * - the information in tms and z is undefined
97 int ParseRfc822Date(const char *str
, struct tm
*tms
, int *z
)
99 int pos
, ch
, n
, sgn
, numZoneDigits
;
100 int day
=1, month
=0, year
=1970, hour
=0, minute
=0, second
=0, zone
=0;
107 * Ignore optional day of the week.
111 * Day -- one or two digits
113 /* -- skip over non-digits */
116 while (ch
&& !('0' <= ch
&& ch
<= '9')) {
118 pos
+= CommentLength(&str
[pos
]);
125 /* -- convert next one or two digits */
127 if ('0' <= ch
&& ch
<= '9') {
132 if ('0' <= ch
&& ch
<= '9') {
138 if (1 <= n
&& n
<= 31) {
145 * Month. Use case-insensitive string compare for added robustness
147 /* -- skip over chars to first possible month char */
148 while (ch
&& !('A' <= ch
&& ch
<= 'S') && !('a' <= ch
&& ch
<= 's')) {
150 pos
+= CommentLength(&str
[pos
]);
157 /* -- convert the month name */
163 if ((str
[pos
+1] == 'p' || str
[pos
+1] == 'P')
164 && (str
[pos
+2] == 'r' || str
[pos
+2] == 'R')) {
170 else if ((str
[pos
+1] == 'u' || str
[pos
+1] == 'U')
171 && (str
[pos
+2] == 'g' || str
[pos
+2] == 'G')) {
180 if ((str
[pos
+1] == 'e' || str
[pos
+1] == 'E')
181 && (str
[pos
+2] == 'c' || str
[pos
+2] == 'C')) {
190 if ((str
[pos
+1] == 'e' || str
[pos
+1] == 'E')
191 && (str
[pos
+2] == 'b' || str
[pos
+2] == 'B')) {
200 if ((str
[pos
+1] == 'a' || str
[pos
+1] == 'A')
201 && (str
[pos
+2] == 'n' || str
[pos
+2] == 'N')) {
207 else if ((str
[pos
+1] == 'u' || str
[pos
+1] == 'U')
208 && (str
[pos
+2] == 'l' || str
[pos
+2] == 'L')) {
214 else if ((str
[pos
+1] == 'u' || str
[pos
+1] == 'U')
215 && (str
[pos
+2] == 'n' || str
[pos
+2] == 'N')) {
224 if ((str
[pos
+1] == 'a' || str
[pos
+1] == 'A')
225 && (str
[pos
+2] == 'r' || str
[pos
+2] == 'R')) {
231 else if ((str
[pos
+1] == 'a' || str
[pos
+1] == 'A')
232 && (str
[pos
+2] == 'y' || str
[pos
+2] == 'Y')) {
241 if ((str
[pos
+1] == 'o' || str
[pos
+1] == 'O')
242 && (str
[pos
+2] == 'v' || str
[pos
+2] == 'V')) {
251 if ((str
[pos
+1] == 'c' || str
[pos
+1] == 'c')
252 && (str
[pos
+2] == 't' || str
[pos
+2] == 'T')) {
261 if ((str
[pos
+1] == 'e' || str
[pos
+1] == 'E')
262 && (str
[pos
+2] == 'p' || str
[pos
+2] == 'P')) {
269 if (0 <= n
&& n
<= 11) {
276 * Year -- two or four digits (four preferred)
278 /* -- skip over non-digits */
279 while (ch
&& !('0' <= ch
&& ch
<= '9')) {
281 pos
+= CommentLength(&str
[pos
]);
288 /* -- convert up to four digits */
290 if ('0' <= ch
&& ch
<= '9') {
295 if ('0' <= ch
&& ch
<= '9') {
301 if ('0' <= ch
&& ch
<= '9') {
307 if ('0' <= ch
&& ch
<= '9') {
314 /* Fixed year 2000 problem (fix by tony@lasernet.globalnet.co.uk) */
316 n
+= 2000; /* When less than 70 assume after year 2000 */
318 n
+= 1900; /* When >69 and <100 assume 1970 to 1999 */
319 /* Additional check to limit valid range to 1970 to 2037 */
320 if ((n
>= 1970) && (n
< 2038))
331 /* -- skip over non-digits */
332 while (ch
&& !('0' <= ch
&& ch
<= '9')) {
334 pos
+= CommentLength(&str
[pos
]);
341 /* -- convert next one or two digits */
343 if ('0' <= ch
&& ch
<= '9') {
348 if ('0' <= ch
&& ch
<= '9') {
354 if (0 <= n
&& n
<= 23) {
361 * Minute -- two digits
363 /* -- scan for ':' */
364 while (ch
&& ch
!= ':') {
366 pos
+= CommentLength(&str
[pos
]);
373 /* -- skip over non-digits */
374 while (ch
&& !('0' <= ch
&& ch
<= '9')) {
376 pos
+= CommentLength(&str
[pos
]);
383 /* -- convert next one or two digits */
385 if ('0' <= ch
&& ch
<= '9') {
390 if ('0' <= ch
&& ch
<= '9') {
396 if (0 <= n
&& n
<= 59) {
403 * Second (optional) -- two digits
405 /* -- scan for ':' or start of time zone */
406 while (ch
&& !(ch
== ':' || ch
== '+' || ch
== '-' || isalpha(ch
))) {
408 pos
+= CommentLength(&str
[pos
]);
415 /* -- get the seconds, if it's there */
418 /* -- skip non-digits */
420 while (ch
&& !('0' <= ch
&& ch
<= '9')) {
422 pos
+= CommentLength(&str
[pos
]);
429 /* -- convert next one or two digits */
431 if ('0' <= ch
&& ch
<= '9') {
436 if ('0' <= ch
&& ch
<= '9') {
442 if (0 <= n
&& n
<= 59) {
448 /* -- scan for start of time zone */
449 while (ch
&& !(ch
== '+' || ch
== '-' || isalpha(ch
))) {
451 pos
+= CommentLength(&str
[pos
]);
459 else /* if (ch != ':') */ {
465 * Note: According to RFC-1123, the military time zones are specified
466 * incorrectly in RFC-822. RFC-1123 then states that "military time
467 * zones in RFC-822 headers carry no information."
468 * Here, we follow the specification in RFC-822. What else could we
469 * do? Military time zones should *never* be used!
479 /* -- skip non-digits */
481 while (ch
&& !('0' <= ch
&& ch
<= '9')) {
485 while( str
[pos
+ numZoneDigits
] && isdigit(str
[pos
+ numZoneDigits
] ) )
487 /* -- convert next four digits */
489 while ( numZoneDigits
) {
490 switch(numZoneDigits
) {
492 if ('0' <= ch
&& ch
<= '9') {
499 if ('0' <= ch
&& ch
<= '9') {
506 if ('0' <= ch
&& ch
<= '9') {
513 if ('0' <= ch
&& ch
<= '9') {
526 if (str
[pos
+1] == 'T' || str
[pos
+1] == 't') {
530 /* Military time zone */
536 if ((str
[pos
+1] == 'M' || str
[pos
+1] == 'm')
537 && (str
[pos
+2] == 'T' || str
[pos
+2] == 't')) {
541 /* Military time zone */
547 if ((str
[pos
+1] == 'S' || str
[pos
+1] == 's')
548 && (str
[pos
+2] == 'T' || str
[pos
+2] == 't')) {
551 else if ((str
[pos
+1] == 'D' || str
[pos
+1] == 'd')
552 && (str
[pos
+2] == 'T' || str
[pos
+2] == 't')) {
556 /* Military time zone */
562 if ((str
[pos
+1] == 'S' || str
[pos
+1] == 's')
563 && (str
[pos
+2] == 'T' || str
[pos
+2] == 't')) {
566 else if ((str
[pos
+1] == 'D' || str
[pos
+1] == 'd')
567 && (str
[pos
+2] == 'T' || str
[pos
+2] == 't')) {
571 /* Military time zone */
577 if ((str
[pos
+1] == 'S' || str
[pos
+1] == 's')
578 && (str
[pos
+2] == 'T' || str
[pos
+2] == 't')) {
581 else if ((str
[pos
+1] == 'D' || str
[pos
+1] == 'd')
582 && (str
[pos
+2] == 'T' || str
[pos
+2] == 't')) {
586 /* Military time zone */
592 if ((str
[pos
+1] == 'S' || str
[pos
+1] == 's')
593 && (str
[pos
+2] == 'T' || str
[pos
+2] == 't')) {
596 else if ((str
[pos
+1] == 'D' || str
[pos
+1] == 'd')
597 && (str
[pos
+2] == 'T' || str
[pos
+2] == 't')) {
601 /* Military time zone */
606 /* Military time zone */
610 /* Military time zone */
611 if ('A' <= ch
&& ch
<= 'I') {
614 else if ('K' <= ch
&& ch
<= 'M') {
617 else if ('N' <= ch
&& ch
<= 'Y') {
620 /* Some software doesn't set the timezone, so we default
621 to +/-0 so KMail isn't too strict. --dnaber@mini.gt.owl.de, 2000-06-11
629 tms
->tm_year
= year
- 1900;
633 tms
->tm_min
= minute
;
634 tms
->tm_sec
= second
;
653 return isValid
? 0 : -1;
657 #ifdef DW_TESTING_DATEPARSER
663 const char* testStr
[] = {
667 const char* wdays
[] = {
668 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
671 const char* months
[] = {
672 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
673 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
678 struct tm
*ptms
, tms1
, tms2
;
683 /* try a bunch of random dates */
685 for (i
=0; i
< 1000; ++i
) {
686 tt
= rand()*((double)0x7fffffff/RAND_MAX
);
687 zone1
= (rand()%49 - 24)*30;
690 sgn
= (zone1
>= 0) ? '+' : '-';
691 sprintf(buf
, "%s, %2d %s %d %d%d:%d%d:%d%d %c%d%d%d%d",
692 wdays
[tms1
.tm_wday
], tms1
.tm_mday
, months
[tms1
.tm_mon
],
694 tms1
.tm_hour
/10, tms1
.tm_hour
%10,
695 tms1
.tm_min
/10, tms1
.tm_min
%10,
696 tms1
.tm_sec
/10, tms1
.tm_sec
%10,
697 sgn
, abs(zone1
)/60/10, abs(zone1
)/60%10,
698 abs(zone1
)%60/10, abs(zone1
)%60%10);
699 ParseRfc822Date(buf
, &tms2
, &zone2
);
700 if (tms1
.tm_year
!= tms2
.tm_year
) {
701 fprintf(stderr
, "Bad year\n");
703 if (tms1
.tm_mon
!= tms2
.tm_mon
) {
704 fprintf(stderr
, "Bad month\n");
706 if (tms1
.tm_mday
!= tms2
.tm_mday
) {
707 fprintf(stderr
, "Bad day\n");
709 if (tms1
.tm_hour
!= tms2
.tm_hour
) {
710 fprintf(stderr
, "Bad hour\n");
712 if (tms1
.tm_min
!= tms2
.tm_min
) {
713 fprintf(stderr
, "Bad minute\n");
715 if (tms1
.tm_sec
!= tms2
.tm_sec
) {
716 fprintf(stderr
, "Bad second\n");
718 if (zone1
!= zone2
) {
719 fprintf(stderr
, "Bad zone\n");