1/*++
2/* NAME
3/*	mail_date 3
4/* SUMMARY
5/*	return formatted time
6/* SYNOPSIS
7/*	#include <mail_date.h>
8/*
9/*	const char *mail_date(when)
10/*	time_t	when;
11/* DESCRIPTION
12/*	mail_date() converts the time specified in \fIwhen\fR to the
13/*	form: "Mon, 9 Dec 1996 05:38:26 -0500 (EST)" and returns
14/*	a pointer to the result. The result is overwritten upon
15/*	each call.
16/* DIAGNOSTICS
17/*	Panic: the offset from UTC is more than a whole day. Fatal
18/*	error: out of memory.
19/* LICENSE
20/* .ad
21/* .fi
22/*	The Secure Mailer license must be distributed with this software.
23/* AUTHOR(S)
24/*	Wietse Venema
25/*	IBM T.J. Watson Research
26/*	P.O. Box 704
27/*	Yorktown Heights, NY 10598, USA
28/*--*/
29
30/* System library. */
31
32#include <sys_defs.h>
33#include <time.h>
34#include <stdlib.h>
35
36/* Utility library. */
37
38#include <msg.h>
39#include <vstring.h>
40
41/* Global library. */
42
43#include "mail_date.h"
44
45 /*
46  * Application-specific.
47  */
48#define DAY_MIN		(24 * HOUR_MIN)	/* minutes in a day */
49#define	HOUR_MIN	60		/* minutes in an hour */
50#define MIN_SEC		60		/* seconds in a minute */
51
52/* mail_date - return formatted time */
53
54const char *mail_date(time_t when)
55{
56    static VSTRING *vp;
57    struct tm *lt;
58    struct tm gmt;
59    int     gmtoff;
60
61    /*
62     * As if strftime() isn't expensive enough, we're dynamically adjusting
63     * the size for the result, so we won't be surprised by long names etc.
64     */
65    if (vp == 0)
66	vp = vstring_alloc(100);
67    else
68	VSTRING_RESET(vp);
69
70    /*
71     * POSIX does not require that struct tm has a tm_gmtoff field, so we
72     * must compute the time offset from UTC by hand.
73     *
74     * Starting with the difference in hours/minutes between 24-hour clocks,
75     * adjust for differences in years, in yeardays, and in (leap) seconds.
76     *
77     * Assume 0..23 hours in a day, 0..59 minutes in an hour. The library spec
78     * has changed: we can no longer assume that there are 0..59 seconds in a
79     * minute.
80     */
81    gmt = *gmtime(&when);
82    lt = localtime(&when);
83    gmtoff = (lt->tm_hour - gmt.tm_hour) * HOUR_MIN + lt->tm_min - gmt.tm_min;
84    if (lt->tm_year < gmt.tm_year)
85	gmtoff -= DAY_MIN;
86    else if (lt->tm_year > gmt.tm_year)
87	gmtoff += DAY_MIN;
88    else if (lt->tm_yday < gmt.tm_yday)
89	gmtoff -= DAY_MIN;
90    else if (lt->tm_yday > gmt.tm_yday)
91	gmtoff += DAY_MIN;
92    if (lt->tm_sec <= gmt.tm_sec - MIN_SEC)
93	gmtoff -= 1;
94    else if (lt->tm_sec >= gmt.tm_sec + MIN_SEC)
95	gmtoff += 1;
96
97    /*
98     * First, format the date and wall-clock time. XXX The %e format (day of
99     * month, leading zero replaced by blank) isn't in my POSIX book, but
100     * many vendors seem to support it.
101     */
102#ifdef MISSING_STRFTIME_E
103#define STRFTIME_FMT "%a, %d %b %Y %H:%M:%S "
104#else
105#define STRFTIME_FMT "%a, %e %b %Y %H:%M:%S "
106#endif
107
108    while (strftime(vstring_end(vp), vstring_avail(vp), STRFTIME_FMT, lt) == 0)
109	VSTRING_SPACE(vp, 100);
110    VSTRING_SKIP(vp);
111
112    /*
113     * Then, add the UTC offset.
114     */
115    if (gmtoff < -DAY_MIN || gmtoff > DAY_MIN)
116	msg_panic("UTC time offset %d is larger than one day", gmtoff);
117    vstring_sprintf_append(vp, "%+03d%02d", (int) (gmtoff / HOUR_MIN),
118			   (int) (abs(gmtoff) % HOUR_MIN));
119
120    /*
121     * Finally, add the time zone name.
122     */
123    while (strftime(vstring_end(vp), vstring_avail(vp), " (%Z)", lt) == 0)
124	VSTRING_SPACE(vp, vstring_avail(vp) + 100);
125    VSTRING_SKIP(vp);
126
127    return (vstring_str(vp));
128}
129
130#ifdef TEST
131
132#include <vstream.h>
133
134int     main(void)
135{
136    vstream_printf("%s\n", mail_date(time((time_t *) 0)));
137    vstream_fflush(VSTREAM_OUT);
138    return (0);
139}
140
141#endif
142