1 /**
2  * Some nice "polyfills" for standard std.datetime types.
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <ae@cy.md>
12  */
13 
14 module ae.utils.time.types;
15 
16 // ***************************************************************************
17 
18 import std.datetime;
19 
20 /// `typeof(SysTime.stdTime)`, the numeric type used to store absolute time in D.
21 alias StdTime = typeof(SysTime.init.stdTime); // long
22 
23 /// Convert from `StdTime` to `Duration`.
24 alias stdDur = hnsecs;
25 
26 /// Like `SysTime.stdTime`.
27 @property StdTime stdTime(Duration d) pure @safe nothrow @nogc
28 {
29 	return d.total!"hnsecs"();
30 }
31 
32 /// `true` when the duration `d` is zero.
33 @property bool empty(Duration d) pure @safe nothrow @nogc
34 {
35 	return !d.stdTime;
36 }
37 
38 /// Workaround SysTime.fracSecs only being available in 2.067,
39 /// and SysTime.fracSec becoming deprecated in the same version.
40 static if (!is(typeof(SysTime.init.fracSecs)))
41 @property Duration fracSecs(SysTime s)
42 {
43 	enum hnsecsPerSecond = convert!("seconds", "hnsecs")(1);
44 	return hnsecs(s.stdTime % hnsecsPerSecond);
45 }
46 
47 /// As above, for Duration.split and Duration.get
48 static if (!is(typeof(Duration.init.split!())))
49 @property auto split(units...)(Duration d)
50 {
51 	static struct Result
52 	{
53 		mixin("long " ~ [units].join(", ") ~ ";");
54 	}
55 
56 	Result result;
57 	foreach (unit; units)
58 	{
59 		static if (is(typeof(d.get!unit))) // unit == "msecs" || unit == "usecs" || unit == "hnsecs" || unit == "nsecs")
60 			long value = d.get!unit();
61 		else
62 			long value = mixin("d.fracSec." ~ unit);
63 		mixin("result." ~ unit ~ " = value;");
64 	}
65 	return result;
66 }
67 
68 // ***************************************************************************
69 
70 /// An absolute, timezone-less point in time.
71 /// Like `SysTime`, but does not carry timezone information.
72 /// Zero-overhead wrapper around `StdTime` which attempts to
73 /// be compatible with `std.datetime`.
74 struct AbsTime
75 {
76 	StdTime stdTime;
77 
78 	this(StdTime stdTime) pure @safe nothrow @nogc { this.stdTime = stdTime; }
79 	this(SysTime sysTime) pure @safe nothrow @nogc { this.stdTime = sysTime.stdTime; }
80 
81 	this(DateTime dateTime, Duration fracSecs = Duration.zero) pure @safe nothrow @nogc
82 	{
83 		assert(fracSecs >= Duration.zero && fracSecs < seconds(1), "Invalid fracSecs");
84 
85 		immutable dateDiff = dateTime.date - Date.init;
86 		immutable todDiff = dateTime.timeOfDay - TimeOfDay.init;
87 
88 		auto t = dateDiff + todDiff + fracSecs;
89 		this(t.stdTime);
90 	}
91 
92 	private static immutable epochDate = Date(1, 1, 1);
93 	this(Date date) pure @safe nothrow @nogc { this((date - epochDate).stdTime); }
94 
95 	@property SysTime sysTime(immutable TimeZone tz = null) const pure @safe nothrow /*@nogc*/ { return SysTime(stdTime, tz); }
96 
97 	/// The Xth day of the Gregorian Calendar (in the UTC time zone) that this AbsTime is on.
98 	@property int dayOfGregorianCal() const pure @safe nothrow @nogc
99 	{
100 		// As in SysTime:
101 		auto days = cast(int) stdTime.stdDur.total!"days";
102 		if (stdTime > 0 || stdTime == days.days.stdTime)
103 			days++;
104 		return days;
105 	}
106 
107 	@safe unittest // As in SysTime
108 	{
109 		import std.datetime.date : DateTime;
110 
111 		assert(AbsTime(DateTime(1, 1, 1, 0, 0, 0)).dayOfGregorianCal == 1);
112 		assert(AbsTime(DateTime(1, 12, 31, 23, 59, 59)).dayOfGregorianCal == 365);
113 		assert(AbsTime(DateTime(2, 1, 1, 2, 2, 2)).dayOfGregorianCal == 366);
114 
115 		assert(AbsTime(DateTime(0, 12, 31, 7, 7, 7)).dayOfGregorianCal == 0);
116 		assert(AbsTime(DateTime(0, 1, 1, 19, 30, 0)).dayOfGregorianCal == -365);
117 		assert(AbsTime(DateTime(-1, 12, 31, 4, 7, 0)).dayOfGregorianCal == -366);
118 
119 		assert(AbsTime(DateTime(2000, 1, 1, 9, 30, 20)).dayOfGregorianCal == 730_120);
120 		assert(AbsTime(DateTime(2010, 12, 31, 15, 45, 50)).dayOfGregorianCal == 734_137);
121 	}
122 
123 	Date opCast(T : Date)() const pure @safe nothrow @nogc { return Date(dayOfGregorianCal); }
124 
125 	int opCmp(const AbsTime b) const pure @safe nothrow @nogc { return this.stdTime < b.stdTime ? -1 : this.stdTime > b.stdTime ? +1 : 0; }
126 	int opCmp(const SysTime b) const pure @safe nothrow @nogc { return this.opCmp(AbsTime(b)); }
127 
128 	Duration opBinary(string op : "-")(AbsTime b) const pure @safe nothrow @nogc { return (this.stdTime - b.stdTime).stdDur; }
129 	Duration opBinary(string op : "-")(SysTime b) const pure @safe nothrow @nogc { return (this.stdTime - b.stdTime).stdDur; }
130 	Duration opBinaryRight(string op : "-")(SysTime a) const pure @safe nothrow @nogc { return (a.stdTime - this.stdTime).stdDur; }
131 
132 	AbsTime opBinary(string op : "-")(Duration d) const pure @safe nothrow @nogc { return AbsTime(this.stdTime - d.stdTime); }
133 	AbsTime opBinary(string op : "+")(Duration d) const pure @safe nothrow @nogc { return AbsTime(this.stdTime + d.stdTime); }
134 	AbsTime opBinaryRight(string op : "+")(Duration d) const pure @safe nothrow @nogc { return AbsTime(d.stdTime + this.stdTime); }
135 
136 	string toString() const @safe nothrow { return sysTime.toString(); }
137 
138 	static enum min = AbsTime(SysTime.min.stdTime);
139 	static enum max = AbsTime(SysTime.max.stdTime);
140 }
141 
142 AbsTime absTime(StdTime stdTime) { return AbsTime(stdTime); }
143 AbsTime	absTime(SysTime sysTime) { return AbsTime(sysTime); }