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