C++ Utilities  5.10.5
Useful C++ classes and routines such as argument parser, IO and conversion utilities
datetime.cpp
Go to the documentation of this file.
1 #include "./datetime.h"
2 
3 #include "../conversion/stringbuilder.h"
4 #include "../conversion/stringconversion.h"
5 
6 #include <iomanip>
7 #include <sstream>
8 #include <stdexcept>
9 
10 using namespace std;
11 
12 namespace CppUtilities {
13 
14 const int DateTime::m_daysPerYear = 365;
15 const int DateTime::m_daysPer4Years = 1461;
16 const int DateTime::m_daysPer100Years = 36524;
17 const int DateTime::m_daysPer400Years = 146097;
18 const int DateTime::m_daysTo1601 = 584388;
19 const int DateTime::m_daysTo1899 = 693593;
20 const int DateTime::m_daysTo10000 = 3652059;
21 const int DateTime::m_daysToMonth365[13] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };
22 const int DateTime::m_daysToMonth366[13] = { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 };
23 const int DateTime::m_daysInMonth365[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
24 const int DateTime::m_daysInMonth366[12] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
25 
26 template <typename num1, typename num2, typename num3> constexpr bool inRangeInclMax(num1 val, num2 min, num3 max)
27 {
28  return (val) >= (min) && (val) <= (max);
29 }
30 
31 template <typename num1, typename num2, typename num3> constexpr bool inRangeExclMax(num1 val, num2 min, num3 max)
32 {
33  return (val) >= (min) && (val) < (max);
34 }
35 
59 DateTime DateTime::fromTimeStamp(time_t timeStamp)
60 {
61  if (timeStamp) {
62  struct tm *const timeinfo = localtime(&timeStamp);
63  return DateTime::fromDateAndTime(timeinfo->tm_year + 1900, timeinfo->tm_mon + 1, timeinfo->tm_mday, timeinfo->tm_hour, timeinfo->tm_min,
64  timeinfo->tm_sec < 60 ? timeinfo->tm_sec : 59, 0);
65  } else {
66  return DateTime();
67  }
68 }
69 
79 DateTime DateTime::fromString(const char *str)
80 {
81  int values[6] = { 0 };
82  int *const dayIndex = values + 2;
83  int *const secondsIndex = values + 5;
84  int *valueIndex = values;
85  int *const valuesEnd = values + 7;
86  double millisecondsFact = 100.0, milliseconds = 0.0;
87  for (const char *strIndex = str;; ++strIndex) {
88  const char c = *strIndex;
89  if (c <= '9' && c >= '0') {
90  if (valueIndex > secondsIndex) {
91  milliseconds += (c - '0') * millisecondsFact;
92  millisecondsFact /= 10;
93  } else {
94  Detail::raiseAndAdd(*valueIndex, 10, c);
95  }
96  } else if ((c == '-' || c == ':' || c == '/') || (c == '.' && (valueIndex == secondsIndex))
97  || ((c == ' ' || c == 'T') && (valueIndex == dayIndex))) {
98  if (++valueIndex == valuesEnd) {
99  break; // just ignore further values for now
100  }
101  } else if (c == '\0') {
102  break;
103  } else {
104  throw ConversionException(argsToString("Unexpected character \"", c, '\"'));
105  }
106  }
107  return DateTime::fromDateAndTime(values[0], values[1], *dayIndex, values[3], values[4], *secondsIndex, milliseconds);
108 }
109 
120 std::pair<DateTime, TimeSpan> DateTime::fromIsoString(const char *str)
121 {
122  int values[9] = { 0 };
123  int *const yearIndex = values + 0;
124  int *const monthIndex = values + 1;
125  int *const dayIndex = values + 2;
126  int *const hourIndex = values + 3;
127  int *const secondsIndex = values + 5;
128  int *const miliSecondsIndex = values + 6;
129  int *const deltaHourIndex = values + 7;
130  int *const valuesEnd = values + 9;
131  int *valueIndex = values;
132  unsigned int remainingDigits = 4;
133  bool deltaNegative = false;
134  double millisecondsFact = 100.0, milliseconds = 0.0;
135  for (const char *strIndex = str;; ++strIndex) {
136  const char c = *strIndex;
137  if (c <= '9' && c >= '0') {
138  if (valueIndex == miliSecondsIndex) {
139  milliseconds += (c - '0') * millisecondsFact;
140  millisecondsFact /= 10;
141  } else {
142  if (!remainingDigits) {
143  if (++valueIndex == miliSecondsIndex || valueIndex >= valuesEnd) {
144  throw ConversionException("Max. number of digits exceeded");
145  }
146  remainingDigits = 2;
147  }
148  *valueIndex *= 10;
149  *valueIndex += c - '0';
150  remainingDigits -= 1;
151  }
152  } else if (c == 'T') {
153  if (++valueIndex != hourIndex) {
154  throw ConversionException("\"T\" expected before hour");
155  }
156  remainingDigits = 2;
157  } else if (c == '-') {
158  if (valueIndex < dayIndex) {
159  ++valueIndex;
160  } else if (++valueIndex >= secondsIndex) {
161  valueIndex = deltaHourIndex;
162  deltaNegative = true;
163  } else {
164  throw ConversionException("Unexpected \"-\" after day");
165  }
166  remainingDigits = 2;
167  } else if (c == '.') {
168  if (valueIndex != secondsIndex) {
169  throw ConversionException("Unexpected \".\"");
170  } else {
171  ++valueIndex;
172  }
173  } else if (c == ':') {
174  if (valueIndex < hourIndex) {
175  throw ConversionException("Unexpected \":\" before hour");
176  } else if (valueIndex == secondsIndex) {
177  throw ConversionException("Unexpected \":\" after second");
178  } else {
179  ++valueIndex;
180  }
181  remainingDigits = 2;
182  } else if ((c == '+') && (++valueIndex >= secondsIndex)) {
183  valueIndex = deltaHourIndex;
184  deltaNegative = false;
185  remainingDigits = 2;
186  } else if ((c == 'Z') && (++valueIndex >= secondsIndex)) {
187  valueIndex = deltaHourIndex + 2;
188  remainingDigits = 2;
189  } else if (c == '\0') {
190  break;
191  } else {
192  throw ConversionException(argsToString("Unexpected \"", c, '\"'));
193  }
194  }
195  auto delta = TimeSpan::fromMinutes(*deltaHourIndex * 60 + values[8]);
196  if (deltaNegative) {
197  delta = TimeSpan(-delta.totalTicks());
198  }
199  if (valueIndex < monthIndex && !*monthIndex) {
200  *monthIndex = 1;
201  }
202  if (valueIndex < dayIndex && !*dayIndex) {
203  *dayIndex = 1;
204  }
205  return make_pair(DateTime::fromDateAndTime(*yearIndex, *monthIndex, *dayIndex, *hourIndex, values[4], *secondsIndex, milliseconds), delta);
206 }
207 
213 void DateTime::toString(string &result, DateTimeOutputFormat format, bool noMilliseconds) const
214 {
215  if (format == DateTimeOutputFormat::Iso) {
216  result = toIsoString();
217  return;
218  }
219 
220  stringstream s(stringstream::in | stringstream::out);
221  s << setfill('0');
222 
223  if (format == DateTimeOutputFormat::IsoOmittingDefaultComponents) {
224  constexpr auto dateDelimiter = '-', timeDelimiter = ':';
225  const int components[] = { year(), month(), day(), hour(), minute(), second(), millisecond(), microsecond(), nanosecond() };
226  const int *const firstTimeComponent = components + 3;
227  const int *const firstFractionalComponent = components + 6;
228  const int *const lastComponent = components + 8;
229  const int *componentsEnd = noMilliseconds ? firstFractionalComponent : lastComponent + 1;
230  for (const int *i = componentsEnd - 1; i > components; --i) {
231  if (i >= firstTimeComponent && *i == 0) {
232  componentsEnd = i;
233  } else if (i < firstTimeComponent && *i == 1) {
234  componentsEnd = i;
235  }
236  }
237  for (const int *i = components; i != componentsEnd; ++i) {
238  if (i == firstTimeComponent) {
239  s << 'T';
240  } else if (i == firstFractionalComponent) {
241  s << '.';
242  }
243  if (i == components) {
244  s << setw(4) << *i;
245  } else if (i < firstFractionalComponent) {
246  if (i < firstTimeComponent) {
247  s << dateDelimiter;
248  } else if (i > firstTimeComponent) {
249  s << timeDelimiter;
250  }
251  s << setw(2) << *i;
252  } else if (i < lastComponent) {
253  s << setw(3) << *i;
254  } else {
255  s << *i / TimeSpan::nanosecondsPerTick;
256  }
257  }
258  result = s.str();
259  return;
260  }
261 
262  if (format == DateTimeOutputFormat::DateTimeAndWeekday || format == DateTimeOutputFormat::DateTimeAndShortWeekday)
263  s << printDayOfWeek(dayOfWeek(), format == DateTimeOutputFormat::DateTimeAndShortWeekday) << ' ';
264  if (format == DateTimeOutputFormat::DateOnly || format == DateTimeOutputFormat::DateAndTime || format == DateTimeOutputFormat::DateTimeAndWeekday
265  || format == DateTimeOutputFormat::DateTimeAndShortWeekday)
266  s << setw(4) << year() << '-' << setw(2) << month() << '-' << setw(2) << day();
267  if (format == DateTimeOutputFormat::DateAndTime || format == DateTimeOutputFormat::DateTimeAndWeekday
268  || format == DateTimeOutputFormat::DateTimeAndShortWeekday)
269  s << " ";
270  if (format == DateTimeOutputFormat::TimeOnly || format == DateTimeOutputFormat::DateAndTime || format == DateTimeOutputFormat::DateTimeAndWeekday
271  || format == DateTimeOutputFormat::DateTimeAndShortWeekday) {
272  s << setw(2) << hour() << ':' << setw(2) << minute() << ':' << setw(2) << second();
273  int ms = millisecond();
274  if (!noMilliseconds && ms > 0) {
275  s << '.' << setw(3) << ms;
276  }
277  }
278  result = s.str();
279 }
280 
285 string DateTime::toIsoStringWithCustomDelimiters(TimeSpan timeZoneDelta, char dateDelimiter, char timeDelimiter, char timeZoneDelimiter) const
286 {
287  stringstream s(stringstream::in | stringstream::out);
288  s << setfill('0');
289  s << setw(4) << year() << dateDelimiter << setw(2) << month() << dateDelimiter << setw(2) << day() << 'T' << setw(2) << hour() << timeDelimiter
290  << setw(2) << minute() << timeDelimiter << setw(2) << second();
291  const int milli(millisecond());
292  const int micro(microsecond());
293  const int nano(nanosecond());
294  if (milli || micro || nano) {
295  s << '.' << setw(3) << milli;
296  if (micro || nano) {
297  s << setw(3) << micro;
298  if (nano) {
299  s << nano / TimeSpan::nanosecondsPerTick;
300  }
301  }
302  }
303  if (!timeZoneDelta.isNull()) {
304  if (timeZoneDelta.isNegative()) {
305  s << '-';
306  timeZoneDelta = TimeSpan(-timeZoneDelta.totalTicks());
307  } else {
308  s << '+';
309  }
310  s << setw(2) << timeZoneDelta.hours() << timeZoneDelimiter << setw(2) << timeZoneDelta.minutes();
311  }
312  return s.str();
313 }
314 
319 string DateTime::toIsoString(TimeSpan timeZoneDelta) const
320 {
321  return toIsoStringWithCustomDelimiters(timeZoneDelta);
322 }
323 
331 const char *DateTime::printDayOfWeek(DayOfWeek dayOfWeek, bool abbreviation)
332 {
333  if (abbreviation) {
334  switch (dayOfWeek) {
335  case DayOfWeek::Monday:
336  return "Mon";
337  case DayOfWeek::Tuesday:
338  return "Tue";
339  case DayOfWeek::Wednesday:
340  return "Wed";
341  case DayOfWeek::Thursday:
342  return "Thu";
343  case DayOfWeek::Friday:
344  return "Fri";
345  case DayOfWeek::Saturday:
346  return "Sat";
347  case DayOfWeek::Sunday:
348  return "Sun";
349  }
350  } else {
351  switch (dayOfWeek) {
352  case DayOfWeek::Monday:
353  return "Monday";
354  case DayOfWeek::Tuesday:
355  return "Tuesday";
356  case DayOfWeek::Wednesday:
357  return "Wednesday";
358  case DayOfWeek::Thursday:
359  return "Thursday";
360  case DayOfWeek::Friday:
361  return "Friday";
362  case DayOfWeek::Saturday:
363  return "Saturday";
364  case DayOfWeek::Sunday:
365  return "Sunday";
366  }
367  }
368  return "";
369 }
370 
371 #if defined(PLATFORM_UNIX) && !defined(PLATFORM_MAC)
376 DateTime DateTime::exactGmtNow()
377 {
378  struct timespec t;
379  clock_gettime(CLOCK_REALTIME, &t);
380  return DateTime(DateTime::unixEpochStart().totalTicks() + static_cast<std::uint64_t>(t.tv_sec) * TimeSpan::ticksPerSecond
381  + static_cast<std::uint64_t>(t.tv_nsec) / 100);
382 }
383 #endif
384 
388 std::uint64_t DateTime::dateToTicks(int year, int month, int day)
389 {
390  if (!inRangeInclMax(year, 1, 9999)) {
391  throw ConversionException("year is out of range");
392  }
393  if (!inRangeInclMax(month, 1, 12)) {
394  throw ConversionException("month is out of range");
395  }
396  const auto *const daysToMonth = reinterpret_cast<const int *>(isLeapYear(year) ? m_daysToMonth366 : m_daysToMonth365);
397  const int passedMonth = month - 1;
398  if (!inRangeInclMax(day, 1, daysToMonth[month] - daysToMonth[passedMonth])) {
399  throw ConversionException("day is out of range");
400  }
401  const auto passedYears = static_cast<unsigned int>(year - 1);
402  const auto passedDays = static_cast<unsigned int>(day - 1);
403  return (passedYears * m_daysPerYear + passedYears / 4 - passedYears / 100 + passedYears / 400
404  + static_cast<unsigned int>(daysToMonth[passedMonth]) + passedDays)
405  * TimeSpan::ticksPerDay;
406 }
407 
411 std::uint64_t DateTime::timeToTicks(int hour, int minute, int second, double millisecond)
412 {
413  if (!inRangeExclMax(hour, 0, 24)) {
414  throw ConversionException("hour is out of range");
415  }
416  if (!inRangeExclMax(minute, 0, 60)) {
417  throw ConversionException("minute is out of range");
418  }
419  if (!inRangeExclMax(second, 0, 60)) {
420  throw ConversionException("second is out of range");
421  }
422  if (!inRangeExclMax(millisecond, 0.0, 1000.0)) {
423  throw ConversionException("millisecond is out of range");
424  }
425  return static_cast<std::uint64_t>(hour * TimeSpan::ticksPerHour) + static_cast<std::uint64_t>(minute * TimeSpan::ticksPerMinute)
426  + static_cast<std::uint64_t>(second * TimeSpan::ticksPerSecond) + static_cast<std::uint64_t>(millisecond * TimeSpan::ticksPerMillisecond);
427 }
428 
433 int DateTime::getDatePart(DatePart part) const
434 {
435  const auto fullDays = static_cast<int>(m_ticks / TimeSpan::ticksPerDay);
436  const auto full400YearBlocks = fullDays / m_daysPer400Years;
437  const auto daysMinusFull400YearBlocks = fullDays - full400YearBlocks * m_daysPer400Years;
438  auto full100YearBlocks = daysMinusFull400YearBlocks / m_daysPer100Years;
439  if (full100YearBlocks == 4) {
440  full100YearBlocks = 3;
441  }
442  const auto daysMinusFull100YearBlocks = daysMinusFull400YearBlocks - full100YearBlocks * m_daysPer100Years;
443  const auto full4YearBlocks = daysMinusFull100YearBlocks / m_daysPer4Years;
444  const auto daysMinusFull4YearBlocks = daysMinusFull100YearBlocks - full4YearBlocks * m_daysPer4Years;
445  auto full1YearBlocks = daysMinusFull4YearBlocks / m_daysPerYear;
446  if (full1YearBlocks == 4) {
447  full1YearBlocks = 3;
448  }
449  if (part == DatePart::Year) {
450  return full400YearBlocks * 400 + full100YearBlocks * 100 + full4YearBlocks * 4 + full1YearBlocks + 1;
451  }
452  const auto restDays = daysMinusFull4YearBlocks - full1YearBlocks * m_daysPerYear;
453  if (part == DatePart::DayOfYear) { // day
454  return restDays + 1;
455  }
456  const auto *const daysToMonth = (full1YearBlocks == 3 && (full4YearBlocks != 24 || full100YearBlocks == 3)) ? m_daysToMonth366 : m_daysToMonth365;
457  auto month = 1;
458  while (restDays >= daysToMonth[month]) {
459  ++month;
460  }
461  if (part == DatePart::Month) {
462  return month;
463  } else if (part == DatePart::Day) {
464  return restDays - daysToMonth[month - 1] + 1;
465  }
466  return 0;
467 }
468 
469 } // namespace CppUtilities
The ConversionException class is thrown by the various conversion functions of this library when a co...
Represents an instant in time, typically expressed as a date and time of day.
Definition: datetime.h:53
Represents a time interval.
Definition: timespan.h:25
constexpr bool isNull() const
Returns true if the time interval represented by the current TimeSpan class is null.
Definition: timespan.h:441
constexpr std::int64_t totalTicks() const
Returns the number of ticks that represent the value of the current TimeSpan class.
Definition: timespan.h:190
constexpr int minutes() const
Returns the minutes component of the time interval represented by the current TimeSpan class.
Definition: timespan.h:280
constexpr bool isNegative() const
Returns true if the time interval represented by the current TimeSpan class is negative.
Definition: timespan.h:449
constexpr int hours() const
Returns the hours component of the time interval represented by the current TimeSpan class.
Definition: timespan.h:288
Contains all utilities provides by the c++utilities library.
DatePart
Specifies the date part.
Definition: datetime.h:46
constexpr bool inRangeExclMax(num1 val, num2 min, num3 max)
Definition: datetime.cpp:31
constexpr bool inRangeInclMax(num1 val, num2 min, num3 max)
Definition: datetime.cpp:26
StringType argsToString(Args &&...args)
constexpr T max(T first, T second)
Returns the greatest of the given items.
Definition: math.h:100
DateTimeOutputFormat
Specifies the output format.
Definition: datetime.h:17
constexpr T min(T first, T second)
Returns the smallest of the given items.
Definition: math.h:88
DayOfWeek
Specifies the day of the week.
Definition: datetime.h:31
constexpr int i