1 module logdefer.time.duration;
2 
3 
4 import core.checkedint : muls;
5 import std.conv : to;
6 import std.traits : isIntegral;
7 
8 
9 alias Nanos = Duration!(TimeUnit.NANOS);
10 alias Micros = Duration!(TimeUnit.MICROS);
11 alias Millis = Duration!(TimeUnit.MILLIS);
12 alias Seconds = Duration!(TimeUnit.SECONDS);
13 
14 enum TimeUnit
15 {
16     NANOS=1,
17     MICROS=1_000,
18     MILLIS=1_000_000,
19     SECONDS=1_000_000_000
20 }
21 
22 @safe
23 struct Duration(TimeUnit timeUnit)
24 {
25     public:
26         long value;
27         static immutable TimeUnit units = timeUnit;
28         static immutable string displayUnits = getDisplayUnits();
29 
30         @nogc
31         nothrow this(long value)
32         {
33             this.value = value;
34         }
35 
36         @nogc
37         nothrow this(OtherDuration)(const OtherDuration otherDuration)
38         {
39             this.value = toDuration!(Duration!timeUnit)(otherDuration).value;
40         }
41 
42         @nogc
43         nothrow ToDuration toDuration(ToDuration)() const
44         {
45             return toDuration!(ToDuration)(this);
46         }
47 
48         @nogc
49         nothrow double toUnits(ToUnits)() const
50         {
51             return toUnits!(ToUnits)(this);
52         }
53 
54         nothrow string toString(ToUnits = TimeUnit)() const
55         {
56             //XXX
57             return to!string(value) ~ " " ~ displayUnits;
58         }
59 
60         @nogc
61         nothrow bool opEquals(OtherDuration)(const OtherDuration otherDuration)
62         {
63             return opEquals(otherDuration);
64         }
65 
66         @nogc
67         nothrow bool opEquals(OtherDuration)(const ref OtherDuration otherDuration)
68         {
69             return toDuration!Nanos(this).value == toDuration!Nanos(otherDuration).value;
70         }
71 
72         @nogc
73         nothrow void opAssign(OtherDuration)(const OtherDuration otherDuration)
74         {
75             this.value = toDuration!(Duration!timeUnit)(otherDuration).value;
76         }
77 
78         @nogc
79         pure nothrow int opCmp(OtherDuration)(const OtherDuration other) const
80         {
81             return opCmp(other);
82         }
83 
84         @nogc
85         pure nothrow int opCmp(OtherDuration)(const ref OtherDuration other) const
86         {
87             static if (isIntegral!OtherDuration)
88             {
89                 return this.value > other ? 1
90                     : this.value < other ? -1
91                     : 0;
92             }
93             else
94             {
95                 long thisNanos = toDuration!Nanos(this).value;
96                 long otherNanos = toDuration!Nanos(other).value;
97 
98                 return thisNanos > otherNanos ? 1
99                     : thisNanos < otherNanos ? -1
100                     : 0;
101             }
102         }
103 
104         Duration!timeUnit opBinary(string op)(long operand) const
105         if (op == "*")
106         {
107             bool overflow;
108             auto value = muls(this.value, operand, overflow);
109 
110             if (overflow)
111             {
112                 throw new Exception("Overflow");
113             }
114 
115             return Duration!timeUnit(value);
116         }
117 
118         Duration!timeUnit opBinary(string op)(long operand) const
119         if (op == "/")
120         {
121             if (operand == 0)
122             {
123                 throw new Exception("Divide by 0");
124             }
125 
126             return Duration!timeUnit(this.value / operand);
127         }
128 
129         @nogc
130         pure static nothrow ToDuration toDuration(ToDuration, FromDuration)(const FromDuration fromDuration)
131         {
132             static if (ToDuration.units > FromDuration.units)
133             {
134                 return ToDuration(fromDuration.value / (ToDuration.units / FromDuration.units));
135             }
136             else static if (ToDuration.units < FromDuration.units)
137             {
138                 return ToDuration(fromDuration.value * (FromDuration.units / ToDuration.units));
139             }
140             else
141             {
142                 return ToDuration(fromDuration.value);
143             }
144         }
145 
146         @nogc
147         pure static nothrow double toUnits(ToUnits, FromDuration)(const FromDuration fromDuration)
148         {
149             static if (ToUnits.units > FromDuration.units)
150             {
151                 return cast(double)(fromDuration.value) / (ToUnits.units / FromDuration.units);
152             }
153             else static if (ToUnits.units < FromDuration.units)
154             {
155                 return cast(double)(fromDuration.value) * (FromDuration.units / ToUnits.units);
156             }
157             else
158             {
159                 return cast(double)fromDuration.value;
160             }
161         }
162 
163     private:
164         static string getDisplayUnits()
165         {
166             final switch(timeUnit)
167             {
168                 case TimeUnit.SECONDS: return "seconds";
169                 case TimeUnit.MILLIS: return "milliseconds";
170                 case TimeUnit.MICROS: return "microseconds";
171                 case TimeUnit.NANOS: return "nanoseconds";
172             }
173         }
174 }
175 
176 
177 version(unittest)
178 {
179     import std.stdio : writeln;
180 }
181 
182 unittest
183 {
184     writeln("[UnitTest toDuration Seconds]");
185 
186     Nanos nanos = Seconds(1);
187     Micros micros = Seconds(1);
188     Millis millis = Seconds(1);
189     Seconds seconds = Seconds(1);
190 
191     assert(nanos == Nanos(1_000_000_000));
192     assert(micros == Micros(1_000_000));
193     assert(millis == Millis(1_000));
194     assert(seconds == Seconds(1));
195 }
196 
197 unittest
198 {
199     writeln("[UnitTest toDuration Millis]");
200 
201     Nanos nanos = Millis(1);
202     Micros micros = Millis(1);
203     Millis millis = Millis(1);
204     Seconds seconds = Millis(1);
205 
206     assert(nanos == Nanos(1_000_000));
207     assert(micros == Micros(1_000));
208     assert(millis == Millis(1));
209     assert(seconds == Seconds(0));
210 }
211 
212 unittest
213 {
214     writeln("[UnitTest toDuration Micros]");
215 
216     Nanos nanos = Micros(1);
217     Micros micros = Micros(1);
218     Millis millis = Micros(1);
219     Seconds seconds = Micros(1);
220 
221     assert(nanos == Nanos(1_000));
222     assert(micros == Micros(1));
223     assert(millis == Millis(0));
224     assert(seconds == Seconds(0));
225 }
226 
227 unittest
228 {
229     writeln("[UnitTest toDuration Nanos]");
230 
231     Nanos nanos = Nanos(1);
232     Micros micros = Nanos(1);
233     Millis millis = Nanos(1);
234     Seconds seconds = Nanos(1);
235 
236     assert(nanos == Nanos(1));
237     assert(micros == Micros(0));
238     assert(millis == Millis(0));
239     assert(seconds == Seconds(0));
240 }
241 
242 unittest
243 {
244     writeln("[UnitTest toUnits Seconds]");
245 
246     auto nanos = Seconds(1).toUnits!Nanos;
247     auto micros = Seconds(1).toUnits!Micros;
248     auto millis = Seconds(1).toUnits!Millis;
249     auto seconds = Seconds(1).toUnits!Seconds;
250 
251     assert(nanos == 1_000_000_000f);
252     assert(micros == 1_000_000f);
253     assert(millis == 1_000f);
254     assert(seconds == 1f);
255 }
256 
257 unittest
258 {
259     writeln("[UnitTest toUnits Millis]");
260 
261     auto nanos = Millis(1).toUnits!Nanos;
262     auto micros = Millis(1).toUnits!Micros;
263     auto millis = Millis(1).toUnits!Millis;
264     auto seconds = Millis(1).toUnits!Seconds;
265 
266     assert(nanos == 1_000_000f);
267     assert(micros == 1_000f);
268     assert(millis == 1f);
269     assert(seconds == 0.001f);
270 }
271 
272 unittest
273 {
274     writeln("[UnitTest toUnits Micros]");
275 
276     auto nanos = Micros(1).toUnits!Nanos;
277     auto micros = Micros(1).toUnits!Micros;
278     auto millis = Micros(1).toUnits!Millis;
279     auto seconds = Micros(1).toUnits!Seconds;
280 
281     assert(nanos == 1_000f);
282     assert(micros == 1f);
283     assert(millis == 0.001f);
284     assert(seconds == 0.000001f);
285 }
286 
287 unittest
288 {
289     writeln("[UnitTest toUnits Nanos]");
290 
291     auto nanos = Nanos(1).toUnits!Nanos;
292     auto micros = Nanos(1).toUnits!Micros;
293     auto millis = Nanos(1).toUnits!Millis;
294     auto seconds = Nanos(1).toUnits!Seconds;
295 
296     assert(nanos == 1f);
297     assert(micros == 0.001f);
298     assert(millis == 0.000001f);
299     assert(seconds == 0.000000001f);
300 }
301 
302 unittest
303 {
304     writeln("[UnitTest opAssign]");
305 
306     Nanos nanos;
307     Micros micros;
308     Millis millis;
309     Seconds seconds;
310 
311     nanos = Micros(10);
312     micros = Nanos(1_000);
313     millis = Seconds(1);
314     seconds = Millis(5_999);
315 
316     assert(nanos == Nanos(10_000));
317     assert(micros == Micros(1));
318     assert(millis == Millis(1_000));
319     assert(seconds == Seconds(5));
320 }
321 
322 unittest
323 {
324     writeln("[UnitTest opCmp]");
325 
326     assert(Seconds(1) > Seconds(0));
327     assert(Seconds(1) == Seconds(1));
328     assert(Seconds(1) < Seconds(2));
329 
330     assert(Seconds(1) > Millis(999));
331     assert(Seconds(1) == Millis(1_000));
332     assert(Seconds(1) < Millis(1_001));
333 
334     assert(Seconds(1) > Micros(999_999));
335     assert(Seconds(1) == Micros(1_000_000));
336     assert(Seconds(1) < Micros(1_000_001));
337 
338     assert(Seconds(1) > Nanos(999_999_999));
339     assert(Seconds(1) == Nanos(1_000_000_000));
340     assert(Seconds(1) < Nanos(1_000_000_001));
341 
342     assert(Nanos(1) < Micros(1));
343     assert(Nanos(1_000) == Micros(1));
344     assert(Nanos(1_001) > Micros(1));
345 }
346 
347 unittest
348 {
349     writeln("[UnitTest Duration]");
350 
351     Seconds seconds = Seconds(100);
352     Millis millis = Millis(100);
353     Micros micros = Micros(100);
354     Nanos nanos = Nanos(100);
355 
356     assert(seconds.units == TimeUnit.SECONDS);
357     assert(seconds.toString() == "100 seconds");
358 
359     assert(millis.units == TimeUnit.MILLIS);
360     assert(millis.toString() == "100 milliseconds");
361 
362     assert(micros.units == TimeUnit.MICROS);
363     assert(micros.toString() == "100 microseconds");
364 
365     assert(nanos.units == TimeUnit.NANOS);
366     assert(nanos.toString() == "100 nanoseconds");
367 }