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