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