1 /**
2 *   PostgreSQL time types binary format.
3 *   
4 *   Copyright: © 2014 DSoftOut
5 *   Authors: NCrashed <ncrashed@gmail.com>
6 */
7 module dpq2.types.time;
8 
9 import dpq2.result;
10 import dpq2.oids;
11 
12 import std.datetime;
13 import std.bitmanip: bigEndianToNative;
14 import std.math;
15 import core.stdc.time: time_t;
16 
17 pure:
18 
19 /// Returns value data as native Date
20 @property Date binaryValueAs(T)(in Value v)
21 if( is( T == Date ) )
22 {
23     if(!(v.oidType == OidType.Date))
24         throwTypeComplaint(v.oidType, "Date", __FILE__, __LINE__);
25 
26     if(!(v.data.length == uint.sizeof))
27         throw new AnswerConvException(ConvExceptionType.SIZE_MISMATCH,
28             "Value length isn't equal to Postgres date type", __FILE__, __LINE__);
29 
30     int jd = bigEndianToNative!uint(v.data.ptr[0..uint.sizeof]);
31     int year, month, day;
32     j2date(jd, year, month, day);
33 
34     return Date(year, month, day);
35 }
36 
37 /// Returns value time without time zone as native TimeOfDay
38 @property TimeOfDay binaryValueAs(T)(in Value v)
39 if( is( T == TimeOfDay ) )
40 {
41     if(!(v.oidType == OidType.Time))
42         throwTypeComplaint(v.oidType, "time without time zone", __FILE__, __LINE__);
43 
44     if(!(v.data.length == TimeADT.sizeof))
45         throw new AnswerConvException(ConvExceptionType.SIZE_MISMATCH,
46             "Value length isn't equal to Postgres time without time zone type", __FILE__, __LINE__);
47 
48     return time2tm(bigEndianToNative!TimeADT(v.data.ptr[0..TimeADT.sizeof]));
49 }
50 
51 /// Returns value timestamp without time zone as TimeStampWithoutTZ
52 @property TimeStampWithoutTZ binaryValueAs(T)(in Value v)
53 if( is( T == TimeStampWithoutTZ ) )
54 {
55     if(!(v.oidType == OidType.TimeStamp))
56         throwTypeComplaint(v.oidType, "timestamp without time zone", __FILE__, __LINE__);
57 
58     if(!(v.data.length == long.sizeof))
59         throw new AnswerConvException(ConvExceptionType.SIZE_MISMATCH,
60             "Value length isn't equal to Postgres timestamp without time zone type", __FILE__, __LINE__);
61 
62     return rawTimeStamp2nativeTime(bigEndianToNative!long(v.data.ptr[0..long.sizeof]));
63 }
64 
65 struct TimeStampWithoutTZ
66 {
67     DateTime dateTime;
68     FracSec fracSec; /// fractional seconds
69 
70     static max()
71     {
72         return TimeStampWithoutTZ(DateTime.max, FracSec.from!"hnsecs"(long.max));
73     }
74 
75     static min()
76     {
77         return TimeStampWithoutTZ(DateTime.min, FracSec.zero);
78     }
79 }
80 
81 private:
82 
83 TimeStampWithoutTZ rawTimeStamp2nativeTime(long raw)
84 {
85     version(Have_Int64_TimeStamp)
86     {
87         if(raw >= time_t.max) return TimeStampWithoutTZ.max;
88         if(raw <= time_t.min) return TimeStampWithoutTZ.min;
89     }
90 
91     pg_tm tm;
92     fsec_t ts;
93 
94     if(timestamp2tm(raw, tm, ts) < 0)
95         throw new AnswerException(
96             ExceptionType.OUT_OF_RANGE, "Timestamp is out of range",
97             __FILE__, __LINE__
98         );
99 
100     return raw_pg_tm2nativeTime(tm, ts);
101 }
102 
103 TimeStampWithoutTZ raw_pg_tm2nativeTime(pg_tm tm, fsec_t ts)
104 {
105     TimeStampWithoutTZ res;
106 
107     res.dateTime = DateTime(
108             tm.tm_year,
109             tm.tm_mon,
110             tm.tm_mday,
111             tm.tm_hour,
112             tm.tm_min,
113             tm.tm_sec
114         );
115 
116     version(Have_Int64_TimeStamp)
117     {
118         res.fracSec = FracSec.from!"usecs"(ts);
119     }
120     else
121     {
122         res.fracSec = FracSec.from!"usecs"((cast(long)(ts * 10e6)));
123     }
124 
125     return res;
126 }
127 
128 pure:
129 
130 // Here is used names from the original Postgresql source
131 
132 void j2date(int jd, out int year, out int month, out int day)
133 {
134     enum POSTGRES_EPOCH_JDATE = 2451545;
135     enum MONTHS_PER_YEAR = 12;
136 
137     jd += POSTGRES_EPOCH_JDATE;
138 
139     uint julian = jd + 32044;
140     uint quad = julian / 146097;
141     uint extra = (julian - quad * 146097) * 4 + 3;
142     julian += 60 + quad * 3 + extra / 146097;
143     quad = julian / 1461;
144     julian -= quad * 1461;
145     int y = julian * 4 / 1461;
146     julian = ((y != 0) ? ((julian + 305) % 365) : ((julian + 306) % 366))
147         + 123;
148     year = (y+ quad * 4) - 4800;
149     quad = julian * 2141 / 65536;
150     day = julian - 7834 * quad / 256;
151     month = (quad + 10) % MONTHS_PER_YEAR + 1;
152 }
153 
154 version(Have_Int64_TimeStamp)
155 {
156     private alias long Timestamp;
157     private alias long TimestampTz;
158     private alias long TimeADT;
159     private alias long TimeOffset;
160     private alias int  fsec_t;      /* fractional seconds (in microseconds) */
161     
162     void TMODULO(ref long t, ref long q, double u)
163     {
164         q = cast(long)(t / u);
165         if (q != 0) t -= q * cast(long)u;
166     }
167 }
168 else
169 {
170     private alias Timestamp = double;
171     private alias TimestampTz = double;
172     private alias TimeADT = double;
173     private alias TimeOffset = double;
174     private alias fsec_t = double;    /* fractional seconds (in seconds) */
175     
176     void TMODULO(T)(ref double t, ref T q, double u)
177         if(is(T == double) || is(T == int))
178     {
179         q = cast(T)((t < 0) ? ceil(t / u) : floor(t / u));
180         if (q != 0) t -= rint(q * u);
181     }
182     
183     double TIMEROUND(double j) 
184     {
185         enum TIME_PREC_INV = 10000000000.0;
186         return rint((cast(double) j) * TIME_PREC_INV) / TIME_PREC_INV;
187     }
188 }
189 
190 TimeOfDay time2tm(TimeADT time)
191 {
192     version(Have_Int64_TimeStamp)
193     {
194         immutable long USECS_PER_HOUR  = 3600000000;
195         immutable long USECS_PER_MINUTE = 60000000;
196         immutable long USECS_PER_SEC = 1000000;
197 
198         int tm_hour = cast(int)(time / USECS_PER_HOUR);
199         time -= tm_hour * USECS_PER_HOUR;
200         int tm_min = cast(int)(time / USECS_PER_MINUTE);
201         time -= tm_min * USECS_PER_MINUTE;
202         int tm_sec = cast(int)(time / USECS_PER_SEC);
203         time -= tm_sec * USECS_PER_SEC;
204 
205         return TimeOfDay(tm_hour, tm_min, tm_sec);
206     }
207     else
208     {
209         enum SECS_PER_HOUR = 3600;
210         enum SECS_PER_MINUTE = 60;
211 
212         double      trem;
213         int tm_hour, tm_min, tm_sec;
214     recalc:
215         trem = time;
216         TMODULO(trem, tm_hour, cast(double) SECS_PER_HOUR);
217         TMODULO(trem, tm_min, cast(double) SECS_PER_MINUTE);
218         TMODULO(trem, tm_sec, 1.0);
219         trem = TIMEROUND(trem);
220         /* roundoff may need to propagate to higher-order fields */
221         if (trem >= 1.0)
222         {
223             time = ceil(time);
224             goto recalc;
225         }
226 
227         return TimeOfDay(tm_hour, tm_min, tm_sec);
228     }
229 }
230 
231 struct pg_tm
232 {
233     int         tm_sec;
234     int         tm_min;
235     int         tm_hour;
236     int         tm_mday;
237     int         tm_mon;         /* origin 0, not 1 */
238     int         tm_year;        /* relative to 1900 */
239     int         tm_wday;
240     int         tm_yday;
241     int         tm_isdst;
242     long        tm_gmtoff;
243     string      tm_zone;
244 }
245 
246 alias pg_time_t = long;
247 
248 immutable ulong SECS_PER_DAY = 86400;
249 immutable ulong POSTGRES_EPOCH_JDATE = 2451545;
250 immutable ulong UNIX_EPOCH_JDATE     = 2440588;
251 
252 immutable ulong USECS_PER_DAY    = 86_400_000_000;
253 immutable ulong USECS_PER_HOUR   = 3_600_000_000;
254 immutable ulong USECS_PER_MINUTE = 60_000_000;
255 immutable ulong USECS_PER_SEC    = 1_000_000;
256 
257 immutable ulong SECS_PER_HOUR   = 3600;
258 immutable ulong SECS_PER_MINUTE = 60;
259 
260 /**
261 * timestamp2tm() - Convert timestamp data type to POSIX time structure.
262 *
263 * Note that year is _not_ 1900-based, but is an explicit full value.
264 * Also, month is one-based, _not_ zero-based.
265 * Returns:
266 *   0 on success
267 *  -1 on out of range
268 *
269 * If attimezone is null, the global timezone (including possibly brute forced
270 * timezone) will be used.
271 */
272 int timestamp2tm(Timestamp dt, out pg_tm tm, out fsec_t fsec)
273 {
274     Timestamp   date;
275     Timestamp   time;
276     pg_time_t   utime;
277 
278     version(Have_Int64_TimeStamp)
279     {
280         time = dt;
281         TMODULO(time, date, USECS_PER_DAY);
282 
283         if (time < 0)
284         {
285             time += USECS_PER_DAY;
286             date -= 1;
287         }
288 
289         j2date(cast(int) date, tm.tm_year, tm.tm_mon, tm.tm_mday);
290         dt2time(time, tm.tm_hour, tm.tm_min, tm.tm_sec, fsec);
291     } else
292     {
293         time = dt;
294         TMODULO(time, date, cast(double) SECS_PER_DAY);
295 
296         if (time < 0)
297         {
298             time += SECS_PER_DAY;
299             date -= 1;
300         }
301 
302     recalc_d:
303         j2date(cast(int) date, tm.tm_year, tm.tm_mon, tm.tm_mday);
304     recalc_t:
305         dt2time(time, tm.tm_hour, tm.tm_min, tm.tm_sec, fsec);
306 
307         fsec = TSROUND(fsec);
308         /* roundoff may need to propagate to higher-order fields */
309         if (fsec >= 1.0)
310         {
311             time = cast(Timestamp)ceil(time);
312             if (time >= cast(double) SECS_PER_DAY)
313             {
314                 time = 0;
315                 date += 1;
316                 goto recalc_d;
317             }
318             goto recalc_t;
319         }
320     }
321 
322     return 0;
323 }
324 
325 void dt2time(Timestamp jd, out int hour, out int min, out int sec, out fsec_t fsec)
326 {
327     TimeOffset  time;
328 
329     time = jd;
330     version(Have_Int64_TimeStamp)
331     {
332         hour = cast(int)(time / USECS_PER_HOUR);
333         time -= hour * USECS_PER_HOUR;
334         min = cast(int)(time / USECS_PER_MINUTE);
335         time -= min * USECS_PER_MINUTE;
336         sec = cast(int)(time / USECS_PER_SEC);
337         fsec = cast(int)(time - sec*USECS_PER_SEC);
338     } else
339     {
340         hour = cast(int)(time / SECS_PER_HOUR);
341         time -= hour * SECS_PER_HOUR;
342         min = cast(int)(time / SECS_PER_MINUTE);
343         time -= min * SECS_PER_MINUTE;
344         sec = cast(int)time;
345         fsec = cast(int)(time - sec);
346     }
347 }