Xclox
C++11 header-only cross-platform date and time library with an asynchronous NTP client
time.h
1 /*
2  * Copyright (c) 2024 Abdullatif Kalla.
3  *
4  * This source code is licensed under the MIT license found in the
5  * LICENSE.txt file in the root directory of this source tree.
6  */
7 
8 #include "xclox/time.hpp"
9 
10 using namespace xclox;
11 using namespace std::chrono;
12 
13 TEST_SUITE("Time")
14 {
15  TEST_CASE("constructible")
16  {
17  SUBCASE("default")
18  {
19  Time t;
20 
21  CHECK(t.hour() == 0);
22  CHECK(t.minute() == 0);
23  CHECK(t.second() == 0);
24  CHECK(t.millisecond() == 0);
25  CHECK(t.microsecond() == 0);
26  CHECK(t.nanosecond() == 0);
27  }
28  SUBCASE("hour, minute, second")
29  {
30  Time t(13, 44, 2);
31 
32  CHECK(t.hour() == 13);
33  CHECK(t.minute() == 44);
34  CHECK(t.second() == 2);
35  CHECK(t.millisecond() == 0);
36  CHECK(t.microsecond() == 0);
37  CHECK(t.nanosecond() == 0);
38  }
39  SUBCASE("time values with a subsecond duration")
40  {
41  Time t(13, 44, 2, Time::Nanoseconds(781945521));
42 
43  CHECK(t.hour() == 13);
44  CHECK(t.minute() == 44);
45  CHECK(t.second() == 2);
46  CHECK(t.millisecond() == 781);
47  CHECK(t.microsecond() == 781945);
48  CHECK(t.nanosecond() == 781945521);
49  }
50  SUBCASE("aliased time duration")
51  {
52  CHECK(Time(Time::Hours(23)) == Time(23, 0, 0));
53  CHECK(Time(Time::Minutes(178)) == Time(2, 58, 0, 0));
54  CHECK(Time(Time::Seconds(7199)) == Time(1, 59, 59, 0));
55  CHECK(Time(Time::Milliseconds(7198943)) == Time(1, 59, 58, 943));
56  CHECK(Time(Time::Microseconds(74362675869)) == Time(20, 39, 22, Time::Microseconds(675869)));
57  CHECK(Time(Time::Nanoseconds(8974362675546)) == Time(2, 29, 34, Time::Nanoseconds(362675546)));
58  CHECK(Time(Time::Hours(16) + Time::Minutes(18) + Time::Seconds(55) + Time::Milliseconds(178) + Time::Microseconds(221) + Time::Nanoseconds(759)) == Time(16, 18, 55, Time::Nanoseconds(178221759)));
59  }
60  SUBCASE("system time duration")
61  {
62  const auto& duration = system_clock::now().time_since_epoch() % hours(24);
63  Time t(duration);
64 
65  CHECK(t.hour() == duration_cast<hours>(duration).count());
66  CHECK(t.minute() == duration_cast<minutes>(duration % hours(1)).count());
67  CHECK(t.second() == duration_cast<seconds>(duration % minutes(1)).count());
68  CHECK(t.millisecond() == duration_cast<milliseconds>(duration % seconds(1)).count());
69  CHECK(t.microsecond() == duration_cast<microseconds>(duration % seconds(1)).count());
70  CHECK(t.nanosecond() == duration_cast<nanoseconds>(duration % seconds(1)).count());
71  }
72  SUBCASE("system time point")
73  {
74  const auto& now = system_clock::now();
75  Time t(now);
76 
77  CHECK(t.hour() == duration_cast<hours>(now.time_since_epoch() % hours(24)).count());
78  CHECK(t.minute() == duration_cast<minutes>(now.time_since_epoch() % hours(1)).count());
79  CHECK(t.second() == duration_cast<seconds>(now.time_since_epoch() % minutes(1)).count());
80  CHECK(t.millisecond() == duration_cast<milliseconds>(now.time_since_epoch() % seconds(1)).count());
81  CHECK(t.microsecond() == duration_cast<microseconds>(now.time_since_epoch() % seconds(1)).count());
82  CHECK(t.nanosecond() == duration_cast<nanoseconds>(now.time_since_epoch() % seconds(1)).count());
83  }
84  }
85 
86  TEST_CASE("validatable")
87  {
88  CHECK(Time().isValid() == false);
89  CHECK(Time(Time::Hours(-1)).isValid() == false);
90  CHECK(Time(Time::Hours(23)).isValid() == true);
91  CHECK(Time(Time::Hours(24)).isValid() == false);
92  CHECK(Time(Time::Hours(25)).isValid() == false);
93  // additional tests.
94  CHECK(Time(0, 0, 0).isValid() == true);
95  CHECK(Time(1, 2, 3).isValid() == true);
96  CHECK(Time(-1, 0, 3).isValid() == false);
97  CHECK(Time(30, 20, 10).isValid() == false);
98  CHECK(Time(system_clock::now().time_since_epoch()).isValid() == false);
99  CHECK(Time(system_clock::now()).isValid() == true);
100  CHECK(Time::current().isValid() == true);
101  CHECK(Time::midnight().isValid() == true);
102  }
103 
104  TEST_CASE("comparable")
105  {
106  CHECK(Time(7, 9, 2, Time::Nanoseconds(675869233)) < Time(7, 45, 22, Time::Nanoseconds(536969233)));
107  CHECK(Time(7, 9, 2, Time::Nanoseconds(536969435)) <= Time(7, 9, 2, Time::Nanoseconds(536969435)));
108  CHECK(Time(8, 9, 2, Time::Nanoseconds(675869676)) > Time(7, 45, 22, Time::Nanoseconds(536969212)));
109  CHECK(Time(7, 46, 2, Time::Nanoseconds(675869112)) >= Time(7, 45, 22, Time::Nanoseconds(536969112)));
110  CHECK(Time(15, 4, 12, Time::Nanoseconds(554969231)) == Time(15, 4, 12, Time::Nanoseconds(554969231)));
111  CHECK(Time(7, 9, 2, Time::Nanoseconds(675869123)) != Time(4, 45, 22, Time::Nanoseconds(536969321)));
112  }
113 
114  TEST_CASE("copyable")
115  {
116  Time t1;
117  Time t2(1, 2, 3);
118  Time t3(3, 2, 1);
119 
120  SUBCASE("construction")
121  {
122  Time t4(t1);
123  Time t5(t2);
124  Time t6(std::move(t3));
125  CHECK(t1 == t4);
126  CHECK(t2 == t5);
127  CHECK(t3 == t6);
128  }
129  SUBCASE("assignment")
130  {
131  Time t4 = t1;
132  Time t5 = t2;
133  Time t6 = std::move(t3);
134 
135  CHECK(t1 == t4);
136  CHECK(t2 == t5);
137  CHECK(t3 == t6);
138  }
139  }
140 
141  TEST_CASE("current time")
142  {
143  CHECK(abs(duration_cast<milliseconds>(Time::current() - Time(system_clock::now())).count()) < 100);
144  }
145 
146  TEST_CASE("midnight")
147  {
148  CHECK((Time::midnight() - Time(0, 0, 0)).count() == 0);
149  }
150 
151  TEST_CASE("addition & subtraction")
152  {
153  SUBCASE("hours")
154  {
155  CHECK(Time(Time::Hours(7)).addHours(2) == Time(9, 0, 0, 0));
156  CHECK(Time(Time::Hours(9)).subtractHours(2) == Time(7, 0, 0, 0));
157  }
158  SUBCASE("minutes")
159  {
160  CHECK(Time(Time::Minutes(178)).addMinutes(2) == Time(3, 0, 0, 0));
161  CHECK(Time(Time::Minutes(180)).subtractMinutes(2) == Time(2, 58, 0, 0));
162  }
163  SUBCASE("seconds")
164  {
165  CHECK(Time(Time::Seconds(55)).addSeconds(9) == Time(0, 1, 4, 0));
166  CHECK(Time(Time::Seconds(64)).subtractSeconds(9) == Time(0, 0, 55, 0));
167  }
168  SUBCASE("milliseconds")
169  {
170  CHECK(Time(Time::Milliseconds(555)).addMilliseconds(445) == Time(0, 0, 1, 0));
171  CHECK(Time(Time::Milliseconds(1000)).subtractMilliseconds(445) == Time(0, 0, 0, 555));
172  }
173  SUBCASE("microseconds")
174  {
175  CHECK(Time(Time::Microseconds(555)).addMicroseconds(445) == Time(0, 0, 0, Time::Microseconds(1000)));
176  CHECK(Time(Time::Microseconds(1000)).subtractMicroseconds(445) == Time(0, 0, 0, Time::Microseconds(555)));
177  }
178  SUBCASE("nanoseconds")
179  {
180  CHECK(Time(Time::Nanoseconds(8974362675556)).addNanoseconds(445) == Time(2, 29, 34, Time::Nanoseconds(362676001)));
181  CHECK(Time(Time::Nanoseconds(8974362676001)).subtractNanoseconds(445) == Time(2, 29, 34, Time::Nanoseconds(362675556)));
182  }
183  SUBCASE("operators")
184  {
185  CHECK(Time(11, 23, 11) - Time(10, 23, 11) == Time::Hours(1));
186  CHECK(Time(11, 23, 11) - Time::Hours(10) == Time(1, 23, 11));
187  CHECK(Time(1, 23, 11) + Time::Hours(10) == Time(11, 23, 11));
188  }
189  }
190 
191  TEST_CASE("diffing")
192  {
193  CHECK(Time::hoursBetween(Time(10, 23, 25), Time(11, 23, 29)) == 1);
194  CHECK(Time::minutesBetween(Time(11, 23, 11), Time(11, 53, 11)) == 30);
195  CHECK(Time::secondsBetween(Time(9, 23, 55), Time(9, 23, 35)) == -20);
196  CHECK(Time::millisecondsBetween(Time(7, 23, 11, 850), Time(7, 23, 12, 900)) == 1050);
197  CHECK(Time::microsecondsBetween(Time(13, 23, 20, Time::Microseconds(789500)), Time(13, 23, 20, Time::Microseconds(789400))) == -100);
198  CHECK(Time::nanosecondsBetween(Time(18, 56, 5, Time::Nanoseconds(789500235)), Time(18, 56, 5, Time::Nanoseconds(789500135))) == -100);
199  }
200 
201  TEST_CASE("formatting")
202  {
203  SUBCASE("empty format")
204  {
205  CHECK(Time(1, 2, 3).toString("") == "");
206  }
207  SUBCASE("invalid time")
208  {
209  CHECK(Time().toString("H:m:s") == "");
210  CHECK(Time(0, 0, -1).toString("H:m:s") == "");
211  CHECK(Time(Time::Hours(24)).toString("HH:mm:ss") == "");
212  }
213  SUBCASE("12 hours")
214  {
215  CHECK(Time(23, 45, 2).toString("H:m:s") == "11:45:2");
216  CHECK(Time(0, 45, 2).toString("H:m:s") == "12:45:2");
217  CHECK(Time(3, 45, 2).toString("HH:m:s") == "03:45:2");
218  }
219  SUBCASE("meridiem label small letters")
220  {
221  CHECK(Time(3, 45, 2).toString("HH:mm:ss a") == "03:45:02 am");
222  CHECK(Time(13, 45, 2).toString("HH:mm:ss a") == "01:45:02 pm");
223  CHECK(Time(Time::Hours(0)).toString("HH:mm:ss a") == "12:00:00 am");
224  CHECK(Time(Time::Hours(12)).toString("HH:mm:ss a") == "12:00:00 pm");
225  }
226  SUBCASE("meridiem label capital letters")
227  {
228  CHECK(Time(3, 45, 2).toString("HH:mm:ss A") == "03:45:02 AM");
229  CHECK(Time(13, 45, 2).toString("HH:mm:ss A") == "01:45:02 PM");
230  CHECK(Time(Time::Hours(0)).toString("HH:mm:ss A") == "12:00:00 AM");
231  CHECK(Time(Time::Hours(12)).toString("HH:mm:ss A") == "12:00:00 PM");
232  }
233  SUBCASE("reserve unparsable expressions")
234  {
235  CHECK(Time(3, 45, 2).toString("hhmmss") == "034502");
236  CHECK(Time(21, 52, 41).toString("hhmmss ieee") == "215241 ieee");
237  }
238  SUBCASE("24 hours")
239  {
240  CHECK(Time(0, 0, 0).toString("h:m:s") == "0:0:0");
241  CHECK(Time(0, 0, 0).toString("hh:mm:ss") == "00:00:00");
242  CHECK(Time(22, 45, 2).toString("h:m:s") == "22:45:2");
243  CHECK(Time(3, 45, 2).toString("hh:m:s") == "03:45:2");
244  }
245  SUBCASE("minutes")
246  {
247  CHECK(Time(Time::Hours(22) + Time::Minutes(5)).toString("h:m") == "22:5");
248  CHECK(Time(Time::Hours(22) + Time::Minutes(5)).toString("h:mm") == "22:05");
249  }
250  SUBCASE("seconds")
251  {
252  CHECK(Time(Time::Minutes(55) + Time::Seconds(7)).toString("m:s") == "55:7");
253  CHECK(Time(Time::Minutes(55) + Time::Seconds(7)).toString("m:ss") == "55:07");
254  }
255  SUBCASE("fraction")
256  {
258 
259  CHECK(t.toString("hh:mm:ss.f") == "07:09:02.6");
260  CHECK(t.toString("hh:mm:ss.ff") == "07:09:02.67");
261  CHECK(t.toString("hh:mm:ss.fff") == "07:09:02.675");
262  CHECK(t.toString("hh:mm:ss.ffff") == "07:09:02.6758");
263  CHECK(t.toString("hh:mm:ss.fffff") == "07:09:02.67586");
264  CHECK(t.toString("hh:mm:ss.ffffff") == "07:09:02.675869");
265  CHECK(t.toString("hh:mm:ss.fffffff") == "07:09:02.6758690");
266  CHECK(t.toString("hh:mm:ss.ffffffff") == "07:09:02.67586909");
267  CHECK(t.toString("hh:mm:ss.fffffffff") == "07:09:02.675869093");
268  }
269  SUBCASE("fraction - zero milliseconds")
270  {
272 
273  CHECK(t.toString("hh:mm:ss.f") == "07:09:02.0");
274  CHECK(t.toString("hh:mm:ss.ff") == "07:09:02.00");
275  CHECK(t.toString("hh:mm:ss.fff") == "07:09:02.000");
276  CHECK(t.toString("hh:mm:ss.ffff") == "07:09:02.0008");
277  CHECK(t.toString("hh:mm:ss.fffff") == "07:09:02.00086");
278  CHECK(t.toString("hh:mm:ss.ffffff") == "07:09:02.000869");
279  CHECK(t.toString("hh:mm:ss.fffffff") == "07:09:02.0008690");
280  CHECK(t.toString("hh:mm:ss.ffffffff") == "07:09:02.00086909");
281  CHECK(t.toString("hh:mm:ss.fffffffff") == "07:09:02.000869093");
282  }
283  SUBCASE("fraction - zero microseconds")
284  {
286 
287  CHECK(t.toString("h:m:s") == "7:9:2");
288  CHECK(t.toString("hh:mm:ss") == "07:09:02");
289  CHECK(t.toString("hh:mm:ss.f") == "07:09:02.6");
290  CHECK(t.toString("hh:mm:ss.ff") == "07:09:02.67");
291  CHECK(t.toString("hh:mm:ss.fff") == "07:09:02.675");
292  CHECK(t.toString("hh:mm:ss.ffff") == "07:09:02.6750");
293  CHECK(t.toString("hh:mm:ss.fffff") == "07:09:02.67500");
294  CHECK(t.toString("hh:mm:ss.ffffff") == "07:09:02.675000");
295  CHECK(t.toString("hh:mm:ss.fffffff") == "07:09:02.6750000");
296  CHECK(t.toString("hh:mm:ss.ffffffff") == "07:09:02.67500004");
297  CHECK(t.toString("hh:mm:ss.fffffffff") == "07:09:02.675000044");
298  }
299  SUBCASE("fraction - zero nanoseconds")
300  {
302 
303  CHECK(t.toString("h:m:s") == "7:9:2");
304  CHECK(t.toString("hh:mm:ss") == "07:09:02");
305  CHECK(t.toString("hh:mm:ss.f") == "07:09:02.6");
306  CHECK(t.toString("hh:mm:ss.ff") == "07:09:02.67");
307  CHECK(t.toString("hh:mm:ss.fff") == "07:09:02.675");
308  CHECK(t.toString("hh:mm:ss.ffff") == "07:09:02.6758");
309  CHECK(t.toString("hh:mm:ss.fffff") == "07:09:02.67586");
310  CHECK(t.toString("hh:mm:ss.ffffff") == "07:09:02.675869");
311  CHECK(t.toString("hh:mm:ss.fffffff") == "07:09:02.6758690");
312  CHECK(t.toString("hh:mm:ss.ffffffff") == "07:09:02.67586900");
313  CHECK(t.toString("hh:mm:ss.fffffffff") == "07:09:02.675869000");
314  CHECK(t.toString("hh:mm:ss.fff fff fff") == "07:09:02.675 675 675");
315  }
316  }
317 
318  TEST_CASE("parsing")
319  {
320  CHECK(Time::fromString("9", "h") == Time(Time::Hours(9)));
321  CHECK(Time::fromString("01", "hh") == Time(Time::Hours(1)));
322  CHECK(Time::fromString("9", "H") == Time(Time::Hours(9)));
323  CHECK(Time::fromString("12", "H") == Time(Time::Hours(12)));
324  CHECK(Time::fromString("01", "HH") == Time(Time::Hours(1)));
325  CHECK(Time::fromString("01 pm", "HH a") == Time(Time::Hours(13)));
326  CHECK(Time::fromString("01 PM", "HH A") == Time(Time::Hours(13)));
327  CHECK(Time::fromString("3", "m") == Time(Time::Minutes(3)));
328  CHECK(Time::fromString("03", "mm") == Time(Time::Minutes(3)));
329  CHECK(Time::fromString("37", "s") == Time(Time::Seconds(37)));
330  CHECK(Time::fromString("06", "ss") == Time(Time::Seconds(6)));
331  CHECK(Time::fromString("1", "f") == Time(Time::Milliseconds(100)));
332  CHECK(Time::fromString("12", "ff") == Time(Time::Milliseconds(120)));
333  CHECK(Time::fromString("123", "fff") == Time(Time::Milliseconds(123)));
334  CHECK(Time::fromString("1234", "ffff") == Time(Time::Microseconds(123400)));
335  CHECK(Time::fromString("12345", "fffff") == Time(Time::Microseconds(123450)));
336  CHECK(Time::fromString("123456", "ffffff") == Time(Time::Microseconds(123456)));
337  CHECK(Time::fromString("1234567", "fffffff") == Time(Time::Nanoseconds(123456700)));
338  CHECK(Time::fromString("12345678", "ffffffff") == Time(Time::Nanoseconds(123456780)));
339  CHECK(Time::fromString("123456789", "fffffffff") == Time(Time::Nanoseconds(123456789)));
340  CHECK(Time::fromString("14:32:09.123456789", "hh:mm:ss.fffffffff") == Time(14, 32, 9, Time::Nanoseconds(123456789)));
341  CHECK(Time::fromString("143209", "hhmmss") == Time(14, 32, 9));
342  CHECK(Time::fromString("ieee 143209", "ieee hhmmss") == Time(14, 32, 9));
343  }
344 
345  TEST_CASE("conversion")
346  {
347  SUBCASE("nanoseconds")
348  {
349  CHECK(Time(23, 56, 33, Time::Nanoseconds(978432162)).toNanosecondsSinceMidnight() == 86193978432162);
350  }
351  SUBCASE("microseconds")
352  {
353  CHECK(Time(23, 56, 33, Time::Nanoseconds(978432162)).toMicrosecondsSinceMidnight() == 86193978432);
354  }
355  SUBCASE("milliseconds")
356  {
357  CHECK(Time(23, 56, 33, Time::Nanoseconds(978432162)).toMillisecondsSinceMidnight() == 86193978);
358  }
359  SUBCASE("seconds")
360  {
361  CHECK(Time(23, 56, 33, Time::Nanoseconds(978432162)).toSecondsSinceMidnight() == 86193);
362  }
363  SUBCASE("minutes")
364  {
365  CHECK(Time(23, 56, 33, Time::Nanoseconds(978432162)).toMinutesSinceMidnight() == 1436);
366  }
367  SUBCASE("hours")
368  {
369  CHECK(Time(23, 56, 33, Time::Nanoseconds(978432162)).toHoursSinceMidnight() == 23);
370  }
371  SUBCASE("broken")
372  {
373  Time t(14, 32, 9);
374  std::tm tmTime = t.toBrokenStdTime();
375  CHECK(tmTime.tm_hour == t.hour());
376  CHECK(tmTime.tm_min == t.minute());
377  CHECK(tmTime.tm_sec == t.second());
378  }
379  SUBCASE("scalar")
380  {
381  Time t(14, 32, 9);
382  std::time_t tTime = t.toScalarStdTime();
383  CHECK(tTime == t.toSecondsSinceMidnight());
384  }
385  }
386 
387  TEST_CASE("serialization & deserialization")
388  {
389  Time t;
390  std::stringstream ss;
391  ss << Time(14, 32, 9);
392  ss >> t;
393  CHECK(t == Time(14, 32, 9));
394  }
395 } // TEST_SUITE
Time is an immutable time class representing a time without a time zone in the ISO-8601 calendar syst...
Definition: time.hpp:46
static long millisecondsBetween(const Time &from, const Time &to)
Returns the number of milliseconds between from and to.
Definition: time.hpp:599
static int minutesBetween(const Time &from, const Time &to)
Returns the number of minutes between from and to.
Definition: time.hpp:611
long nanosecond() const
Returns the nanosecond of second (0, 999999999).
Definition: time.hpp:263
int minute() const
Returns the minute of hour (0, 59).
Definition: time.hpp:287
int millisecond() const
Returns the millisecond of second (0, 999).
Definition: time.hpp:275
static Time current()
Returns a Time object set to the current time obtained from the system clock.
Definition: time.hpp:529
int hour() const
Returns the hour of day (0, 23).
Definition: time.hpp:293
std::chrono::milliseconds Milliseconds
Millisecond duration.
Definition: time.hpp:58
std::chrono::minutes Minutes
Minute duration.
Definition: time.hpp:60
static Time fromString(const std::string &time, const std::string &format)
Returns a Time object from the string time according to the format string format.
Definition: time.hpp:544
long microsecond() const
Returns the microsecond of second (0, 999999).
Definition: time.hpp:269
static long long microsecondsBetween(const Time &from, const Time &to)
Returns the number of microseconds between from and to.
Definition: time.hpp:593
static long long nanosecondsBetween(const Time &from, const Time &to)
Returns the number of nanoseconds between from and to.
Definition: time.hpp:587
std::chrono::microseconds Microseconds
Microsecond duration.
Definition: time.hpp:57
static int hoursBetween(const Time &from, const Time &to)
Returns the number of hours between from and to.
Definition: time.hpp:617
static long secondsBetween(const Time &from, const Time &to)
Returns the number of seconds between from and to.
Definition: time.hpp:605
std::chrono::nanoseconds Nanoseconds
Nanosecond duration.
Definition: time.hpp:56
std::chrono::seconds Seconds
Second duration.
Definition: time.hpp:59
std::chrono::hours Hours
Hour duration.
Definition: time.hpp:61
static Time midnight()
Returns a Time object set to midnight (i.e., "00:00:00").
Definition: time.hpp:535
int second() const
Returns the second of minute (0, 59).
Definition: time.hpp:281