1 /* times.c -- Time/date utilities
2 *
3 * Copyright (c) 1994-2010 Carnegie Mellon University. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in
14 * the documentation and/or other materials provided with the
15 * distribution.
16 *
17 * 3. The name "Carnegie Mellon University" must not be used to
18 * endorse or promote products derived from this software without
19 * prior written permission. For permission or any legal
20 * details, please contact
21 * Carnegie Mellon University
22 * Center for Technology Transfer and Enterprise Creation
23 * 4615 Forbes Avenue
24 * Suite 302
25 * Pittsburgh, PA 15213
26 * (412) 268-7393, fax: (412) 268-7395
27 * innovation@andrew.cmu.edu
28 *
29 * 4. Redistributions of any form whatsoever must retain the following
30 * acknowledgment:
31 * "This product includes software developed by Computing Services
32 * at Carnegie Mellon University (http://www.cmu.edu/computing/)."
33 *
34 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
35 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
36 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
37 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
38 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
39 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
40 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
41 */
42
43 #include <ctype.h>
44 #include <memory.h>
45 #include <stdio.h>
46 #include <string.h>
47 #include <strings.h>
48
49 #include "assert.h"
50 #include "times.h"
51 #include "util.h"
52 #include "gmtoff.h"
53 #include "mkgmtime.h"
54
55 EXPORTED const char monthname[][4] = {
56 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
57 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
58 };
59 EXPORTED const char wday[][4] = {
60 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
61 };
62
63
64 #define EOB (-1) /* End Of Buffer */
65
66 static const char rfc5322_special[256] = {
67 [' '] = 1,
68 ['\t'] = 1,
69 ['\r'] = 1,
70 ['\n'] = 1,
71 };
72
73 static const char rfc5322_separators[256] = {
74 [' '] = 1,
75 [','] = 1,
76 ['-'] = 1,
77 ['+'] = 1,
78 [':'] = 1,
79 };
80
81 enum {
82 Alpha = 1, /* Alphabet */
83 UAlpha = 2, /* Uppercase Alphabet */
84 LAlpha = 4, /* Lowercase Alphabet */
85 Digit = 8, /* Digits/Numbers */
86 TZSign = 16, /* Timezone sign +/- */
87 };
88
89 static const long rfc5322_usascii_charset[257] = {
90 ['0' + 1 ... '9' + 1] = Digit,
91 ['A' + 1 ... 'Z' + 1] = Alpha | UAlpha,
92 ['a' + 1 ... 'z' + 1] = Alpha | LAlpha,
93 ['+' + 1] = TZSign,
94 ['-' + 1] = TZSign
95 };
96
97 struct rfc5322dtbuf {
98 const char *str;
99 int len;
100 int offset;
101 };
102
103 #define isleap(year) (!((year) % 4) && (((year) % 100) || !((year) % 400)))
104
monthdays(int year,int month)105 static int monthdays(int year/*since 1900*/, int month/*0-based*/)
106 {
107 int leapday;
108 static const int mdays[12] = {
109 31, 28, 31, 30, 31, 30,
110 31, 31, 30, 31, 30, 31
111 };
112
113 leapday = (month == 1 && isleap(year+1900));
114 return mdays[month] + leapday;
115 }
116
dayofyear(int year,int month,int day)117 static int dayofyear(int year/*since 1900*/, int month/*0-based*/, int day)
118 {
119 static const int ydays[2][13] = {
120 {0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
121 {0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}
122 };
123 return ydays[isleap(year+1900)][month+1] + day;
124 }
125
126 #undef isleap
127
dayofweek(int year,int month,int day)128 static int dayofweek(int year/*since 1900*/, int month/*0-based*/, int day)
129 {
130 /* Uses Zeller's congruence for the Gregorian calendar
131 * https://en.wikipedia.org/wiki/Zeller%27s_congruence
132 * h is the day of the week with Saturday = 0 */
133 int q = day; // day of month
134 int m = month <= 1 ? month + 13 : month + 1; // month, (3 = March, 4 = April, ..., 14 = February)
135 int Y = month <= 1 ? 1900 + year - 1 : 1900 + year; // year
136 int h = (q + ((13 * (m + 1)) / 5) + Y + (Y / 4) - (Y / 100) + (Y / 400)) % 7;
137 return (h + 6) % 7;
138 }
139
140 /* 'buf' must be at least 80 characters */
time_to_rfc822(time_t t,char * buf,size_t len)141 EXPORTED int time_to_rfc822(time_t t, char *buf, size_t len)
142 {
143 struct tm *tm;
144 long gmtoff;
145 int gmtnegative = 0;
146
147 assert(buf != NULL);
148
149 tm = localtime(&t);
150 gmtoff = gmtoff_of(tm, t);
151 if (gmtoff < 0) {
152 gmtoff = -gmtoff;
153 gmtnegative = 1;
154 }
155 gmtoff /= 60;
156
157 return snprintf(buf, len, "%s, %02d %s %4d %02d:%02d:%02d %c%.2lu%.2lu",
158 wday[tm->tm_wday],
159 tm->tm_mday, monthname[tm->tm_mon], tm->tm_year + 1900,
160 tm->tm_hour, tm->tm_min, tm->tm_sec,
161 gmtnegative ? '-' : '+', gmtoff / 60, gmtoff % 60);
162 }
163
164
165 /*
166 * Skip RFC 822 FWS = Folding White Space. This is the white
167 * space that can be inserted harmlessly into structured
168 * RFC 822 headers, including splitting them over multiple lines.
169 *
170 * Note that RFC 822 isn't entirely clear about whether such
171 * space may be present in date-times, but it's successor
172 * RFC 2822 is quite clear and explicit. Note also that
173 * neither RFC allows for (comments) inside a date-time,
174 * so we don't attempt to handle that here.
175 */
skip_fws(const char * p)176 static const char *skip_fws(const char *p)
177 {
178 if (!p)
179 return NULL;
180 while (*p && Uisspace(*p)) {
181 /* check for end of an RFC 822 header line */
182 if (*p == '\n') {
183 p++;
184 if (*p != ' ' && *p != '\t')
185 return NULL;
186 }
187 else
188 p++;
189 }
190 return (*p ? p : NULL);
191 }
192
parse_rfc822(const char * s,time_t * tp,int dayonly)193 static int parse_rfc822(const char *s, time_t *tp, int dayonly)
194 {
195 const char *origs = s;
196 struct tm tm;
197 time_t t;
198 char month[4];
199 int zone_off = 0;
200
201 if (!s)
202 goto baddate;
203
204 memset(&tm, 0, sizeof(tm));
205
206 s = skip_fws(s);
207 if (!s)
208 goto baddate;
209
210 if (Uisalpha(*s)) {
211 /* Day name -- skip over it */
212 s++;
213 if (!Uisalpha(*s))
214 goto baddate;
215 s++;
216 if (!Uisalpha(*s))
217 goto baddate;
218 s++;
219 s = skip_fws(s);
220 if (!s || *s++ != ',')
221 goto baddate;
222 s = skip_fws(s);
223 if (!s)
224 goto baddate;
225 }
226
227 if (!Uisdigit(*s))
228 goto baddate;
229 tm.tm_mday = *s++ - '0';
230 if (Uisdigit(*s)) {
231 tm.tm_mday = tm.tm_mday*10 + *s++ - '0';
232 }
233
234 /* Parse month name */
235 s = skip_fws(s);
236 if (!s)
237 goto baddate;
238 month[0] = *s++;
239 if (!Uisalpha(month[0]))
240 goto baddate;
241 month[1] = *s++;
242 if (!Uisalpha(month[1]))
243 goto baddate;
244 month[2] = *s++;
245 if (!Uisalpha(month[2]))
246 goto baddate;
247 month[3] = '\0';
248 for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) {
249 if (!strcasecmp(month, monthname[tm.tm_mon])) break;
250 }
251 if (tm.tm_mon == 12)
252 goto baddate;
253
254 /* Parse year */
255 s = skip_fws(s);
256 if (!s || !Uisdigit(*s))
257 goto baddate;
258 tm.tm_year = *s++ - '0';
259 if (!Uisdigit(*s))
260 goto baddate;
261 tm.tm_year = tm.tm_year * 10 + *s++ - '0';
262 if (Uisdigit(*s)) {
263 if (tm.tm_year < 19)
264 goto baddate;
265 tm.tm_year -= 19;
266 tm.tm_year = tm.tm_year * 10 + *s++ - '0';
267 if (!Uisdigit(*s))
268 goto baddate;
269 tm.tm_year = tm.tm_year * 10 + *s++ - '0';
270 } else {
271 if (tm.tm_year < 70) {
272 /* two-digit year, probably after 2000.
273 * This patent was overturned, right?
274 */
275 tm.tm_year += 100;
276 }
277 }
278 if (Uisdigit(*s)) {
279 /* five-digit date */
280 goto baddate;
281 }
282
283 if (tm.tm_mday > monthdays(tm.tm_year, tm.tm_mon))
284 goto baddate;
285
286 s = skip_fws(s);
287 if (s && !dayonly) {
288 /* Parse hour */
289 if (!s || !Uisdigit(*s))
290 goto badtime;
291 tm.tm_hour = *s++ - '0';
292 if (!Uisdigit(*s))
293 goto badtime;
294 tm.tm_hour = tm.tm_hour * 10 + *s++ - '0';
295 if (!s || *s++ != ':')
296 goto badtime;
297
298 /* Parse min */
299 if (!s || !Uisdigit(*s))
300 goto badtime;
301 tm.tm_min = *s++ - '0';
302 if (!Uisdigit(*s))
303 goto badtime;
304 tm.tm_min = tm.tm_min * 10 + *s++ - '0';
305
306 if (*s == ':') {
307 /* Parse sec */
308 if (!++s || !Uisdigit(*s))
309 goto badtime;
310 tm.tm_sec = *s++ - '0';
311 if (!Uisdigit(*s))
312 goto badtime;
313 tm.tm_sec = tm.tm_sec * 10 + *s++ - '0';
314 }
315
316 s = skip_fws(s);
317 if (s) {
318 /* Parse timezone offset */
319 if (*s == '+' || *s == '-') {
320 /* Parse numeric offset */
321 int east = (*s++ == '-');
322
323 if (!s || !Uisdigit(*s))
324 goto badzone;
325 zone_off = *s++ - '0';
326 if (!s || !Uisdigit(*s))
327 goto badzone;
328 zone_off = zone_off * 10 + *s++ - '0';
329 if (!s || !Uisdigit(*s))
330 goto badzone;
331 zone_off = zone_off * 6 + *s++ - '0';
332 if (!s || !Uisdigit(*s))
333 goto badzone;
334 zone_off = zone_off * 10 + *s++ - '0';
335
336 if (east)
337 zone_off = -zone_off;
338 }
339 else if (Uisalpha(*s)) {
340 char zone[4];
341
342 zone[0] = *s++;
343 if (!Uisalpha(*s)) {
344 /* Parse military (single-char) zone */
345 zone[1] = '\0';
346 lcase(zone);
347 if (zone[0] < 'j')
348 zone_off = (zone[0] - 'a' + 1) * 60;
349 else if (zone[0] == 'j')
350 goto badzone;
351 else if (zone[0] <= 'm')
352 zone_off = (zone[0] - 'a') * 60;
353 else if (zone[0] < 'z')
354 zone_off = ('m' - zone[0]) * 60;
355 else
356 zone_off = 0;
357 }
358 else {
359 zone[1] = *s++;
360 if (!Uisalpha(*s)) {
361 /* Parse UT (universal time) */
362 zone[2] = '\0';
363 lcase(zone);
364 if (strcmp(zone, "ut"))
365 goto badzone;
366 zone_off = 0;
367 }
368 else {
369 /* Parse 3-char time zone */
370 char *p;
371
372 zone[2] = *s;
373 zone[3] = '\0';
374 lcase(zone);
375 /* GMT (Greenwich mean time) */
376 if (!strcmp(zone, "gmt"))
377 zone_off = 0;
378
379 /* US time zone */
380 else {
381 p = strchr("aecmpyhb", zone[0]);
382 if (!p || zone[2] != 't')
383 goto badzone;
384 zone_off = (strlen(p) - 12) * 60;
385 if (zone[1] == 'd')
386 zone_off += 60;
387 else if (zone[1] != 's')
388 goto badzone;
389 }
390 }
391 }
392 }
393 else
394 badzone:
395 zone_off = 0;
396 }
397 }
398 else
399 badtime:
400 tm.tm_hour = 12;
401
402 tm.tm_isdst = -1;
403
404 if (!dayonly)
405 t = mkgmtime(&tm);
406 else {
407 assert(zone_off == 0);
408 t = mktime(&tm);
409 }
410 if (t >= 0) {
411 *tp = (t - zone_off * 60);
412 return s - origs;
413 }
414
415 baddate:
416 return -1;
417 }
418
419 /*
420 * Parse an RFC 822 (strictly speaking, RFC 2822) date-time
421 * from the @s into a UNIX time_t *@tp. The string @s is
422 * terminated either by a NUL or by an RFC 822 end of header
423 * line (CRLF not followed by whitespace); this allows
424 * parsing dates directly out of mapped messages.
425 *
426 * Returns: number of characters consumed from @s or -1 on error.
427 */
time_from_rfc822(const char * s,time_t * tp)428 EXPORTED int time_from_rfc822(const char *s, time_t *tp)
429 {
430 return parse_rfc822(s, tp, 0);
431 }
432
433 /*
434 * Parse an RFC 822 (strictly speaking, RFC 2822) date-time
435 * from the @s into a UNIX time_t *@tp, but parse only the
436 * date portion, ignoring the time and timezone and returning
437 * a time in the server's timezone. This is a godawful hack
438 * designed to support the Cyrus implementation of the
439 * IMAP SEARCH command.
440 *
441 * Returns: number of characters consumed from @s or -1 on error.
442 */
day_from_rfc822(const char * s,time_t * tp)443 EXPORTED int day_from_rfc822(const char *s, time_t *tp)
444 {
445 return parse_rfc822(s, tp, 1);
446 }
447
offsettime_normalize(struct offsettime * t)448 static int offsettime_normalize(struct offsettime *t)
449 {
450 /* sanity check the date/time (including leap day & second) */
451 if (t->tm.tm_mon < 0 || t->tm.tm_mon > 11 ||
452 t->tm.tm_mday < 1 ||
453 t->tm.tm_mday > monthdays(t->tm.tm_year, t->tm.tm_mon) ||
454 t->tm.tm_hour > 23 || t->tm.tm_min > 59 || t->tm.tm_sec > 60) {
455 return 0;
456 }
457 /* Set day of week and year fields */
458 t->tm.tm_wday = dayofweek(t->tm.tm_year, t->tm.tm_mon, t->tm.tm_mday);
459 t->tm.tm_yday = dayofyear(t->tm.tm_year, t->tm.tm_mon, t->tm.tm_mday);
460 t->tm.tm_isdst = -1;
461 return 1;
462 }
463
464 /*
465 * Parse an RFC 3339 = ISO 8601 format date-time string,
466 * preserving the zone offset.
467 * Returns: number of characters in @s consumed, or -1 on error.
468 */
offsettime_from_iso8601(const char * s,struct offsettime * t)469 EXPORTED int offsettime_from_iso8601(const char *s, struct offsettime *t)
470 {
471 const char *origs = s;
472 int n;
473
474 /* parse the ISO 8601 date/time */
475 /* XXX should use strptime ? */
476 memset(t, 0, sizeof(struct offsettime));
477 n = sscanf(s, "%4d-%2d-%2dT%2d:%2d:%2d",
478 &t->tm.tm_year, &t->tm.tm_mon, &t->tm.tm_mday,
479 &t->tm.tm_hour, &t->tm.tm_min, &t->tm.tm_sec);
480 if (n != 6)
481 return -1;
482
483 s += 19;
484 if (*s == '.') {
485 /* skip fractional secs */
486 while (Uisdigit(*(++s)));
487 }
488
489 /* handle offset */
490 switch (*s++) {
491 case 'Z': t->tm_off = 0; break;
492 case '-': t->tm_off = -1; break;
493 case '+': t->tm_off = 1; break;
494 default: return -1;
495 }
496 if (t->tm_off) {
497 int tm_houroff, tm_minoff;
498
499 n = sscanf(s, "%2d:%2d", &tm_houroff, &tm_minoff);
500 if (n != 2)
501 return -1;
502 t->tm_off *= 60 * (60 * tm_houroff + tm_minoff);
503 s += 5;
504 }
505
506 t->tm.tm_year -= 1900; /* normalize to years since 1900 */
507 t->tm.tm_mon--; /* normalize to months since January */
508
509 if (!offsettime_normalize(t))
510 return -1;
511
512 return s - origs;
513 }
514
515
516 /*
517 * Parse an RFC 3339 = ISO 8601 format date-time string.
518 * Returns: number of characters in @s consumed, or -1 on error.
519 */
time_from_iso8601(const char * s,time_t * tp)520 EXPORTED int time_from_iso8601(const char *s, time_t *tp)
521 {
522 struct offsettime ot;
523
524 int r = offsettime_from_iso8601(s, &ot);
525 if (r < 0) return r;
526
527 /* normalize to GMT */
528 *tp = mkgmtime(&ot.tm) - ot.tm_off;
529 return r;
530 }
531
breakdown_time_to_iso8601(const struct timeval * t,struct tm * tm,enum timeval_precision tv_precision,long gmtoff,char * buf,size_t len,int withsep)532 static int breakdown_time_to_iso8601(const struct timeval *t, struct tm *tm,
533 enum timeval_precision tv_precision,
534 long gmtoff, char *buf, size_t len,
535 int withsep)
536 {
537 int gmtnegative = 0;
538 size_t rlen;
539 const char *datefmt = withsep ? "%Y-%m-%dT%H:%M:%S" : "%Y%m%dT%H%M%S";
540
541 /*assert(date > 0); - it turns out these can happen, annoyingly enough */
542 /*assert(tm->tm_year >= 69);*/
543
544 if (gmtoff < 0) {
545 gmtoff = -gmtoff;
546 gmtnegative = 1;
547 }
548 gmtoff /= 60;
549
550 rlen = strftime(buf, len, datefmt, tm);
551 if (rlen > 0) {
552 switch(tv_precision) {
553 case timeval_ms:
554 rlen += snprintf(buf+rlen, len-rlen, ".%.3lu", t->tv_usec/1000);
555 break;
556 case timeval_us:
557 rlen += snprintf(buf+rlen, len-rlen, ".%.6lu", t->tv_usec);
558 break;
559 case timeval_s:
560 break;
561 }
562
563 /* UTC can be written "Z" or "+00:00" */
564 if (gmtoff == 0)
565 rlen += snprintf(buf+rlen, len-rlen, "Z");
566 else
567 rlen += snprintf(buf+rlen, len-rlen, "%c%.2lu:%.2lu",
568 gmtnegative ? '-' : '+', gmtoff/60, gmtoff%60);
569 }
570
571 return rlen;
572 }
573
574 /*
575 * Generate an RFC 3339 = ISO 8601 format date-time string in Zulu (UTC).
576 *
577 * Returns: number of characters in @buf generated, or -1 on error.
578 */
time_to_iso8601(time_t t,char * buf,size_t len,int withsep)579 EXPORTED int time_to_iso8601(time_t t, char *buf, size_t len, int withsep)
580 {
581 struct tm *tm = (struct tm *) gmtime(&t);
582 struct timeval tv = { t, 0 };
583 long gmtoff = gmtoff_of(tm, tv.tv_sec);
584
585 return breakdown_time_to_iso8601(&tv, tm, timeval_s, gmtoff, buf, len, withsep);
586 }
587
offsettime_to_iso8601(struct offsettime * t,char * buf,size_t len,int withsep)588 EXPORTED int offsettime_to_iso8601(struct offsettime *t, char *buf, size_t len, int withsep)
589 {
590 struct timeval tv = { mktime(&t->tm), 0 };
591 return breakdown_time_to_iso8601(&tv, &t->tm, timeval_s, t->tm_off, buf, len, withsep);
592 }
593
594 /*
595 * Generate an RFC 3339 = ISO 8601 format date-time string in local time with
596 * offset from UTC and fractions of second.
597 *
598 * Returns: number of characters in @buf generated, or -1 on error.
599 */
timeval_to_iso8601(const struct timeval * tv,enum timeval_precision tv_prec,char * buf,size_t len)600 EXPORTED int timeval_to_iso8601(const struct timeval *tv, enum timeval_precision tv_prec,
601 char *buf, size_t len)
602 {
603 struct tm *tm = localtime(&(tv->tv_sec));
604 long gmtoff = gmtoff_of(tm, tv->tv_sec);
605 return breakdown_time_to_iso8601(tv, tm, tv_prec, gmtoff, buf, len, 1);
606 }
607
time_to_rfc3339(time_t t,char * buf,size_t len)608 EXPORTED int time_to_rfc3339(time_t t, char *buf, size_t len)
609 {
610 struct tm *tm = gmtime(&t);
611
612 return snprintf(buf, len, "%04d-%02d-%02dT%02d:%02d:%02dZ",
613 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
614 tm->tm_hour, tm->tm_min, tm->tm_sec);
615 }
616
617 /*
618 * Convert a time_t date to an IMAP-style date
619 * datebuf needs to be >= 30 bytes.
620 *
621 * Returns: number of characters in @buf generated, or -1 on error.
622 */
time_to_rfc3501(time_t date,char * buf,size_t len)623 EXPORTED int time_to_rfc3501(time_t date, char *buf, size_t len)
624 {
625 struct tm *tm = localtime(&date);
626 long gmtoff = gmtoff_of(tm, date);
627 int gmtnegative = 0;
628
629 /*assert(date > 0); - it turns out these can happen, annoyingly enough */
630 assert(tm->tm_year >= 69);
631
632 if (gmtoff < 0) {
633 gmtoff = -gmtoff;
634 gmtnegative = 1;
635 }
636 gmtoff /= 60;
637 return snprintf(buf, len,
638 "%2u-%s-%u %.2u:%.2u:%.2u %c%.2lu%.2lu",
639 tm->tm_mday, monthname[tm->tm_mon], tm->tm_year+1900,
640 tm->tm_hour, tm->tm_min, tm->tm_sec,
641 gmtnegative ? '-' : '+', gmtoff/60, gmtoff%60);
642 }
643
644
645 /*
646 * Parse a string in IMAP date-time format (and some more
647 * obscure legacy formats too) to a time_t. Parses both
648 * date and time parts.
649 *
650 * Specific formats accepted are listed below. Note that only
651 * the first two are compliant with RFC 3501, the remainder
652 * are legacy formats. Note that the " quotes are not part
653 * of the format, they're just used in this comment to show
654 * where the leading spaces are.
655 *
656 * "dd-mmm-yyyy HH:MM:SS zzzzz"
657 * " d-mmm-yyyy HH:MM:SS zzzzz"
658 * "dd-mmm-yy HH:MM:SS z"
659 * " d-mmm-yy HH:MM:SS z"
660 * "dd-mmm-yy HH:MM:SS zz"
661 * " d-mmm-yy HH:MM:SS zz"
662 * "dd-mmm-yy HH:MM:SS zzz"
663 * " d-mmm-yy HH:MM:SS zzz"
664 *
665 * where:
666 * dd is the day-of-month between 1 and 31 inclusive.
667 * mmm is the three-letter abbreviation for the English
668 * month name (case insensitive).
669 * yy is the 2 digit year, between 00 (the year 1900)
670 * and 99 (the year 1999) inclusive.
671 * yyyy is the 4 digit year, between 1900 and disaster
672 * (31b time_t wrapping in 2038 is not handled, sorry).
673 * HH is the hour, zero padded, between 00 and 23 inclusive.
674 * MM is the minute, zero padded, between 00 and 59 inclusive.
675 * MM is the second, zero padded, between 00 and 60 inclusive
676 * (to account for leap seconds).
677 * z is a US military style single character time zone.
678 * A (Alpha) is +0100 ... I (India) is +0900
679 * J (Juliet) is not defined
680 * K (Kilo) is +1000 ... M (Mike) is +1200
681 * N (November) is -0100 ... Y (Yankee) is -1200
682 * Z (Zulu) is UTC
683 * zz is the case-insensitive string "UT", denoting UTC time.
684 * zzz is a three-character case insensitive North American
685 * time zone name, one of the following (listed with the
686 * UTC offsets and comments):
687 * AST -0400 Atlantic Standard Time
688 * ADT -0300 Atlantic Daylight Time
689 * EST -0500 Eastern Standard Time
690 * EDT -0400 Eastern Daylight Time
691 * CST -0600 Central Standard Time
692 * CDT -0500 Central Daylight Time
693 * MST -0700 Mountain Standard Time
694 * MDT -0600 Mountain Daylight Time
695 * PST -0800 Pacific Standard Time
696 * PDT -0700 Pacific Daylight Time
697 * YST -0900 Yukon Standard Time
698 * (Obsolete, now AKST = Alaska S.T.)
699 * YDT -0800 Yukon Daylight Time
700 * (Obsolete, now AKDT = Alaska D.T.)
701 * HST -1000 Hawaiian Standard Time
702 * (Obsolete, now HAST = Hawaiian/Aleutian S.T.)
703 * HDT -0900 Hawaiian Daylight Time
704 * (Obsolete, now HADT = Hawaiian/Aleutian D.T.)
705 * BST -1100 Used in American Samoa & Midway Island
706 * (Obsolete, now SST = Samoa S.T.)
707 * BDT -1000 Nonsensical, standard time is used
708 * all year around in the SST territories.
709 * zzzzz is a numeric time zone offset in the form +HHMM
710 * or -HMMM.
711 *
712 * Returns: Number of characters consumed from @s on success,
713 * or -1 on error.
714 */
time_from_rfc3501(const char * s,time_t * date)715 EXPORTED int time_from_rfc3501(const char *s, time_t *date)
716 {
717 const char *origs = s;
718 int c;
719 struct tm tm;
720 int old_format = 0;
721 char month[4], zone[4], *p;
722 time_t tmp_gmtime;
723 int zone_off; /* timezone offset in minutes */
724
725 memset(&tm, 0, sizeof tm);
726
727 /* Day of month */
728 c = *s++;
729 if (c == ' ')
730 c = '0';
731 else if (!isdigit(c))
732 goto baddate;
733 tm.tm_mday = c - '0';
734
735 c = *s++;
736 if (isdigit(c)) {
737 tm.tm_mday = tm.tm_mday * 10 + c - '0';
738 c = *s++;
739 if (tm.tm_mday <= 0 || tm.tm_mday > 31)
740 goto baddate;
741 }
742
743 if (c != '-')
744 goto baddate;
745 c = *s++;
746
747 /* Month name */
748 if (!isalpha(c))
749 goto baddate;
750 month[0] = c;
751 c = *s++;
752 if (!isalpha(c))
753 goto baddate;
754 month[1] = c;
755 c = *s++;
756 if (!isalpha(c))
757 goto baddate;
758 month[2] = c;
759 c = *s++;
760 month[3] = '\0';
761
762 for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) {
763 if (!strcasecmp(month, monthname[tm.tm_mon]))
764 break;
765 }
766 if (tm.tm_mon == 12)
767 goto baddate;
768
769 if (c != '-')
770 goto baddate;
771 c = *s++;
772
773 /* Year */
774 if (!isdigit(c))
775 goto baddate;
776 tm.tm_year = c - '0';
777 c = *s++;
778 if (!isdigit(c))
779 goto baddate;
780 tm.tm_year = tm.tm_year * 10 + c - '0';
781 c = *s++;
782 if (isdigit(c)) {
783 if (tm.tm_year < 19)
784 goto baddate;
785 tm.tm_year -= 19;
786 tm.tm_year = tm.tm_year * 10 + c - '0';
787 c = *s++;
788 if (!isdigit(c))
789 goto baddate;
790 tm.tm_year = tm.tm_year * 10 + c - '0';
791 c = *s++;
792 }
793 else
794 old_format++;
795
796 if (tm.tm_mday > monthdays(tm.tm_year, tm.tm_mon))
797 goto baddate;
798
799 /* Hour */
800 if (c != ' ')
801 goto baddate;
802 c = *s++;
803 if (!isdigit(c))
804 goto baddate;
805 tm.tm_hour = c - '0';
806 c = *s++;
807 if (!isdigit(c))
808 goto baddate;
809 tm.tm_hour = tm.tm_hour * 10 + c - '0';
810 c = *s++;
811 if (tm.tm_hour > 23)
812 goto baddate;
813
814 /* Minute */
815 if (c != ':')
816 goto baddate;
817 c = *s++;
818 if (!isdigit(c))
819 goto baddate;
820 tm.tm_min = c - '0';
821 c = *s++;
822 if (!isdigit(c))
823 goto baddate;
824 tm.tm_min = tm.tm_min * 10 + c - '0';
825 c = *s++;
826 if (tm.tm_min > 59)
827 goto baddate;
828
829 /* Second */
830 if (c != ':')
831 goto baddate;
832 c = *s++;
833 if (!isdigit(c))
834 goto baddate;
835 tm.tm_sec = c - '0';
836 c = *s++;
837 if (!isdigit(c))
838 goto baddate;
839 tm.tm_sec = tm.tm_sec * 10 + c - '0';
840 c = *s++;
841 if (tm.tm_min > 60)
842 goto baddate;
843
844 /* Time zone */
845 if (old_format) {
846 if (c != ' ')
847 goto baddate;
848 c = *s++;
849
850 if (!isalpha(c))
851 goto baddate;
852 zone[0] = c;
853 c = *s++;
854
855 if (c == '\0') {
856 /* Military (single-char) zones */
857 zone[1] = '\0';
858 lcase(zone);
859 if (zone[0] <= 'i') {
860 zone_off = (zone[0] - 'a' + 1)*60;
861 }
862 else if (zone[0] == 'j') {
863 goto baddate;
864 }
865 else if (zone[0] <= 'm') {
866 zone_off = (zone[0] - 'k' + 10)*60;
867 }
868 else if (zone[0] < 'z') {
869 zone_off = ('m' - zone[0])*60;
870 }
871 else /* 'z' */
872 zone_off = 0;
873 }
874 else {
875 /* UT (universal time) */
876 zone[1] = c;
877 c = *s++;
878 if (c == '\0') {
879 zone[2] = '\0';
880 lcase(zone);
881 if (!strcmp(zone, "ut"))
882 goto baddate;
883 zone_off = 0;
884 }
885 else {
886 /* 3-char time zone */
887 zone[2] = c;
888 c = *s++;
889 if (c != '\0')
890 goto baddate;
891 zone[3] = '\0';
892 lcase(zone);
893 p = strchr("aecmpyhb", zone[0]);
894 if (c != '\0' || zone[2] != 't' || !p)
895 goto baddate;
896 zone_off = (strlen(p) - 12)*60;
897 if (zone[1] == 'd')
898 zone_off += 60;
899 else if (zone[1] != 's')
900 goto baddate;
901 }
902 }
903 }
904 else {
905 if (c != ' ')
906 goto baddate;
907 c = *s++;
908
909 if (c != '+' && c != '-')
910 goto baddate;
911 zone[0] = c;
912
913 c = *s++;
914 if (!isdigit(c))
915 goto baddate;
916 zone_off = c - '0';
917 c = *s++;
918 if (!isdigit(c))
919 goto baddate;
920 zone_off = zone_off * 10 + c - '0';
921 c = *s++;
922 if (!isdigit(c))
923 goto baddate;
924 zone_off = zone_off * 6 + c - '0';
925 c = *s++;
926 if (!isdigit(c))
927 goto baddate;
928 zone_off = zone_off * 10 + c - '0';
929
930 if (zone[0] == '-')
931 zone_off = -zone_off;
932
933 c = *s++;
934 if (c != '\0')
935 goto baddate;
936 }
937
938 tm.tm_isdst = -1;
939
940 tmp_gmtime = mkgmtime(&tm);
941 if (tmp_gmtime == -1)
942 goto baddate;
943
944 *date = tmp_gmtime - zone_off*60;
945
946 return s-1 - origs;
947
948 baddate:
949 return -1;
950 }
951
952 /**
953 ** Support functions for time_from_rfc5322()
954 **/
get_next_char(struct rfc5322dtbuf * buf)955 static inline int get_next_char(struct rfc5322dtbuf *buf)
956 {
957 int c;
958
959 if (buf->offset < buf->len) {
960 buf->offset++;
961 c = buf->str[buf->offset];
962 return c;
963 }
964
965 return EOB;
966 }
967
get_current_char(struct rfc5322dtbuf * buf)968 static inline int get_current_char(struct rfc5322dtbuf *buf)
969 {
970 int offset = buf->offset;
971
972 if (offset < buf->len)
973 return buf->str[offset];
974 else
975 return EOB;
976 }
977
978 /*
979 TODO: Support comments as per RFC.
980 */
skip_ws(struct rfc5322dtbuf * buf,int skipcomment)981 static int skip_ws(struct rfc5322dtbuf *buf,
982 int skipcomment __attribute__((unused)))
983 {
984 int c = buf->str[buf->offset];
985
986 while (c != EOB) {
987 if (rfc5322_special[c]) {
988 c = get_next_char(buf);
989 continue;
990 }
991
992 break;
993 }
994
995 return 1;
996 }
997
get_next_token(struct rfc5322dtbuf * buf,char ** str,int * len)998 static int get_next_token(struct rfc5322dtbuf *buf, char **str, int *len)
999 {
1000 int c, ret = 1;
1001 long ch;
1002 static char cache[RFC5322_DATETIME_MAX];
1003
1004 memset(cache, 1, RFC5322_DATETIME_MAX);
1005
1006 c = get_current_char(buf);
1007 if (c == EOB) {
1008 ret = 0;
1009 goto failed;
1010 }
1011
1012 *len = 0;
1013 for (;;) {
1014 if (rfc5322_special[c] || rfc5322_separators[c])
1015 break;
1016
1017 ch = rfc5322_usascii_charset[c + 1];
1018 if (!(ch & (Alpha | Digit)))
1019 break;
1020
1021 if (*len >= RFC5322_DATETIME_MAX)
1022 break;
1023
1024 cache[*len] = c;
1025 *len += 1;
1026
1027 c = get_next_char(buf);
1028 if (c == EOB) {
1029 ret = 0;
1030 break;
1031 }
1032 }
1033
1034 failed:
1035 *str = cache;
1036
1037 return ret;
1038 }
1039
to_int(char * str,int len)1040 static inline int to_int(char *str, int len)
1041 {
1042 int i, num = 0;
1043
1044 for (i = 0; i < len; i++) {
1045 if (rfc5322_usascii_charset[str[i] + 1] & Digit)
1046 num = num * 10 + (str[i] - '0');
1047 else {
1048 num = -9999;
1049 break;
1050 }
1051 }
1052
1053 return num;
1054 }
1055
to_upper(char ch)1056 static inline int to_upper(char ch)
1057 {
1058 if (rfc5322_usascii_charset[ch + 1] & LAlpha)
1059 ch = ch - 32;
1060
1061 return ch;
1062 }
1063
to_lower(char ch)1064 static inline int to_lower(char ch)
1065 {
1066 if (rfc5322_usascii_charset[ch + 1] & UAlpha)
1067 ch = ch + 32;
1068
1069 return ch;
1070 }
1071
compute_tzoffset(char * str,int len,int sign)1072 static int compute_tzoffset(char *str, int len, int sign)
1073 {
1074 int offset = 0;
1075
1076 if (len == 1) { /* Military timezone */
1077 int ch;
1078 ch = to_upper(str[0]);
1079 if (ch < 'J')
1080 return (str[0] - 'A' + 1) * 60;
1081 if (ch == 'J')
1082 return 0;
1083 if (ch <= 'M')
1084 return (str[0] - 'A') * 60;;
1085 if (ch < 'Z')
1086 return ('M' - str[0]) * 60;
1087
1088 return 0;
1089 }
1090
1091 if (len == 2 &&
1092 to_upper(str[0]) == 'U' &&
1093 to_upper(str[1]) == 'T') { /* Universal Time zone (UT) */
1094 return 0;
1095 }
1096
1097 if (len == 3) {
1098 char *p;
1099
1100 if (to_upper(str[2]) != 'T')
1101 return 0;
1102
1103 p = strchr("AECMPYHB", to_upper(str[0]));
1104 if (!p)
1105 return 0;
1106 offset = (strlen(p) - 12) * 60;
1107
1108 if (to_upper(str[1]) == 'D')
1109 return offset + 60;
1110 if (to_upper(str[1]) == 'S')
1111 return offset;
1112 }
1113
1114 if (len == 4) { /* The number timezone offset */
1115 int i;
1116
1117 for (i = 0; i < len; i++) {
1118 if (!(rfc5322_usascii_charset[str[i] + 1] & Digit))
1119 return 0;
1120 }
1121
1122 offset = ((str[0] - '0') * 10 + (str[1] - '0')) * 60 +
1123 (str[2] - '0') * 10 +
1124 (str[3] - '0');
1125
1126 return (sign == '+') ? offset : -offset;
1127 }
1128
1129 return 0;
1130 }
1131
1132 /*
1133 * Date Format as per https://tools.ietf.org/html/rfc5322#section-3.3:
1134 *
1135 * date-time = [ ([FWS] day-name) "," ]
1136 * ([FWS] 1*2DIGIT FWS)
1137 * month
1138 * (FWS 4*DIGIT FWS)
1139 * 2DIGIT ":" 2DIGIT [ ":" 2DIGIT ]
1140 * (FWS ( "+" / "-" ) 4DIGIT)
1141 * [CFWS]
1142 *
1143 * day-name = "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun"
1144 * month = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / "Jul" / "Aug" /
1145 * "Sep" / "Oct" / "Nov" / "Dec"
1146 *
1147 */
1148
tokenise_str_and_create_tm(struct rfc5322dtbuf * buf,struct tm * tm,long * tz_offset,enum datetime_parse_mode mode)1149 static int tokenise_str_and_create_tm(struct rfc5322dtbuf *buf, struct tm *tm,
1150 long *tz_offset,
1151 enum datetime_parse_mode mode)
1152 {
1153 long ch;
1154 int c, i, len;
1155 char *str_token = NULL;
1156
1157
1158 /* Skip leading WS, if any */
1159 skip_ws(buf, 0);
1160
1161 c = get_current_char(buf);
1162 if (c == EOB)
1163 goto failed;
1164
1165 ch = rfc5322_usascii_charset[c + 1];
1166 if (ch & Alpha) { /* Most likely a weekday at the start. */
1167 if (!get_next_token(buf, &str_token, &len))
1168 goto failed;
1169
1170 /* We might have a weekday token here */
1171 if (len != 3)
1172 goto failed;
1173
1174 /* Determine week day */
1175 int i ;
1176 for (i = 0; i < 7; i++) {
1177 if (!strncasecmp(wday[i], str_token, len)) {
1178 tm->tm_wday = i;
1179 break;
1180 }
1181 }
1182
1183 /* The weekday is followed by a ',', consume that. */
1184 if (get_current_char(buf) == ',')
1185 get_next_char(buf);
1186 else
1187 goto failed;
1188
1189 skip_ws(buf, 0);
1190 }
1191
1192 /** DATE **/
1193 /* date (1 or 2 digits) */
1194 if (!get_next_token(buf, &str_token, &len))
1195 goto failed;
1196
1197 if (len < 1 || len > 2 ||
1198 !(rfc5322_usascii_charset[str_token[0] + 1] & Digit))
1199 goto failed;
1200
1201 tm->tm_mday = to_int(str_token, len);
1202 if (tm->tm_mday == -9999)
1203 goto failed;
1204
1205 /* the separator here is either '-' or FWS */
1206 c = get_next_char(buf);
1207 if (rfc5322_special[c])
1208 skip_ws(buf, 0);
1209
1210 /* month name */
1211 if (!get_next_token(buf, &str_token, &len) ||
1212 len != 3 ||
1213 !(rfc5322_usascii_charset[str_token[0] + 1] & Alpha))
1214 goto failed;
1215
1216 str_token[0] = to_upper(str_token[0]);
1217 str_token[1] = to_lower(str_token[1]);
1218 str_token[2] = to_lower(str_token[2]);
1219 for (i = 0; i < 12; i++) {
1220 if (memcmp(monthname[i], str_token, 3) == 0) {
1221 tm->tm_mon = i;
1222 break;
1223 }
1224 }
1225 if (i == 12)
1226 goto failed;
1227
1228 /* the separator here is either '-' or FWS */
1229 c = get_next_char(buf);
1230 if (rfc5322_special[c])
1231 skip_ws(buf, 0);
1232
1233 /* year 2, 4 or >4 digits */
1234 if (!get_next_token(buf, &str_token, &len))
1235 goto failed;
1236
1237 tm->tm_year = to_int(str_token, len);
1238 if (tm->tm_year == -9999)
1239 goto failed;
1240
1241 if (len == 2) {
1242 /* A 2 digit year */
1243 if (tm->tm_year < 70)
1244 tm->tm_year += 100;
1245 } else {
1246 if (tm->tm_year < 1900)
1247 goto failed;
1248 tm->tm_year -= 1900;
1249 }
1250
1251 if (mode == DATETIME_DATE_ONLY) {
1252 *tz_offset = 0;
1253 goto done;
1254 }
1255
1256 /** TIME **/
1257 skip_ws(buf, 0);
1258 /* hour */
1259 if (!get_next_token(buf, &str_token, &len))
1260 goto failed;
1261
1262 if (len < 1 || len > 2 ||
1263 !(rfc5322_usascii_charset[str_token[0] + 1] & Digit))
1264 goto failed;
1265
1266 tm->tm_hour = to_int(str_token, len);
1267 if (tm->tm_hour == -9999)
1268 goto failed;
1269
1270 /* minutes */
1271 if (get_current_char(buf) == ':' ||
1272 get_current_char(buf) == '.')
1273 get_next_char(buf); /* Consume ':'/'.' */
1274 else
1275 goto failed; /* Something is broken */
1276
1277 if (!get_next_token(buf, &str_token, &len))
1278 goto failed;
1279
1280 if (len < 1 || len > 2 ||
1281 !(rfc5322_usascii_charset[str_token[0] + 1] & Digit))
1282 goto failed;
1283
1284 tm->tm_min = to_int(str_token, len);
1285 if (tm->tm_min == -9999)
1286 goto failed;
1287
1288
1289 /* seconds[optional] */
1290 if (get_current_char(buf) == ':' ||
1291 get_current_char(buf) == '.') {
1292 get_next_char(buf); /* Consume ':'/'.' */
1293
1294 if (!get_next_token(buf, &str_token, &len))
1295 goto failed;
1296
1297 if (len < 1 || len > 2 ||
1298 !(rfc5322_usascii_charset[str_token[0] + 1] & Digit))
1299 goto failed;
1300
1301 tm->tm_sec = to_int(str_token, len);
1302 if (tm->tm_sec == -9999)
1303 goto failed;
1304
1305 }
1306
1307 /* timezone */
1308 skip_ws(buf, 0);
1309 c = get_current_char(buf);
1310 if (c == '+' || c == '-') { /* the '+' or '-' in the timezone */
1311 get_next_char(buf); /* consume '+' or '-' */
1312 }
1313
1314 if (!get_next_token(buf, &str_token, &len)) {
1315 *tz_offset = 0;
1316 } else {
1317 *tz_offset = compute_tzoffset(str_token, len, c);
1318 }
1319
1320 done:
1321 /* dst */
1322 tm->tm_isdst = -1;
1323 *tz_offset *= 60;
1324 return buf->offset;
1325
1326 failed:
1327 return -1;
1328 }
1329
1330
1331 /*
1332 * time_from_rfc5322()
1333 * This is meant to be the replacement function for time_from_rfc822() and
1334 * time_from_rfc3501() functions.
1335 *
1336 * The argument `mode` is set to `DATETIME_DATE_ONLY` when we don't want to
1337 * parse the time and timezone parts of the RFC 5322 date-time string. This is
1338 * a hack designed to support the Cyrus implementation of the IMAP SEARCH
1339 * command.
1340 * For all other cases, the mode should be set to `DATETIME_FULL`.
1341 *
1342 * Returns: Number of characters consumed from @s on success,
1343 * or -1 on error.
1344 */
time_from_rfc5322(const char * s,time_t * date,enum datetime_parse_mode mode)1345 EXPORTED int time_from_rfc5322(const char *s, time_t *date,
1346 enum datetime_parse_mode mode)
1347 {
1348 struct rfc5322dtbuf buf;
1349 struct tm tm;
1350 time_t tmp_time;
1351 long tzone_offset = 0;
1352
1353 if (!s)
1354 goto baddate;
1355
1356 memset(&tm, 0, sizeof(struct tm));
1357 *date = 0;
1358
1359 buf.str = s;
1360 buf.len = strlen(s);
1361 buf.offset = 0;
1362
1363 if (tokenise_str_and_create_tm(&buf, &tm, &tzone_offset, mode) == -1)
1364 goto baddate;
1365
1366 if (mode == DATETIME_DATE_ONLY)
1367 tmp_time = mktime(&tm);
1368 else
1369 tmp_time = mkgmtime(&tm);
1370
1371 if (tmp_time == -1)
1372 goto baddate;
1373
1374 *date = tmp_time - tzone_offset;
1375
1376 return buf.offset;
1377
1378 baddate:
1379 return -1;
1380 }
1381
1382 /*
1383 * Parse a RFC 5322 timestamp into an offset time.
1384 * Wrong week days are ignored.
1385 *
1386 * Returns: Number of characters consumed from @s on success,
1387 * or -1 on error.
1388 */
offsettime_from_rfc5322(const char * s,struct offsettime * t,enum datetime_parse_mode mode)1389 EXPORTED int offsettime_from_rfc5322(const char *s, struct offsettime *t,
1390 enum datetime_parse_mode mode)
1391 {
1392 struct rfc5322dtbuf buf;
1393
1394 if (!s)
1395 goto baddate;
1396
1397 memset(t, 0, sizeof(struct offsettime));
1398
1399 buf.str = s;
1400 buf.len = strlen(s);
1401 buf.offset = 0;
1402
1403 if (tokenise_str_and_create_tm(&buf, &t->tm, &t->tm_off, mode) == -1)
1404 goto baddate;
1405
1406 if (!offsettime_normalize(t))
1407 goto baddate;
1408
1409 return buf.offset;
1410
1411 baddate:
1412 return -1;
1413 }
1414
1415 /*
1416 * time_to_rfc5322()
1417 * Convert a time_t date to an IMAP-style date.
1418 * `buf` which is the buffer this function is going to write into, needs to
1419 * be atleast RFC5322_DATETIME_MAX (32), if not more.
1420 *
1421 */
time_to_rfc5322(time_t date,char * buf,size_t len)1422 EXPORTED int time_to_rfc5322(time_t date, char *buf, size_t len)
1423 {
1424 struct tm *tm = localtime(&date);
1425 long gmtoff = gmtoff_of(tm, date);
1426 int gmtnegative = 0;
1427
1428 if (gmtoff < 0) {
1429 gmtoff = -gmtoff;
1430 gmtnegative = 1;
1431 }
1432
1433 gmtoff /= 60;
1434
1435 return snprintf(buf, len,
1436 "%s, %02d %s %04d %02d:%02d:%02d %c%02lu%02lu",
1437 wday[tm->tm_wday],
1438 tm->tm_mday, monthname[tm->tm_mon], tm->tm_year + 1900,
1439 tm->tm_hour, tm->tm_min, tm->tm_sec,
1440 gmtnegative ? '-' : '+', gmtoff/60, gmtoff%60);
1441 }
1442
offsettime_to_rfc5322(struct offsettime * t,char * buf,size_t len)1443 EXPORTED int offsettime_to_rfc5322(struct offsettime *t, char *buf, size_t len)
1444 {
1445 long gmtoff = t->tm_off;
1446 int gmtnegative = 0;
1447
1448 if (gmtoff < 0) {
1449 gmtoff = -gmtoff;
1450 gmtnegative = 1;
1451 }
1452
1453 gmtoff /= 60;
1454
1455 return snprintf(buf, len,
1456 "%s, %02d %s %04d %02d:%02d:%02d %c%02lu%02lu",
1457 wday[t->tm.tm_wday],
1458 t->tm.tm_mday, monthname[t->tm.tm_mon], t->tm.tm_year + 1900,
1459 t->tm.tm_hour, t->tm.tm_min, t->tm.tm_sec,
1460 gmtnegative ? '-' : '+', gmtoff/60, gmtoff%60);
1461 }
1462