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