1 /******************************************************************************
2 
3     Formats an UNIX time value to a HTTP compliant date/time string
4 
5     Formats an UNIX time value to a HTTP compliant (RFC 1123) date/time string.
6     Contains a static length array as string buffer to provide
7     memory-friendliness.
8 
9     Copyright:
10         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
11         All rights reserved.
12 
13     License:
14         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
15         Alternatively, this file may be distributed under the terms of the Tango
16         3-Clause BSD License (see LICENSE_BSD.txt for details).
17 
18  ******************************************************************************/
19 
20 module ocean.net.http.time.HttpTimeFormatter;
21 
22 
23 import ocean.meta.types.Qualifiers;
24 import ocean.core.Verify;
25 import core.stdc.time:       time_t, tm, time;
26 import core.sys.posix.time: gmtime_r, localtime_r;
27 import core.stdc.stdlib:     lldiv;
28 
29 version (unittest) import ocean.core.Test;
30 
31 /******************************************************************************/
32 
33 struct HttpTimeFormatter
34 {
35     /**************************************************************************
36 
37         Date/time string length constant
38 
39      **************************************************************************/
40 
41     public enum size_t ResultLength = "Sun, 06 Nov 1994 08:49:37 GMT".length;
42 
43     /**************************************************************************
44 
45         Callback function to obtain the wall clock time. By default (null) the
46         system time is queried using time() of the C stdlib.
47         An application may set its own time function, if desired.
48 
49      **************************************************************************/
50 
51     public static time_t function ( ) now = null;
52 
53     /**************************************************************************
54 
55         Date/time string destination buffer
56 
57      **************************************************************************/
58 
59     private char[ResultLength] buf;
60 
61     /**************************************************************************
62 
63         Weekday/month name constants
64 
65      **************************************************************************/
66 
67     private enum istring[7]  weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
68     private enum istring[12] months   = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
69 
70     /**************************************************************************
71 
72         Generates a HTTP compliant date/time string (asctime) from t.
73 
74         Params:
75             t = UNIX time value to be formatted as HTTP date/time string
76 
77         Returns:
78             HTTP date/time string from UNIX time value t. Do not modify (exposes
79             an internal buffer).
80 
81         Throws:
82             Exception if formatting failed (supposed never to happen)
83 
84      **************************************************************************/
85 
86     public mstring format ( time_t t )
87     {
88         return this.format(this.buf, t);
89     }
90 
91     /**************************************************************************
92 
93         Ditto; uses the current wall clock time.
94 
95         Returns:
96             HTTP date/time string from UNIX time value t. Do not modify (exposes
97             an internal buffer).
98 
99         Throws:
100             Exception if formatting failed (supposed never to happen)
101 
102      **************************************************************************/
103 
104     public mstring format ( )
105     {
106         return this.format(this.buf);
107     }
108 
109     /**************************************************************************
110 
111         Generates a HTTP compliant date/time string from t and stores it in dst.
112         dst.length must be ResultLength.
113 
114         Params:
115             dst      = destination string
116             t        = UNIX time value to be formatted as HTTP date/time string
117 
118         Returns:
119             slice to valid result data in dst, starting at dst[0]
120 
121          Throws:
122             Exception if formatting failed (supposed never to happen)
123 
124     **************************************************************************/
125 
126     public static mstring format ( mstring dst, time_t t )
127     {
128         verify(dst.length == ResultLength);
129 
130         tm  datetime;
131 
132         tm* datetimep = gmtime_r(&t, &datetime);
133 
134         if (datetimep is null) throw new Exception("time conversion failed", __FILE__, __LINE__);
135 
136         with (*datetimep)
137         {
138             dst[ 0 ..  3] = weekdays[tm_wday];
139             dst[ 3 ..  5] = ", ";
140             fmt(dst[ 5 ..  7], tm_mday);
141             dst[ 7      ] = ' ';
142             dst[ 8 .. 11] = months[tm_mon];
143             dst[11      ] = ' ';
144             fmt(dst[12 .. 16], tm_year + 1900);
145             dst[16      ] = ' ';
146             fmt(dst[17 .. 19], tm_hour);
147             dst[19      ] = ':';
148             fmt(dst[20 .. 22], tm_min);
149             dst[22      ] = ':';
150             fmt(dst[23 .. 25], tm_sec);
151         }
152 
153         dst[$ - " GMT".length .. $] = " GMT";
154 
155         return dst;
156     }
157 
158     /**************************************************************************
159 
160         Ditto; uses the current wall clock time.
161 
162         Params:
163             dst = destination string
164 
165         Returns:
166             slice to valid result data in dst, starting at dst[0]
167 
168          Throws:
169             Exception if formatting failed (supposed never to happen)
170 
171     **************************************************************************/
172 
173     public static mstring format ( mstring dst )
174     {
175         return format(dst, now? now() : time(null));
176     }
177 
178     /**************************************************************************
179 
180         Converts n to a decimal string, left-padding with '0'.
181         n must be at least 0 and fit into dst (be less than 10 ^ dst.length).
182 
183         Params:
184             dst = destination string
185             n   = number to convert
186 
187     **************************************************************************/
188 
189     private static void fmt ( mstring dst, long n )
190     out
191     {
192         assert (!n, "decimal formatting overflow");
193     }
194     do
195     {
196         verify(n >= 0);
197 
198         foreach_reverse (ref c; dst)
199         {
200             with (lldiv(n, 10))
201             {
202                 c = cast(char) (rem + '0');
203                 n = quot;
204             }
205         }
206     }
207 }
208 
209 
210 unittest
211 {
212     char[HttpTimeFormatter.ResultLength] buf;
213     test (HttpTimeFormatter.format(buf, 352716457) == "Fri, 06 Mar 1981 08:47:37 GMT");
214 }