1 // Written in D programming language
2 /**
3 *   PostgreSQL time types binary format.
4 *   
5 *   There are following supported libpq formats:
6 *   <ul>
7 *   <li>$(B date) - handles year, month, day. Corresponding D type - $(B std.datetime.Date).</li>
8 *   <li>$(B abstime) - unix time in seconds without timezone. Corresponding D type - $(B PGAbsTime) 
9 *        wrapper around $(B std.datetime.SysTime).</li>
10 *   <li>$(B reltime) - seconds count positive or negative for representation of time durations.
11 *        Corresponding D type - $(B PGRelTime) wrapper around $(B core.time.Duration). Note that
12 *        D's duration holds hnsecs count, but reltime precise at least seconds.</li>
13 *   <li>$(B time) - day time without time zone. Corresponding D type - $(B PGTime) wrapper around
14 *        $(B std.datetime.TimeOfDay).</li>
15 *   <li>$(B time with zone) - day time with time zone. Corresponding D type - $(B PGTimeWithZone)
16 *        structure that can be casted to $(B std.datetime.TimeOfDay) and $(B std.datetime.SimpleTimeZone).</li>
17 *   <li>$(B interval) - time duration (modern replacement for $(B reltime)). Corresponding D time - 
18 *        $(B TimeInterval) that handles microsecond, day and month counts.</li>
19 *   <li>$(B tinterval) - interval between two points in time. Consists of two abstime values: begin and end.
20 *        Correponding D type - $(B PGInterval) wrapper around $(B std.datetime.Interval).</li>
21 *   </ul>
22 *
23 *   Copyright: © 2014 DSoftOut
24 *   License: Subject to the terms of the MIT license, as written in the included LICENSE file.
25 *   Authors: NCrashed <ncrashed@gmail.com>
26 */
27 module pgator.db.pq.types.time;
28 
29 import pgator.db.pq.types.oids;
30 import pgator.db.connection;
31 import core.stdc.time;
32 import std.datetime;
33 import std.bitmanip;
34 import vibe.data.bson;
35 import std.math;
36 
37 private void j2date(int jd, out int year, out int month, out int day)
38 {
39     enum POSTGRES_EPOCH_JDATE = 2451545;
40     enum MONTHS_PER_YEAR = 12;
41 
42     jd += POSTGRES_EPOCH_JDATE;
43     
44     uint julian = jd + 32044;
45     uint quad = julian / 146097;
46     uint extra = (julian - quad * 146097) * 4 + 3;
47     julian += 60 + quad * 3 + extra / 146097;
48     quad = julian / 1461;
49     julian -= quad * 1461;
50     int y = julian * 4 / 1461;
51     julian = ((y != 0) ? ((julian + 305) % 365) : ((julian + 306) % 366))
52         + 123;
53     year = (y+ quad * 4) - 4800;
54     quad = julian * 2141 / 65536;
55     day = julian - 7834 * quad / 256;
56     month = (quad + 10) % MONTHS_PER_YEAR + 1;
57 }
58 
59 Date convert(PQType type)(ubyte[] val)
60     if(type == PQType.Date)
61 {
62     assert(val.length == uint.sizeof);
63     uint raw = val.read!uint;
64     int year, month, day;
65     j2date(raw, year, month, day);
66     
67     return Date(year, month, day);
68 }
69 
70 /**
71 *   Wrapper around SysTime to handle libpq abstime.
72 *
73 *   Note: abstime is stored in UTC timezone.
74 */
75 struct PGAbsTime
76 {
77     private SysTime time;
78     alias time this;
79     
80     static PGAbsTime fromBson(Bson bson)
81     {
82         auto val = SysTime.fromISOExtString(bson.get!string);
83         return PGAbsTime(val);
84     }
85     
86     Bson toBson() const
87     {
88         return Bson(time.toISOExtString);
89     }
90 }
91 
92 PGAbsTime convert(PQType type)(ubyte[] val)
93     if(type == PQType.AbsTime)
94 {
95     assert(val.length == 4);
96     return PGAbsTime(SysTime(unixTimeToStdTime(val.read!int), UTC()));
97 }
98 
99 /**
100 *   Wrapper around Duration to handle libpq reltime.
101 *
102 *   Note: reltime can be negative.
103 */
104 struct PGRelTime
105 {
106     private Duration dur;
107     alias dur this;
108     
109     static PGRelTime fromBson(Bson bson)
110     {
111         return PGRelTime(bson.get!long.dur!"seconds");
112     }
113     
114     Bson toBson() const
115     {
116         return Bson(dur.total!"seconds");
117     }
118 }
119 
120 PGRelTime convert(PQType type)(ubyte[] val)
121     if(type == PQType.RelTime)
122 {
123     assert(val.length == 4);
124     return PGRelTime(val.read!int.dur!"seconds");
125 }
126 
127 version(Have_Int64_TimeStamp)
128 {
129     private alias long Timestamp;
130     private alias long TimestampTz;
131     private alias long TimeADT;
132     private alias long TimeOffset;
133     private alias int  fsec_t;      /* fractional seconds (in microseconds) */
134     
135     void TMODULO(ref long t, ref long q, double u)
136     {
137         q = cast(long)(t / u);
138         if (q != 0) t -= q * cast(long)u;
139     }
140 }
141 else
142 {
143     import std.math;
144     
145     private alias double Timestamp;
146     private alias double TimestampTz;
147     private alias double TimeADT;
148     private alias double TimeOffset;
149     private alias double fsec_t;    /* fractional seconds (in seconds) */
150     
151     void TMODULO(T)(ref double t, ref T q, double u)
152         if(is(T == double) || is(T == int))
153     {
154         q = cast(T)((t < 0) ? ceil(t / u) : floor(t / u));
155         if (q != 0) t -= rint(q * u);
156     }
157     
158     double TIMEROUND(double j) 
159     {
160         enum TIME_PREC_INV = 10000000000.0;
161         return rint((cast(double) j) * TIME_PREC_INV) / TIME_PREC_INV;
162     }
163 }
164 
165 private TimeOfDay time2tm(TimeADT time)
166 {
167     version(Have_Int64_TimeStamp)
168     {
169         immutable long USECS_PER_HOUR  = 3600000000;
170         immutable long USECS_PER_MINUTE = 60000000;
171         immutable long USECS_PER_SEC = 1000000;
172         
173         int tm_hour = cast(int)(time / USECS_PER_HOUR);
174         time -= tm_hour * USECS_PER_HOUR;
175         int tm_min = cast(int)(time / USECS_PER_MINUTE);
176         time -= tm_min * USECS_PER_MINUTE;
177         int tm_sec = cast(int)(time / USECS_PER_SEC);
178         time -= tm_sec * USECS_PER_SEC;
179         
180         return TimeOfDay(tm_hour, tm_min, tm_sec);
181     }
182     else
183     {    
184         enum SECS_PER_HOUR = 3600;
185         enum SECS_PER_MINUTE = 60;
186         
187         double      trem;
188         int tm_hour, tm_min, tm_sec;
189     recalc:
190         trem = time;
191         TMODULO(trem, tm_hour, cast(double) SECS_PER_HOUR);
192         TMODULO(trem, tm_min, cast(double) SECS_PER_MINUTE);
193         TMODULO(trem, tm_sec, 1.0);
194         trem = TIMEROUND(trem);
195         /* roundoff may need to propagate to higher-order fields */
196         if (trem >= 1.0)
197         {
198             time = ceil(time);
199             goto recalc;
200         }
201         return TimeOfDay(tm_hour, tm_min, tm_sec);
202     }
203 }
204 
205 /**
206 *   Wrapper around TimeOfDay to allow serializing to bson.
207 */
208 struct PGTime
209 {
210     private TimeOfDay time;
211     alias time this;
212     
213     static PGTime fromBson(Bson bson)
214     {
215         return PGTime(TimeOfDay(bson.hour.get!int, bson.minute.get!int, bson.second.get!int));
216     }
217     
218     Bson toBson() const
219     {
220         Bson[string] map;
221         map["hour"] = time.hour;
222         map["minute"] = time.minute;
223         map["second"] = time.second;
224         return Bson(map);
225     }
226 }
227 
228 PGTime convert(PQType type)(ubyte[] val)
229     if(type == PQType.Time)
230 {
231     assert(val.length == 8);
232     return PGTime(time2tm(val.read!TimeADT));
233 }
234 
235 /**
236 *   Represents PostgreSQL Time with TimeZone.
237 *   Time zone is stored as UTC offset in seconds without DST.
238 */
239 struct PGTimeWithZone
240 {
241     int hour, minute, second, timeZoneOffset;
242     
243     this(TimeOfDay tm, const SimpleTimeZone tz) pure
244     {
245         hour = tm.hour;
246         minute = tm.minute;
247         second = tm.second;
248         
249         static if (__VERSION__ < 2066) {
250         	timeZoneOffset = cast(int)tz.utcOffset.dur!"minutes".total!"seconds";
251     	} else {
252     		timeZoneOffset = cast(int)tz.utcOffset.total!"seconds";
253     	}
254     }
255     
256     T opCast(T)() const if(is(T == TimeOfDay))
257     {
258         return TimeOfDay(hour, minute, second);
259     }
260     
261     T opCast(T)() const if(is(T == immutable SimpleTimeZone))
262     {
263         return new immutable SimpleTimeZone(dur!"seconds"(timeZoneOffset));
264     }
265 }
266 
267 PGTimeWithZone convert(PQType type)(ubyte[] val)
268     if(type == PQType.TimeWithZone)
269 {
270     assert(val.length == 12);
271     return PGTimeWithZone(time2tm(val.read!TimeADT), new immutable SimpleTimeZone(-val.read!int.dur!"seconds"));
272 }
273 
274 /**
275 *   PostgreSQL time interval isn't same with D std.datetime one.
276 *   It is simple Duration.
277 *
278 *   Consists of: microseconds $(B time), $(B day) count and $(B month) count.
279 *   Libpq uses different represantation for $(B time), but i store only 
280 *   in usecs format.
281 */
282 struct TimeInterval
283 {
284     // in microseconds
285     long        time;           /* all time units other than days, months and
286                                  * years */
287     int         day;            /* days, after time for alignment */
288     int         month;          /* months and years, after time for alignment */
289     
290     this(ubyte[] arr)
291     {
292         assert(arr.length == TimeOffset.sizeof + 2*int.sizeof);
293         version(Have_Int64_TimeStamp)
294         {
295             time  = arr.read!long;
296         }
297         else
298         {
299             time  = cast(long)(arr.read!double * 10e6);
300         }
301         
302         day   = arr.read!int;
303         month = arr.read!int;
304     }
305 }
306 
307 TimeInterval convert(PQType type)(ubyte[] val)
308     if(type == PQType.TimeInterval)
309 {
310     return TimeInterval(val);
311 }
312 
313 /**
314 *   Wrapper around std.datetime.Interval to handle [de]serializing
315 *   acceptable for JSON-RPC and still conform with libpq format.
316 *
317 *   Note: libpq representation of abstime slightly different from
318 *   std.datetime, thats why time converted to string could differ
319 *   a lot for PostgreSQL and SysTime (about 9000-15000 seconds nonconstant 
320 *   offset). 
321 */
322 struct PGInterval
323 {
324     private Interval!SysTime interval;
325     alias interval this;
326 
327     this(Interval!SysTime val)
328     {
329     	interval = val;
330     }
331     
332     static PGInterval fromBson(Bson bson)
333     {
334         auto begin = SysTime.fromISOExtString(bson.begin.get!string);
335         auto end   = SysTime.fromISOExtString(bson.end.get!string);
336         return PGInterval(Interval!SysTime(begin, end));
337     }
338     
339     Bson toBson() const
340     {
341         Bson[string] map;
342         map["begin"] = Bson(interval.begin.toISOExtString);
343         map["end"]   = Bson(interval.end.toISOExtString);
344         return Bson(map);
345     }
346 }
347 
348 /// Avoiding linking problems when std.datetime.Interval invariant isn't generated
349 /// by dmd. See at: std.datetime.Interval:18404
350 private debug extern(C) void _D3std8datetime36__T8IntervalTS3std8datetime7SysTimeZ8Interval11__invariantMxFNaZv()
351 {
352 	
353 }
354 
355 PGInterval convert(PQType type)(ubyte[] val)
356     if(type == PQType.Interval)
357 {
358     assert(val.length == 3*int.sizeof);
359     auto state = val.read!int;
360     auto beg = SysTime(unixTimeToStdTime(val.read!int), UTC());
361     auto end = SysTime(unixTimeToStdTime(val.read!int), UTC());
362 
363     return PGInterval(Interval!SysTime(beg, end));
364 }
365  
366 private
367 {
368     struct pg_tm
369     {
370         int         tm_sec;
371         int         tm_min;
372         int         tm_hour;
373         int         tm_mday;
374         int         tm_mon;         /* origin 0, not 1 */
375         int         tm_year;        /* relative to 1900 */
376         int         tm_wday;
377         int         tm_yday;
378         int         tm_isdst;
379         long        tm_gmtoff;
380         string      tm_zone;
381     }
382     
383     alias long pg_time_t;
384     struct pg_tz;
385     
386     immutable ulong SECS_PER_DAY = 86400;
387     immutable ulong POSTGRES_EPOCH_JDATE = 2451545;
388     immutable ulong UNIX_EPOCH_JDATE     = 2440588;
389     
390     immutable ulong USECS_PER_DAY    = 86_400_000_000;
391     immutable ulong USECS_PER_HOUR   = 3_600_000_000;
392     immutable ulong USECS_PER_MINUTE = 60_000_000;
393     immutable ulong USECS_PER_SEC    = 1_000_000;
394     
395     immutable ulong SECS_PER_HOUR   = 3600;
396     immutable ulong SECS_PER_MINUTE = 60;
397     
398     /*
399      *  Round off to MAX_TIMESTAMP_PRECISION decimal places.
400      *  Note: this is also used for rounding off intervals.
401      */
402     enum TS_PREC_INV = 1000000.0;
403     fsec_t TSROUND(fsec_t j)
404     {
405         return cast(fsec_t)(rint((cast(double) (j)) * TS_PREC_INV) / TS_PREC_INV);
406     }
407 }
408 
409  /*
410  * timestamp2tm() - Convert timestamp data type to POSIX time structure.
411  *
412  * Note that year is _not_ 1900-based, but is an explicit full value.
413  * Also, month is one-based, _not_ zero-based.
414  * Returns:
415  *   0 on success
416  *  -1 on out of range
417  *
418  * If attimezone is null, the global timezone (including possibly brute forced
419  * timezone) will be used.
420  */
421 private int timestamp2tm(Timestamp dt, out pg_tm tm, out fsec_t fsec)
422 {
423     Timestamp   date;
424     Timestamp   time;
425     pg_time_t   utime;
426 
427     version(Have_Int64_TimeStamp)
428     {
429         time = dt;
430         TMODULO(time, date, USECS_PER_DAY);
431 
432         if (time < 0)
433         {
434             time += USECS_PER_DAY;
435             date -= 1;
436         }       
437     
438         j2date(cast(int) date, tm.tm_year, tm.tm_mon, tm.tm_mday);
439         dt2time(time, tm.tm_hour, tm.tm_min, tm.tm_sec, fsec);
440     } else
441     {
442         time = dt;
443         TMODULO(time, date, cast(double) SECS_PER_DAY);
444     
445         if (time < 0)
446         {
447             time += SECS_PER_DAY;
448             date -= 1;
449         }    
450          
451     recalc_d:
452         j2date(cast(int) date, tm.tm_year, tm.tm_mon, tm.tm_mday);
453     recalc_t:
454         dt2time(time, tm.tm_hour, tm.tm_min, tm.tm_sec, fsec);
455     
456         fsec = TSROUND(fsec);
457         /* roundoff may need to propagate to higher-order fields */
458         if (fsec >= 1.0)
459         {
460             time = cast(Timestamp)ceil(time);
461             if (time >= cast(double) SECS_PER_DAY)
462             {
463                 time = 0;
464                 date += 1;
465                 goto recalc_d;
466             }
467             goto recalc_t;
468         }
469     }
470 
471     return 0;
472 }
473 
474 private void dt2time(Timestamp jd, out int hour, out int min, out int sec, out fsec_t fsec)
475 {
476     TimeOffset  time;
477 
478     time = jd;
479     version(Have_Int64_TimeStamp)
480     {
481         hour = cast(int)(time / USECS_PER_HOUR);
482         time -= hour * USECS_PER_HOUR;
483         min = cast(int)(time / USECS_PER_MINUTE);
484         time -= min * USECS_PER_MINUTE;
485         sec = cast(int)(time / USECS_PER_SEC);
486         fsec = cast(int)(time - sec*USECS_PER_SEC);
487     } else
488     {
489         hour = cast(int)(time / SECS_PER_HOUR);
490         time -= hour * SECS_PER_HOUR;
491         min = cast(int)(time / SECS_PER_MINUTE);
492         time -= min * SECS_PER_MINUTE;
493         sec = cast(int)time;
494         fsec = cast(int)(time - sec);
495     }
496 } 
497 
498 /**
499 *   Wrapper around std.datetime.SysTime to handle [de]serializing of libpq
500 *   timestamps (without time zone).
501 *
502 *   Note: libpq has two compile configuration with HAS_INT64_TIMESTAMP and
503 *   without (double timestamp format). The pgator should be compiled with
504 *   conform flag to operate properly. 
505 */
506 struct PGTimeStamp
507 {
508     private SysTime time;
509     alias time this;
510     
511     this(SysTime time)
512     {
513         this.time = time;
514     }
515     
516     this(pg_tm tm, fsec_t ts)
517     {
518         time = SysTime(Date(tm.tm_year, tm.tm_mon, tm.tm_mday), UTC());
519         time += (tm.tm_hour % 24).dur!"hours";
520         time += (tm.tm_min % 60).dur!"minutes";
521         time += (tm.tm_sec  % 60).dur!"seconds";
522         version(Have_Int64_TimeStamp)
523         {
524             time += ts.dur!"usecs";
525         } else
526         {
527             time += (cast(long)(ts*10e6)).dur!"usecs";
528         }
529     }
530     
531     static PGTimeStamp fromBson(Bson bson)
532     {
533         auto val = SysTime.fromISOExtString(bson.get!string);
534         return PGTimeStamp(val);
535     }
536     
537     Bson toBson() const
538     {
539         return Bson(time.toISOExtString);
540     }
541 }
542 
543 PGTimeStamp convert(PQType type)(ubyte[] val)
544     if(type == PQType.TimeStamp)
545 {
546     auto raw = val.read!long;
547     
548     version(Have_Int64_TimeStamp)
549     {
550         if(raw >= time_t.max)
551         {
552             return PGTimeStamp(SysTime.max);
553         }
554         if(raw <= time_t.min)
555         {
556             return PGTimeStamp(SysTime.min);
557         }
558     }
559     
560     pg_tm tm;
561     fsec_t ts;
562     
563     if(timestamp2tm(raw, tm, ts) < 0)
564         throw new Exception("Timestamp is out of range!");
565 
566     return PGTimeStamp(tm, ts);
567 }
568 
569 /**
570 *   Wrapper around std.datetime.SysTime to handle [de]serializing of libpq
571 *   time stamps with time zone.
572 *
573 *   Timezone is acquired from PQparameterStatus call for TimeZone parameter.
574 *   Database server doesn't send any info about time zone to client, the
575 *   time zone is important only while showing time to an user.
576 *
577 *   Note: libpq has two compile configuration with HAS_INT64_TIMESTAMP and
578 *   without (double time stamp format). The pgator should be compiled with
579 *   conform flag to operate properly. 
580 */
581 struct PGTimeStampWithZone
582 {
583     private SysTime time;
584     alias time this;
585     
586     this(SysTime time)
587     {
588         this.time = time;
589     }
590     
591     this(pg_tm tm, fsec_t ts, immutable TimeZone zone)
592     {    
593         time = SysTime(Date(tm.tm_year, tm.tm_mon, tm.tm_mday), UTC());
594         time.timezone = zone;
595         time += tm.tm_hour.dur!"hours";
596         time += tm.tm_min.dur!"minutes";
597         time += tm.tm_sec.dur!"seconds";
598         version(Have_Int64_TimeStamp)
599         {
600             time += ts.dur!"usecs";
601         } else
602         {
603             time += (cast(long)(ts*10e6)).dur!"usecs";
604         }
605     }
606     
607     static PGTimeStampWithZone fromBson(Bson bson)
608     {
609         auto val = SysTime.fromISOExtString(bson.get!string);
610         return PGTimeStampWithZone(val);
611     }
612     
613     Bson toBson() const
614     {
615         return Bson(time.toISOExtString);
616     }
617 }
618 
619 PGTimeStampWithZone convert(PQType type)(ubyte[] val, shared IConnection conn)
620     if(type == PQType.TimeStampWithZone)
621 {
622     auto raw = val.read!long;
623     
624     version(Have_Int64_TimeStamp)
625     {
626         if(raw >= time_t.max)
627         {
628             return PGTimeStampWithZone(SysTime.max);
629         }
630         if(raw <= time_t.min)
631         {
632             return PGTimeStampWithZone(SysTime.min);
633         }
634     }
635     
636     pg_tm tm;
637     fsec_t ts;
638     
639     if(timestamp2tm(raw, tm, ts) < 0)
640         throw new Exception("Timestamp is out of range!");
641 
642     return PGTimeStampWithZone(tm, ts, conn.timeZone);
643 }
644 
645 
646 version(IntegrationTest2)
647 {
648     import pgator.db.pq.types.test;
649     import pgator.db.pool;
650     import std.random;
651     import std.algorithm;
652     import std.encoding;
653     import dlogg.log;
654     import dlogg.buffered;
655     
656     void test(PQType type)(shared ILogger strictLogger, shared IConnectionPool pool)
657         if(type == PQType.Date)
658     {
659         strictLogger.logInfo("Testing Date...");
660         auto dformat = pool.dateFormat;
661         auto logger = new shared BufferedLogger(strictLogger);
662         scope(failure) logger.minOutputLevel = LoggingLevel.Notice;
663         scope(exit) logger.finalize;
664         
665         assert(queryValue(logger, pool, "'1999-01-08'::date").deserializeBson!Date.toISOExtString == "1999-01-08");
666         assert(queryValue(logger, pool, "'January 8, 1999'::date").deserializeBson!Date.toISOExtString == "1999-01-08");
667         assert(queryValue(logger, pool, "'1999-Jan-08'::date").deserializeBson!Date.toISOExtString == "1999-01-08");
668         assert(queryValue(logger, pool, "'Jan-08-1999'::date").deserializeBson!Date.toISOExtString == "1999-01-08");
669         assert(queryValue(logger, pool, "'08-Jan-1999'::date").deserializeBson!Date.toISOExtString == "1999-01-08");
670         assert(queryValue(logger, pool, "'19990108'::date").deserializeBson!Date.toISOExtString == "1999-01-08");
671         assert(queryValue(logger, pool, "'990108'::date").deserializeBson!Date.toISOExtString == "1999-01-08");
672         assert(queryValue(logger, pool, "'1999.008'::date").deserializeBson!Date.toISOExtString == "1999-01-08");
673         assert(queryValue(logger, pool, "'J2451187'::date").deserializeBson!Date.toISOExtString == "1999-01-08");
674         assert(queryValue(logger, pool, "'January 8, 99 BC'::date").deserializeBson!Date.toISOExtString == "-0098-01-08");
675            
676         if(dformat.orderFormat == DateFormat.OrderFormat.MDY)
677         {
678             assert(queryValue(logger, pool, "'1/8/1999'::date").deserializeBson!Date.toISOExtString == "1999-01-08");
679             assert(queryValue(logger, pool, "'1/18/1999'::date").deserializeBson!Date.toISOExtString == "1999-01-18");
680             assert(queryValue(logger, pool, "'01/02/03'::date").deserializeBson!Date.toISOExtString == "2003-01-02");
681             assert(queryValue(logger, pool, "'08-Jan-99'::date").deserializeBson!Date.toISOExtString == "1999-01-08");
682             assert(queryValue(logger, pool, "'Jan-08-99'::date").deserializeBson!Date.toISOExtString == "1999-01-08");
683         } 
684         else if(dformat.orderFormat == DateFormat.OrderFormat.DMY)
685         {
686             assert(queryValue(logger, pool, "'1/8/1999'::date").deserializeBson!Date.toISOExtString == "1999-08-01");
687             assert(queryValue(logger, pool, "'01/02/03'::date").deserializeBson!Date.toISOExtString == "2003-02-01");
688             assert(queryValue(logger, pool, "'08-Jan-99'::date").deserializeBson!Date.toISOExtString == "1999-01-08");
689             assert(queryValue(logger, pool, "'Jan-08-99'::date").deserializeBson!Date.toISOExtString == "1999-01-08");
690         }
691         else if(dformat.orderFormat == DateFormat.OrderFormat.YMD)
692         {
693             assert(queryValue(logger, pool, "'01/02/03'::date").deserializeBson!Date.toISOExtString == "2001-02-03");
694             assert(queryValue(logger, pool, "'99-Jan-08'::date").deserializeBson!Date.toISOExtString == "1999-01-08");
695         }
696 
697     }
698     
699     void test(PQType type)(shared ILogger strictLogger, shared IConnectionPool pool)
700         if(type == PQType.AbsTime)
701     {
702         strictLogger.logInfo("Testing AbsTime...");
703         
704         auto logger = new shared BufferedLogger(strictLogger);
705         scope(failure) logger.minOutputLevel = LoggingLevel.Notice;
706         scope(exit) logger.finalize;
707         
708         auto res = queryValue(logger, pool, "'Dec 20 20:45:53 1986 GMT'::abstime").deserializeBson!PGAbsTime;
709         assert(res.time == SysTime.fromSimpleString("1986-Dec-20 20:45:53Z"));
710         
711         res = queryValue(logger, pool, "'Mar 8 03:14:04 2014 GMT'::abstime").deserializeBson!PGAbsTime;
712         assert(res.time == SysTime.fromSimpleString("2014-Mar-08 03:14:04Z"));
713         
714         res = queryValue(logger, pool, "'Dec 20 20:45:53 1986 +3'::abstime").deserializeBson!PGAbsTime;
715         assert(res.time == SysTime.fromSimpleString("1986-Dec-20 20:45:53+03"));
716         
717         res = queryValue(logger, pool, "'Mar 8 03:14:04 2014 +3'::abstime").deserializeBson!PGAbsTime;
718         assert(res.time == SysTime.fromSimpleString("2014-Mar-08 03:14:04+03"));
719         
720     }
721      
722     void test(PQType type)(shared ILogger strictLogger, shared IConnectionPool pool)
723         if(type == PQType.RelTime)
724     {
725         strictLogger.logInfo("Testing RelTime...");
726         
727         auto logger = new shared BufferedLogger(strictLogger);
728         scope(failure) logger.minOutputLevel = LoggingLevel.Notice;
729         scope(exit) logger.finalize;
730         
731         auto res = queryValue(logger, pool, "'2 week 3 day 4 hour 5 minute 6 second'::reltime").deserializeBson!PGRelTime;
732         assert(res == 6.dur!"seconds" + 5.dur!"minutes" + 4.dur!"hours" + 3.dur!"days" + 2.dur!"weeks");
733         
734         res = queryValue(logger, pool, "'2 week 3 day 4 hour 5 minute 6 second ago'::reltime").deserializeBson!PGRelTime;
735         assert(res == (-6).dur!"seconds" + (-5).dur!"minutes" + (-4).dur!"hours" + (-3).dur!"days" + (-2).dur!"weeks");
736     }
737     
738     void test(PQType type)(shared ILogger strictLogger, shared IConnectionPool pool)
739         if(type == PQType.Time)
740     {
741         strictLogger.logInfo("Testing Time...");
742         scope(failure) 
743         {
744             version(Have_Int64_TimeStamp) string s = "with Have_Int64_TimeStamp";
745             else string s = "without Have_Int64_TimeStamp";
746             
747             strictLogger.logInfo("============================================");
748             strictLogger.logInfo(text("Server timestamp format is: ", pool.timestampFormat));
749             strictLogger.logInfo(text("Application was compiled ", s, ". Try to switch the compilation flag."));
750             strictLogger.logInfo("============================================");
751         }
752 
753         auto logger = new shared BufferedLogger(strictLogger);
754         scope(failure) logger.minOutputLevel = LoggingLevel.Notice;
755         scope(exit) logger.finalize;
756         
757         assert((cast(TimeOfDay)queryValue(logger, pool, "'04:05:06.789'::time").deserializeBson!PGTime).toISOExtString == "04:05:06");
758         assert((cast(TimeOfDay)queryValue(logger, pool, "'04:05:06'::time").deserializeBson!PGTime).toISOExtString == "04:05:06");
759         assert((cast(TimeOfDay)queryValue(logger, pool, "'04:05'::time").deserializeBson!PGTime).toISOExtString == "04:05:00");
760         assert((cast(TimeOfDay)queryValue(logger, pool, "'040506'::time").deserializeBson!PGTime).toISOExtString == "04:05:06");
761         assert((cast(TimeOfDay)queryValue(logger, pool, "'04:05 AM'::time").deserializeBson!PGTime).toISOExtString == "04:05:00");
762         assert((cast(TimeOfDay)queryValue(logger, pool, "'04:05 PM'::time").deserializeBson!PGTime).toISOExtString == "16:05:00");
763     }
764     
765     void test(PQType type)(shared ILogger strictLogger, shared IConnectionPool pool)
766         if(type == PQType.TimeWithZone)
767     {
768         strictLogger.logInfo("Testing TimeWithZone...");
769         scope(failure) 
770         {
771             version(Have_Int64_TimeStamp) string s = "with Have_Int64_TimeStamp";
772             else string s = "without Have_Int64_TimeStamp";
773             
774             strictLogger.logInfo("============================================");
775             strictLogger.logInfo(text("Server timestamp format is: ", pool.timestampFormat));
776             strictLogger.logInfo(text("Application was compiled ", s, ". Try to switch the compilation flag."));
777             strictLogger.logInfo("============================================");
778         }
779         
780         auto logger = new shared BufferedLogger(strictLogger);
781         scope(failure) logger.minOutputLevel = LoggingLevel.Notice;
782         scope(exit) logger.finalize;
783         
784         static if (__VERSION__ < 2066) 
785         {
786             auto res = queryValue(logger, pool, "'04:05:06.789-8'::time with time zone").deserializeBson!PGTimeWithZone;
787             assert((cast(TimeOfDay)res).toISOExtString == "04:05:06" && (cast(immutable SimpleTimeZone)res).utcOffset.dur!"minutes".total!"hours" == -8);
788             res = queryValue(logger, pool, "'04:05:06-08:00'::time with time zone").deserializeBson!PGTimeWithZone;
789             assert((cast(TimeOfDay)res).toISOExtString == "04:05:06" && (cast(immutable SimpleTimeZone)res).utcOffset.dur!"minutes".total!"hours" == -8);
790             res = queryValue(logger, pool, "'04:05-08:00'::time with time zone").deserializeBson!PGTimeWithZone;
791             assert((cast(TimeOfDay)res).toISOExtString == "04:05:00" && (cast(immutable SimpleTimeZone)res).utcOffset.dur!"minutes".total!"hours" == -8);
792             res = queryValue(logger, pool, "'040506-08'::time with time zone").deserializeBson!PGTimeWithZone;
793             assert((cast(TimeOfDay)res).toISOExtString == "04:05:06" && (cast(immutable SimpleTimeZone)res).utcOffset.dur!"minutes".total!"hours" == -8);
794             res = queryValue(logger, pool, "'04:05:06 PST'::time with time zone").deserializeBson!PGTimeWithZone;
795             assert((cast(TimeOfDay)res).toISOExtString == "04:05:06" && (cast(immutable SimpleTimeZone)res).utcOffset.dur!"minutes".total!"hours" == -8);
796             res = queryValue(logger, pool, "'2003-04-12 04:05:06 America/New_York'::time with time zone").deserializeBson!PGTimeWithZone;
797             assert((cast(TimeOfDay)res).toISOExtString == "04:05:06" && (cast(immutable SimpleTimeZone)res).utcOffset.dur!"minutes".total!"hours" == -4);
798         } else
799         {
800             auto res = queryValue(logger, pool, "'04:05:06.789-8'::time with time zone").deserializeBson!PGTimeWithZone;
801             assert((cast(TimeOfDay)res).toISOExtString == "04:05:06" && (cast(immutable SimpleTimeZone)res).utcOffset.total!"hours" == -8);
802             res = queryValue(logger, pool, "'04:05:06-08:00'::time with time zone").deserializeBson!PGTimeWithZone;
803             assert((cast(TimeOfDay)res).toISOExtString == "04:05:06" && (cast(immutable SimpleTimeZone)res).utcOffset.total!"hours" == -8);
804             res = queryValue(logger, pool, "'04:05-08:00'::time with time zone").deserializeBson!PGTimeWithZone;
805             assert((cast(TimeOfDay)res).toISOExtString == "04:05:00" && (cast(immutable SimpleTimeZone)res).utcOffset.total!"hours" == -8);
806             res = queryValue(logger, pool, "'040506-08'::time with time zone").deserializeBson!PGTimeWithZone;
807             assert((cast(TimeOfDay)res).toISOExtString == "04:05:06" && (cast(immutable SimpleTimeZone)res).utcOffset.total!"hours" == -8);
808             res = queryValue(logger, pool, "'04:05:06 PST'::time with time zone").deserializeBson!PGTimeWithZone;
809             assert((cast(TimeOfDay)res).toISOExtString == "04:05:06" && (cast(immutable SimpleTimeZone)res).utcOffset.total!"hours" == -8);
810             res = queryValue(logger, pool, "'2003-04-12 04:05:06 America/New_York'::time with time zone").deserializeBson!PGTimeWithZone;
811             assert((cast(TimeOfDay)res).toISOExtString == "04:05:06" && (cast(immutable SimpleTimeZone)res).utcOffset.total!"hours" == -4);
812         }
813     }
814     
815     void test(PQType type)(shared ILogger strictLogger, shared IConnectionPool pool)
816         if(type == PQType.Interval)
817     {
818         strictLogger.logInfo("Testing tinterval...");
819                 
820         auto logger = new shared BufferedLogger(strictLogger);
821         scope(failure) logger.minOutputLevel = LoggingLevel.Notice;
822         scope(exit) logger.finalize;
823         
824         auto res = queryValue(logger, pool, "'[\"Dec 20 20:45:53 1986 GMT\" \"Mar 8 03:14:04 2014 GMT\"]'::tinterval").deserializeBson!PGInterval;
825         assert(res.begin == SysTime.fromSimpleString("1986-Dec-20 20:45:53Z"));
826         assert(res.end   == SysTime.fromSimpleString("2014-Mar-08 03:14:04Z"));
827         
828         res = queryValue(logger, pool, "'[\"Dec 20 20:45:53 1986 +3\" \"Mar 8 03:14:04 2014 +3\"]'::tinterval").deserializeBson!PGInterval;
829         assert(res.begin == SysTime.fromSimpleString("1986-Dec-20 20:45:53+03"));
830         assert(res.end   == SysTime.fromSimpleString("2014-Mar-08 03:14:04+03"));
831 
832     }
833     
834     void test(PQType type)(shared ILogger strictLogger, shared IConnectionPool pool)
835         if(type == PQType.TimeInterval)
836     {
837         strictLogger.logInfo("Testing TimeInterval...");
838         scope(failure) 
839         {
840             version(Have_Int64_TimeStamp) string s = "with Have_Int64_TimeStamp";
841             else string s = "without Have_Int64_TimeStamp";
842             
843             strictLogger.logInfo("============================================");
844             strictLogger.logInfo(text("Server timestamp format is: ", pool.timestampFormat));
845             strictLogger.logInfo(text("Application was compiled ", s, ". Try to switch the compilation flag."));
846             strictLogger.logInfo("============================================");
847         }
848         
849         auto logger = new shared BufferedLogger(strictLogger);
850         scope(failure) logger.minOutputLevel = LoggingLevel.Notice;
851         scope(exit) logger.finalize;
852         
853         auto res = queryValue(logger, pool, "'1-2'::interval").deserializeBson!TimeInterval;
854         assert(res.time == 0 && res.day == 0 && res.month == 14);
855         
856         res = queryValue(logger, pool, "'3 4:05:06'::interval").deserializeBson!TimeInterval;
857         assert(res.time.dur!"usecs" == 4.dur!"hours" + 5.dur!"minutes" + 6.dur!"seconds" && res.day == 3 && res.month == 0);
858         
859         res = queryValue(logger, pool, "'1 year 2 months 3 days 4 hours 5 minutes 6 seconds'::interval").deserializeBson!TimeInterval;
860         assert(res.time.dur!"usecs" == 4.dur!"hours" + 5.dur!"minutes" + 6.dur!"seconds" && res.day == 3 && res.month == 14);
861         
862         res = queryValue(logger, pool, "'P1Y2M3DT4H5M6S'::interval").deserializeBson!TimeInterval;
863         assert(res.time.dur!"usecs" == 4.dur!"hours" + 5.dur!"minutes" + 6.dur!"seconds" && res.day == 3 && res.month == 14);
864         
865         res = queryValue(logger, pool, "'P0001-02-03T04:05:06'::interval").deserializeBson!TimeInterval;
866         assert(res.time.dur!"usecs" == 4.dur!"hours" + 5.dur!"minutes" + 6.dur!"seconds" && res.day == 3 && res.month == 14);
867     }
868     
869     void test(PQType type)(shared ILogger strictLogger, shared IConnectionPool pool)
870         if(type == PQType.TimeStamp)
871     {
872         strictLogger.logInfo("Testing TimeStamp...");
873         scope(failure) 
874         {
875             version(Have_Int64_TimeStamp) string s = "with Have_Int64_TimeStamp";
876             else string s = "without Have_Int64_TimeStamp";
877             
878             strictLogger.logInfo("============================================");
879             strictLogger.logInfo(text("Server timestamp format is: ", pool.timestampFormat));
880             strictLogger.logInfo(text("Application was compiled ", s, ". Try to switch the compilation flag."));
881             strictLogger.logInfo("============================================");
882         }
883         
884         auto logger = new shared BufferedLogger(strictLogger);
885         scope(failure) logger.minOutputLevel = LoggingLevel.Notice;
886         scope(exit) logger.finalize;
887         
888         auto res = queryValue(logger, pool, "TIMESTAMP '2004-10-19 10:23:54'").deserializeBson!PGTimeStamp;
889         assert(res.time == SysTime.fromSimpleString("2004-Oct-19 10:23:54Z"));
890         
891         res = queryValue(logger, pool, "TIMESTAMP '1999-01-08 04:05:06'").deserializeBson!PGTimeStamp;
892         assert(res.time == SysTime.fromSimpleString("1999-Jan-08 04:05:06Z"));
893         
894         res = queryValue(logger, pool, "TIMESTAMP 'January 8 04:05:06 1999 PST'").deserializeBson!PGTimeStamp;
895         assert(res.time == SysTime.fromSimpleString("1999-Jan-08 04:05:06Z"));
896         
897         res = queryValue(logger, pool, "TIMESTAMP 'epoch'").deserializeBson!PGTimeStamp;
898         assert(res.time == SysTime.fromSimpleString("1970-Jan-01 00:00:00Z"));
899         
900         res = queryValue(logger, pool, "TIMESTAMP 'infinity'").deserializeBson!PGTimeStamp;
901         assert(res.time == SysTime.max);
902         
903         res = queryValue(logger, pool, "TIMESTAMP '-infinity'").deserializeBson!PGTimeStamp;
904         assert(res.time == SysTime.min);
905     }
906      
907     void test(PQType type)(shared ILogger strictLogger, shared IConnectionPool pool)
908         if(type == PQType.TimeStampWithZone)
909     {
910         strictLogger.logInfo("Testing TimeStampWithZone...");
911         scope(failure) 
912         {
913             version(Have_Int64_TimeStamp) string s = "with Have_Int64_TimeStamp";
914             else string s = "without Have_Int64_TimeStamp";
915             
916             strictLogger.logInfo("============================================");
917             strictLogger.logInfo(text("Server timestamp format is: ", pool.timestampFormat));
918             strictLogger.logInfo(text("Application was compiled ", s, ". Try to switch the compilation flag."));
919             strictLogger.logInfo("============================================");
920         }
921         
922         auto logger = new shared BufferedLogger(strictLogger);
923         scope(failure) logger.minOutputLevel = LoggingLevel.Notice;
924         scope(exit) logger.finalize;
925         
926         auto res = queryValue(logger, pool, "TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'").deserializeBson!PGTimeStampWithZone;
927         assert(res.time == SysTime.fromSimpleString("2004-Oct-19 10:23:54+02"));
928         
929         res = queryValue(logger, pool, "TIMESTAMP WITH TIME ZONE '1999-01-08 04:05:06-04'").deserializeBson!PGTimeStampWithZone;
930         assert(res.time == SysTime.fromSimpleString("1999-Jan-08 04:05:06-04"));
931         
932         res = queryValue(logger, pool, "TIMESTAMP WITH TIME ZONE 'January 8 04:05:06 1999 -8:00'").deserializeBson!PGTimeStampWithZone;
933         assert(res.time == SysTime.fromSimpleString("1999-Jan-08 04:05:06-08"));
934         
935         res = queryValue(logger, pool, "TIMESTAMP WITH TIME ZONE 'epoch'").deserializeBson!PGTimeStampWithZone;
936         assert(res.time == SysTime.fromSimpleString("1970-Jan-01 00:00:00Z"));
937         
938         res = queryValue(logger, pool, "TIMESTAMP WITH TIME ZONE 'infinity'").deserializeBson!PGTimeStampWithZone;
939         assert(res.time == SysTime.max);
940         
941         res = queryValue(logger, pool, "TIMESTAMP WITH TIME ZONE '-infinity'").deserializeBson!PGTimeStampWithZone;
942         assert(res.time == SysTime.min);
943         
944         res = queryValue(logger, pool, "TIMESTAMP WITH TIME ZONE '2014-11-20 15:47:25+07'").deserializeBson!PGTimeStampWithZone;
945         assert(res.time == SysTime.fromSimpleString("2014-Nov-20 15:47:25+07"));
946     }
947 }