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