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