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