1 /** 2 Datetime extension. 3 */ 4 module karasux.datetime; 5 6 import core.time : Duration, hnsecs; 7 import std.datetime : Clock, UTC, SysTime, unixTimeToStdTime; 8 9 /** 10 Singleton UTC timezone. 11 */ 12 immutable timeZoneUTC = UTC(); 13 14 /** 15 UNIX epoch time. 16 */ 17 immutable UNIX_EPOCH = SysTime(unixTimeToStdTime(0), timeZoneUTC); 18 19 /** 20 Calculate duration since UNIX epoch time. 21 22 Params: 23 t = SysTime value. 24 Returns: 25 Duration since UNIX epoch time (1970/1/1 00:00) 26 */ 27 Duration sinceUnixEpoch()(scope auto ref const(SysTime) t) @nogc nothrow pure @safe 28 { 29 return t - UNIX_EPOCH; 30 } 31 32 /// 33 nothrow pure @safe unittest 34 { 35 import core.time : seconds; 36 37 assert(UNIX_EPOCH.sinceUnixEpoch == Duration.zero); 38 assert(SysTime(unixTimeToStdTime(1000), timeZoneUTC).sinceUnixEpoch == 1000.seconds); 39 } 40 41 /** 42 Returns; 43 Current timestamp since UNIX epoch. 44 */ 45 Duration currentUnixTime() @safe 46 { 47 return Clock.currTime(timeZoneUTC).sinceUnixEpoch; 48 } 49 50 /// 51 @safe unittest 52 { 53 assert(currentUnixTime.total!"seconds" == Clock.currTime(timeZoneUTC).toUnixTime); 54 } 55 56 /** 57 Timestamp type. 58 */ 59 struct Timestamp 60 { 61 /** 62 Returns: 63 now timestamp. 64 */ 65 static @property Timestamp now() @safe 66 { 67 return Timestamp(Clock.currTime(timeZoneUTC).stdTime); 68 } 69 70 /// 71 @safe unittest 72 { 73 immutable now = Clock.currTime(timeZoneUTC).stdTime; 74 assert(Timestamp.now.stdTime >= now); 75 } 76 77 /** 78 Returns: 79 timestamp ISO8601 string reperesentation. 80 */ 81 string toString() const nothrow @safe scope 82 { 83 return SysTime(stdTime, timeZoneUTC).toISOExtString(); 84 } 85 86 /** 87 Parse timestamp. 88 89 Params: 90 timestamp = timestamp string. 91 Returns: 92 parsed timestamp. 93 */ 94 static Timestamp fromString(string timestamp) @safe 95 { 96 return Timestamp(SysTime.fromISOExtString(timestamp).stdTime); 97 } 98 99 /** 100 calculate duration. 101 */ 102 Duration opBinary(string op)(scope auto ref const(Timestamp) rhs) 103 const @nogc nothrow @safe scope if (op == "-") 104 { 105 return hnsecs(mixin("stdTime " ~ op ~ " rhs.stdTime")); 106 } 107 108 /// 109 @nogc nothrow @safe unittest 110 { 111 immutable t1 = Timestamp(1000); 112 immutable t2 = Timestamp(500); 113 assert(t1 - t2 == hnsecs(500)); 114 assert(t2 - t1 == hnsecs(-500)); 115 } 116 117 /** 118 add or subtract duration. 119 */ 120 Timestamp opBinary(string op)(scope auto ref const(Duration) rhs) 121 const @nogc nothrow @safe scope if (op == "-" || op == "+") 122 { 123 return Timestamp(mixin("stdTime " ~ op ~ ` rhs.total!"hnsecs"`)); 124 } 125 126 /// 127 @nogc nothrow @safe unittest 128 { 129 import core.time : dur; 130 immutable t = Timestamp(1000); 131 assert(t + dur!"seconds"(1) == Timestamp(1000 + dur!"seconds"(1).total!"hnsecs")); 132 assert(t - dur!"hnsecs"(100) == Timestamp(900)); 133 } 134 135 /** 136 add or subtract duration. 137 */ 138 void opOpAssign(string op)(scope auto ref const(Duration) rhs) 139 @nogc nothrow @safe scope if (op == "-" || op == "+") 140 { 141 mixin("stdTime " ~ op ~ `= rhs.total!"hnsecs";`); 142 } 143 144 /// 145 @nogc nothrow @safe unittest 146 { 147 import core.time : dur; 148 auto t = Timestamp(1000); 149 t += dur!"seconds"(1); 150 assert(t == Timestamp(1000 + dur!"seconds"(1).total!"hnsecs")); 151 152 t = Timestamp(1000); 153 t -= dur!"hnsecs"(100); 154 assert(t == Timestamp(900)); 155 } 156 157 /** 158 Round down time by unit duration. 159 160 Params: 161 unit = unit duration. 162 Returns: 163 Rounded down timestamp. 164 */ 165 Timestamp roundDown(Duration unit) const @nogc nothrow pure @safe scope 166 { 167 immutable total = unit.total!"hnsecs"; 168 return (total == 0) ? this : Timestamp(stdTime - stdTime % total); 169 } 170 171 /// 172 @nogc nothrow pure @safe unittest 173 { 174 import core.time : hnsecs, dur; 175 immutable t = Timestamp(1234567890); 176 assert(t.roundDown(dur!"seconds"(1)) == Timestamp(1230000000)); 177 assert(t.roundDown(dur!"msecs"(1)) == Timestamp(1234560000)); 178 } 179 180 /** 181 Compare two timestmaps. 182 183 Params: 184 rhs = other hand side. 185 Returns: 186 compare result. 187 */ 188 int opCmp()(auto scope ref const(Timestamp) rhs) const @nogc nothrow pure @safe scope 189 { 190 // prevent overflow. 191 immutable diff = cast(long)(stdTime - rhs.stdTime); 192 return (diff < 0) ? -1 : ((diff > 0) ? 1 : 0); 193 } 194 195 /// 196 @nogc nothrow pure @safe unittest 197 { 198 immutable t1 = Timestamp(12345678912340); 199 immutable t2 = Timestamp(12345678912341); 200 assert(t1 < t2); 201 assert(t1 <= t2); 202 assert(t2 > t1); 203 assert(t2 >= t1); 204 205 assert(!(t1 < t1)); 206 assert(t1 <= t1); 207 assert(!(t1 > t1)); 208 assert(t1 >= t1); 209 } 210 211 /** 212 Compare two timestmaps. 213 214 Params: 215 rhs = other hand side. 216 Returns: 217 compare result. 218 */ 219 bool opEquals()(auto scope ref const(Timestamp) rhs) const @nogc nothrow pure @safe scope 220 { 221 return stdTime == rhs.stdTime; 222 } 223 224 /// 225 @nogc nothrow pure @safe unittest 226 { 227 immutable t1 = Timestamp(12345678912340); 228 immutable t2 = Timestamp(12345678912341); 229 assert(t1 == t1); 230 assert(t2 == t2); 231 assert(t1 != t2); 232 assert(t2 != t1); 233 } 234 235 ulong stdTime; 236 } 237 238 /// 239 @safe unittest 240 { 241 immutable t = Timestamp.fromString("2021-01-02T03:04:05.678Z"); 242 assert(t.toString() == "2021-01-02T03:04:05.678+00:00"); 243 } 244