crawlserv++  [under development]
Application for crawling and analyzing textual content of websites.
DateTime.hpp
Go to the documentation of this file.
1 /*
2  *
3  * ---
4  *
5  * Copyright (C) 2021 Anselm Schmidt (ans[ät]ohai.su)
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version in addition to the terms of any
11  * licences already herein identified.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program. If not, see <https://www.gnu.org/licenses/>.
20  *
21  * ---
22  *
23  * DateTime.hpp
24  *
25  * Namespace for global date/time helper functions.
26  *
27  * Created on: Dec 10, 2018
28  * Author: ans
29  */
30 
31 #ifndef HELPER_DATETIME_HPP_
32 #define HELPER_DATETIME_HPP_
33 
34 #include "Strings.hpp"
35 
36 #include "../Main/Exception.hpp"
37 
38 #include "../_extern/date/include/date/date.h"
39 
40 #include <algorithm> // std::min, std::transform
41 #include <array> // std::array
42 #include <cctype> // std::isdigit, std::ispunct, std::isspace, std::tolower
43 #include <chrono> // std::chrono::seconds, std::chrono::system_clock
44 #include <clocale> // std::setlocale
45 #include <cmath> // std::lround
46 #include <cstddef> // std::size_t
47 #include <cstdint> // std::uint8_t, std::int64_t, std::uint16_t, std::uint64_t
48 #include <cstdlib> // std::strtoul
49 #include <ctime> // std::tm, std::strftime, std::time_t
50 #include <exception> // std::exception
51 #include <iomanip> // std::get_time
52 #include <locale> // std::locale
53 #include <sstream> // std::istringstream
54 #include <stdexcept> // std::runtime_error
55 #include <string> // std::stoi, std::stol, fstd::string, std::to_string
56 #include <string_view> // std::string_view, std::string_view_literals
57 #include <vector> // std::vector
58 
61 
62  /*
63  * CONSTANTS
64  */
65 
66  using std::string_view_literals::operator""sv;
67 
70 
72  inline constexpr auto longDateTime{"%a, %d %b %Y %T %Z"};
73 
75  inline constexpr auto unixTimeFormat{"UNIX"sv};
76 
78  inline constexpr auto unixTimeFormatPlus{"UNIX+"sv};
79 
81  inline constexpr auto unixTimeFormatMinus{"UNIX-"sv};
82 
84  inline constexpr auto unixTimeFormatXLength{5};
85 
87  inline constexpr auto unixTimeFormatXOffset{4};
88 
90  inline constexpr std::array englishOrdinalSuffixes{
91  "st"sv, "nd"sv, "rd"sv, "th"sv
92  };
93 
95  inline constexpr std::array frenchOrdinalSuffixes{
96  "e"sv, "er"sv
97  };
98 
100  inline constexpr std::array russianOrdinalSuffixes{
101  "-ый"sv, "-го"sv, "-му"sv, "-ми"sv, "-й"sv, "-я"sv, "-е"sv, "-м"sv, "-х"sv
102  };
103 
105  inline constexpr std::array ukrainianOrdinalSuffixes{
106  "-а"sv, "-е"sv, "-і"sv, "-я"sv, "-є"sv
107  };
108 
110  inline constexpr auto sqlTimeStamp{"%F %T"};
111 
113  inline constexpr auto sqlTimeStampLength{19};
114 
116  inline constexpr auto englishLocalePrefix{"en"sv};
117 
119  inline constexpr auto frenchLocalePrefix{"fr"sv};
120 
122  inline constexpr auto russianLocalePrefix{"ru"sv};
123 
125  inline constexpr auto ukrainianLocalePrefix{"uk"sv};
126 
128  inline constexpr auto finnishLocalePrefix{"fi"sv};
129 
131  inline constexpr auto amPmLength{2};
132 
134  inline constexpr auto hourChange{12};
135 
137  inline constexpr auto hourNoonMidnight{12};
138 
140  inline constexpr auto centuryFrom{69};
141 
143  inline constexpr auto yearsPerCentury{100};
144 
146  inline constexpr auto microsecondsPerDay{86400000000};
147 
149  inline constexpr auto millisecondsPerDay{86400000};
150 
152  inline constexpr auto secondsPerDay{86400};
153 
155  inline constexpr auto microsecondsPerHour{3600000000};
156 
158  inline constexpr auto millisecondsPerHour{3600000};
159 
161  inline constexpr auto secondsPerHour{3600};
162 
164  inline constexpr auto microsecondsPerMinute{60000000};
165 
167  inline constexpr auto millisecondsPerMinute{60000};
168 
170  inline constexpr auto secondsPerMinute{60};
171 
173  inline constexpr auto microsecondsPerSecond{1000000};
174 
176  inline constexpr auto millisecondsPerSecond{1000};
177 
179  inline constexpr auto microsecondsPerMillisecond{1000};
180 
182  inline constexpr auto isoDateLength{10};
183 
185  inline constexpr auto yearLength{4};
186 
188  inline constexpr auto isoMonthPos{5};
189 
191  inline constexpr auto isoMonthLength{2};
192 
194  inline constexpr auto minTwoDigitYear{1969};
195 
197  inline constexpr auto base10{10};
198 
200  inline constexpr auto reducedToMonthLength{7};
201 
203  inline constexpr auto daysPerWeek{7};
204 
206 
209  inline constexpr std::uint8_t dateWeeks{0};
210 
212 
215  inline constexpr std::uint8_t dateDays{1};
216 
218 
221  inline constexpr std::uint8_t dateMonths{2};
222 
224 
227  inline constexpr std::uint8_t dateYears{3};
228 
230 
231  /*
232  * DECLARATION
233  */
234 
237 
238  void convertLongDateTimeToSQLTimeStamp(std::string& dateTime);
240  std::string& dateTime,
241  const std::string& customFormat
242  );
244  std::string& dateTime,
245  const std::string& customFormat,
246  const std::string& locale
247  );
248  void convertTimeStampToSQLTimeStamp(std::string& timeStamp);
249  void convertSQLTimeStampToTimeStamp(std::string& timeStamp);
250  void convert12hTo24h(int& hour, bool isPm);
251  [[nodiscard]] std::string getYearAndWeek(const std::string& date);
252  void reduceDate(std::string& date, std::uint8_t resolution);
253 
257 
258  [[nodiscard]] std::string microsecondsToString(std::uint64_t microseconds);
259  [[nodiscard]] std::string millisecondsToString(std::uint64_t milliseconds);
260  [[nodiscard]] std::string secondsToString(std::uint64_t seconds);
261  [[nodiscard]] std::string now();
262 
266 
267  [[nodiscard]] bool isValidISODate(const std::string& isoDate);
268 
272 
273  [[nodiscard]] bool isISODateInRange(std::string_view isoDate, std::string_view rangeFrom, std::string_view rangeTo);
274 
278 
279  [[nodiscard]] std::vector<std::string> getDateGap(
280  const std::string& first,
281  const std::string& second,
282  std::uint8_t resolution
283  );
284  [[nodiscard]] std::vector<std::string> getWeekGap(const std::string& first, const std::string& second);
285  [[nodiscard]] std::vector<std::string> getDayGap(const std::string& first, const std::string& second);
286  [[nodiscard]] std::vector<std::string> getMonthGap(const std::string& first, const std::string& second);
287  [[nodiscard]] std::vector<std::string> getYearGap(const std::string& first, const std::string& second);
288 
292 
293  template<std::size_t N>
294  void removeOrdinals(
295  const std::array<std::string_view, N>& suffixes,
296  std::string& strInOut
297  );
298  template<std::size_t N>
299  void removeOrdinals(
300  std::string_view currentLocale,
301  std::string_view localePrefix,
302  const std::array<std::string_view, N>& suffixes,
303  std::string& strInOut
304  );
305  void fixFrenchMonths(std::string_view locale, std::string& strInOut);
306  void fixRussianMonths(std::string_view locale, std::string& strInOut, std::string& formatInOut);
307  void fixUkrainianMonths(std::string_view locale, std::string& strInOut, std::string& formatInOut);
308  void fixFinnishMonths(std::string_view locale, std::string_view format, std::string& strInOut);
309  void extendSingleDigits(std::string& dateTimeString);
310  void fixYear(std::string& sqlTimeStamp, std::string_view format);
311  void handle12hTime(
312  std::string& formatString,
313  const std::string& dateTimeString,
314  bool& outIsAm,
315  bool& outIsPm
316  );
317 
319 
320  /*
321  * CLASSES FOR DATE/TIME EXCEPTIONS
322  */
323 
325 
331 
333 
338 
339  /*
340  * IMPLEMENTATION
341  */
342 
343  /*
344  * CONVERSION
345  */
346 
348 
361  inline void convertLongDateTimeToSQLTimeStamp(std::string& dateTime) {
363  }
364 
366 
399  std::string& dateTime,
400  const std::string& customFormat
401  ) {
402  // check arguments
403  if(dateTime.empty()) {
404  return;
405  }
406 
407  if(customFormat.empty()) {
408  throw Exception(
409  "DateTime::convertCustomDateTimeToSQLTimeStamp():"
410  " No custom format specified"
411  );
412  }
413 
414  // check for UNIX time format
415  if(
416  customFormat == unixTimeFormat
417  || (
418  customFormat.length() > unixTimeFormatXLength
419  && (
420  customFormat.substr(0, unixTimeFormatXLength) == unixTimeFormatPlus
421  || customFormat.substr(0, unixTimeFormatXLength) == unixTimeFormatMinus
422  )
423  )
424  ) {
425  // get (optional) offset from UNIX time
426  std::int64_t offset{};
427 
428  if(customFormat.length() > unixTimeFormatXLength) {
429  try {
430  offset = std::stol(customFormat.substr(unixTimeFormatXOffset));
431  }
432  catch(const std::exception& e) {
433  throw Exception(
434  "DateTime::convertCustomDateTimeToSQLTimeStamp(): Invalid date/time format - "
435  + customFormat
436  + " [expected: UNIX, UNIX+N or UNIX-N where N is a valid number]"
437  );
438  }
439  }
440 
441  // get UNIX time
442  std::time_t unixTime{};
443 
444  try {
445  if(dateTime.find('.') != std::string::npos) {
446  // handle values with comma as floats (and round them)
447  float f{std::stof(dateTime)};
448 
449  unixTime = static_cast<std::time_t>(std::lround(f));
450  }
451  else {
452  unixTime = static_cast<std::time_t>(std::stol(dateTime));
453  }
454  }
455  catch(const std::exception& e) {
456  throw Exception(
457  "Could not convert '"
458  + dateTime
459  + "' [expected format: '"
460  + customFormat
461  + "'] to date/time"
462  );
463  }
464 
465  // remove (optional) offset
466  unixTime -= offset;
467 
468  // conversion
469  dateTime = date::format(
470  sqlTimeStamp,
471  std::chrono::system_clock::from_time_t(unixTime)
472  );
473  }
474  else {
475  // remove English ordinal endings (st, nd, rd, th)
477 
478  std::istringstream in(dateTime);
479  date::sys_seconds tp;
480 
481  in >> date::parse(customFormat, tp);
482 
483  if(bool(in)) {
484  dateTime = date::format(sqlTimeStamp, tp);
485  }
486  else {
487  // try good old C time
488  std::string formatString{customFormat};
489  bool isAm{false};
490  bool isPm{false};
491  std::tm cTime{};
492 
493  extendSingleDigits(dateTime);
494  handle12hTime(formatString, dateTime, isAm, isPm);
495 
496  std::istringstream inStringStream(dateTime);
497 
498  inStringStream.imbue(std::locale(std::setlocale(LC_ALL, nullptr)));
499 
500  inStringStream >> std::get_time(&cTime, formatString.c_str());
501 
502  if(inStringStream.fail()) {
503  throw Exception(
504  "Could not convert '"
505  + dateTime
506  + "' [expected format: '"
507  + formatString
508  + "'] to date/time"
509  );
510  }
511 
512  if(isAm || isPm) {
513  convert12hTo24h(cTime.tm_hour, isPm);
514  }
515 
516  std::array<char, sqlTimeStampLength + 1> out{};
517 
518  const auto len{
519  std::strftime(out.data(), sqlTimeStampLength + 1, sqlTimeStamp, &cTime)
520  };
521 
522  if(len > 0) {
523  dateTime = std::string(out.data(), len);
524  }
525  else {
526  throw Exception(
527  "Could not convert '"
528  + dateTime
529  + "' [expected format: '"
530  + customFormat
531  + "'] to date/time"
532  );
533  }
534  }
535  }
536 
537  fixYear(dateTime, customFormat);
538  }
539 
541 
572  std::string& dateTime,
573  const std::string& customFormat,
574  const std::string& locale
575  ) {
576  // check arguments
577  if(dateTime.empty()) {
578  return;
579  }
580 
581  if(customFormat.empty()) {
582  throw Exception(
583  "DateTime::convertCustomDateTimeToSQLTimeStamp():"
584  " No custom format specified"
585  );
586  }
587 
588  if(locale.empty()) {
589  convertCustomDateTimeToSQLTimeStamp(dateTime, customFormat);
590 
591  return;
592  }
593 
594  // fix French months ("avr." -> "avril")
595  fixFrenchMonths(locale, dateTime);
596 
597  // fix Russian and Ukrainian months
598  std::string formatString{customFormat};
599  fixRussianMonths(locale, dateTime, formatString);
600  fixUkrainianMonths(locale, dateTime, formatString);
601  fixFinnishMonths(locale, formatString, dateTime);
602 
603  // remove ordinals
608 
609  std::istringstream in(dateTime);
610  date::sys_seconds tp;
611 
612  try {
613  in.imbue(std::locale(locale));
614  }
615  catch(const std::runtime_error& e) {
616  throw LocaleException("Unknown locale '" + locale + "'");
617  }
618 
619  in >> date::parse(formatString, tp);
620 
621  if(bool(in)) {
622  dateTime = date::format(sqlTimeStamp, tp);
623  }
624  else {
625  // try good old C time
626  bool isAm{false};
627  bool isPm{false};
628  std::tm cTime{};
629 
630  extendSingleDigits(dateTime);
631  handle12hTime(formatString, dateTime, isAm, isPm);
632 
633  std::istringstream inStringStream(dateTime);
634 
635  inStringStream.imbue(std::locale(locale));
636 
637  inStringStream >> std::get_time(&cTime, formatString.c_str());
638 
639  if(inStringStream.fail()) {
640  throw Exception(
641  "Could not convert '"
642  + dateTime
643  + "' [expected format: '"
644  + formatString
645  + "'] to date/time"
646  );
647  }
648 
649  if(isAm || isPm) {
650  convert12hTo24h(cTime.tm_hour, isPm);
651  }
652 
653  std::array<char, sqlTimeStampLength + 1> out{};
654 
655  const auto len{
656  std::strftime(out.data(), sqlTimeStampLength + 1, sqlTimeStamp, &cTime)
657  };
658 
659  if(len > 0) {
660  dateTime = std::string(out.data(), len);
661  }
662  else {
663  throw Exception(
664  "Could not convert '"
665  + dateTime
666  + "' [expected format: '"
667  + formatString
668  + "', locale: '"
669  + locale
670  + "'] to date/time"
671  );
672  }
673  }
674 
675  fixYear(dateTime, customFormat);
676  }
677 
679 
693  inline void convertTimeStampToSQLTimeStamp(std::string& timeStamp) {
694  convertCustomDateTimeToSQLTimeStamp(timeStamp, "%Y%m%d%H%M%S");
695  }
696 
698 
712  inline void convertSQLTimeStampToTimeStamp(std::string& timeStamp) {
713  // check argument
714  if(timeStamp.empty()) {
715  return;
716  }
717 
718  std::istringstream in(timeStamp);
719  date::sys_seconds tp;
720 
721  in >> date::parse(sqlTimeStamp, tp);
722 
723  if(!bool(in)) {
724  throw Exception(
725  "Could not convert SQL timestamp '"
726  + timeStamp
727  + "' to date/time"
728  );
729  }
730 
731  timeStamp = date::format("%Y%m%d%H%M%S", tp);
732  }
733 
735 
742  inline void convert12hTo24h(
743  int& hour,
744  bool isPm
745  ) {
746  if(isPm) {
747  if(hour < hourNoonMidnight) { /* not 12 PM [noon] */
748  hour += hourChange;
749  }
750  }
751  else if(hour == hourNoonMidnight) { /* at 12 AM [midnight] */
752  hour = 0;
753  }
754  }
755 
757 
775  inline std::string getYearAndWeek(const std::string& date) {
776  std::istringstream in(date);
777  date::sys_days tp;
778 
779  in >> date::parse("%F", tp);
780 
781  if(!bool(in)) {
782  throw Exception(
783  "Could not convert date '"
784  + date
785  + "' to week number"
786  );
787  }
788 
789  return date::format("%G-#%V", tp);
790  }
791 
793 
807  inline void reduceDate(std::string& date, std::uint8_t resolution) {
808  if(date.empty()) {
809  return;
810  }
811 
812  if(date.length() != isoDateLength) {
813  throw Exception("Invalid length of date " + date);
814  }
815 
816  switch(resolution) {
817  case dateWeeks:
818  // reduce to year and week (YYYY-#WW)
819  date = getYearAndWeek(date);
820 
821  break;
822 
823  case dateDays:
824  // no reduction necessary
825 
826  break;
827 
828  case dateMonths:
829  // reduce to month (YYYY-MM)
830  date = date.substr(0, reducedToMonthLength);
831 
832  break;
833 
834  case dateYears:
835  // reduce to year (YYYY)
836  date = date.substr(0, yearLength);
837 
838  break;
839 
840  default:
841  throw Exception("Invalid date resolution: " + std::to_string(resolution));
842  }
843  }
844 
845  /*
846  * FORMATTING
847  */
848 
850 
859  inline std::string microsecondsToString(std::uint64_t microseconds) {
860  auto rest{microseconds};
861  std::string result;
862 
863  const auto days{
864  rest / microsecondsPerDay
865  };
866 
867  rest -= days * microsecondsPerDay;
868 
869  // narrowing cast as hours are never more than hours per day
870  const auto hours{static_cast<std::uint8_t>(rest / microsecondsPerHour)};
871 
872  rest -= hours * microsecondsPerHour;
873 
874  // narrowing cast as minutes are never more than minutes per hour
875  const auto minutes{static_cast<std::uint8_t>(rest / microsecondsPerMinute)};
876 
877  rest -= minutes * microsecondsPerMinute;
878 
879  // narrowing cast as seconds are never more than seconds per minute
880  const auto seconds{static_cast<std::uint8_t>(rest / microsecondsPerSecond)};
881 
882  rest -= seconds * microsecondsPerSecond;
883 
884  // narrowing cast as milliseconds are never more than milliseconds per second
885  const auto milliseconds{static_cast<std::uint16_t>(rest / microsecondsPerMillisecond)};
886 
887  rest -= milliseconds * microsecondsPerMillisecond;
888 
889  if(days > 0) {
890  result += std::to_string(days) + "d ";
891  }
892 
893  if(hours > 0) {
894  result += std::to_string(hours) + "h ";
895  }
896 
897  if(minutes > 0) {
898  result += std::to_string(minutes) + "min ";
899  }
900 
901  if(seconds > 0) {
902  result += std::to_string(seconds) + "s ";
903  }
904 
905  if(milliseconds > 0) {
906  result += std::to_string(milliseconds) + "ms ";
907  }
908 
909  if(rest > 0) {
910  result += std::to_string(rest) + "μs ";
911  }
912 
913  if(result.empty()) {
914  return "<1μs";
915  }
916 
917  // remove last space
918  result.pop_back();
919 
920  return result;
921  }
922 
924 
933  inline std::string millisecondsToString(std::uint64_t milliseconds) {
934  auto rest{milliseconds};
935  std::string result;
936 
937  const auto days{rest / millisecondsPerDay};
938 
939  rest -= days * millisecondsPerDay;
940 
941  // narrowing cast as hours will never be more than hours per day
942  const auto hours{static_cast<std::uint8_t>(rest / millisecondsPerHour)};
943 
944  rest -= hours * millisecondsPerHour;
945 
946  // narrowing cast as minutes will never be more than minutes per hour
947  const auto minutes{static_cast<std::uint8_t>(rest / millisecondsPerMinute)};
948 
949  rest -= minutes * millisecondsPerMinute;
950 
951  // narrowing cast as seconds will never be more than seconds per minute
952  const auto seconds{static_cast<std::uint8_t>(rest / millisecondsPerSecond)};
953 
954  rest -= seconds * millisecondsPerSecond;
955 
956  if(days > 0) {
957  result += std::to_string(days) + "d ";
958  }
959 
960  if(hours > 0) {
961  result += std::to_string(hours) + "h ";
962  }
963 
964  if(minutes > 0) {
965  result += std::to_string(minutes) + "min ";
966  }
967 
968  if(seconds > 0) {
969  result += std::to_string(seconds) + "s ";
970  }
971 
972  if(rest > 0) {
973  result += std::to_string(rest) + "ms ";
974  }
975 
976  if(result.empty()) {
977  return "<1ms";
978  }
979 
980  // remove last space
981  result.pop_back();
982 
983  return result;
984  }
985 
987 
995  inline std::string secondsToString(std::uint64_t seconds) {
996  auto rest{seconds};
997  std::string result;
998 
999  const auto days{rest / secondsPerDay};
1000 
1001  rest -= days * secondsPerDay;
1002 
1003  // narrowing cast as hours are never more than hours per day
1004  const auto hours{static_cast<std::uint8_t>(rest / secondsPerHour)};
1005 
1006  rest -= hours * secondsPerHour;
1007 
1008  // narrowing cast as minutes are never more than minutes per hour
1009  const auto minutes{static_cast<std::uint8_t>(rest / secondsPerMinute)};
1010 
1011  rest -= minutes * secondsPerMinute;
1012 
1013  if(days > 0) {
1014  result += std::to_string(days) + "d ";
1015  }
1016 
1017  if(hours > 0) {
1018  result += std::to_string(hours) + "h ";
1019  }
1020 
1021  if(minutes > 0) {
1022  result += std::to_string(minutes) + "min ";
1023  }
1024 
1025  if(rest > 0) {
1026  result += std::to_string(rest) + "s ";
1027  }
1028 
1029  if(result.empty()) {
1030  return "<1s";
1031  }
1032 
1033  // remove last space
1034  result.pop_back();
1035 
1036  return result;
1037  }
1038 
1040 
1045  inline std::string now() {
1046  return date::format(
1047  sqlTimeStamp,
1048  date::floor<std::chrono::seconds>(
1050  )
1051  );
1052  }
1053 
1054  /*
1055  * VERIFICATION
1056  */
1057 
1059 
1071  inline bool isValidISODate(const std::string& isoDate) {
1072  std::istringstream in(isoDate);
1073  date::sys_days tp;
1074 
1075  in >> date::parse("%F", tp);
1076 
1077  return bool(in);
1078  }
1079 
1080  /*
1081  * COMPARISON
1082  */
1083 
1085 
1105  inline bool isISODateInRange(
1106  std::string_view isoDate,
1107  std::string_view rangeFrom,
1108  std::string_view rangeTo
1109  ) {
1110  if(isoDate.length() < isoDateLength) {
1111  return false;
1112  }
1113 
1114  if(rangeFrom.length() < isoDateLength && rangeTo.length() < isoDateLength) {
1115  return true;
1116  }
1117 
1118  if(rangeTo.length() < isoDateLength) {
1119  return isoDate.substr(0, isoDateLength) >= rangeFrom.substr(0, isoDateLength);
1120  }
1121 
1122  if(rangeFrom.length() < isoDateLength) {
1123  return isoDate.substr(0, isoDateLength) <= rangeTo.substr(0, isoDateLength);
1124  }
1125 
1126  return isoDate.substr(0, isoDateLength) >= rangeFrom.substr(0, isoDateLength)
1127  && isoDate <= rangeTo.substr(0, isoDateLength);
1128  }
1129 
1130  /*
1131  * GAPS INBETWEEN DATES
1132  */
1133 
1135 
1154  inline std::vector<std::string> getDateGap(
1155  const std::string& first,
1156  const std::string& second,
1157  std::uint8_t resolution
1158  ) {
1159  // make sure that first date lies before second date
1160  switch(resolution) {
1161  case dateWeeks:
1162  // dates given as weeks (YYYY-#WW)
1163  return getWeekGap(first, second);
1164 
1165  case dateDays:
1166  // dates given as days, i.e. in ISO format (YYYY-MM-DD)
1167  return getDayGap(first, second);
1168 
1169  case dateMonths:
1170  // dates given as month (YYYY-MM)
1171  return getMonthGap(first, second);
1172 
1173  case dateYears:
1174  // dates given as year (YYYY)
1175  return getYearGap(first, second);
1176 
1177  default:
1178  throw Exception("Invalid date resolution: " + std::to_string(resolution));
1179  }
1180  }
1181 
1183 
1207  inline std::vector<std::string> getWeekGap(const std::string& first, const std::string& second) {
1208  // make sure the first month lies before the second week
1209  if(first > second) {
1210  return getWeekGap(second, first);
1211  }
1212 
1213  // convert strings to time points
1214  std::istringstream inFirst(first + "-1"); /* use Monday */
1215  date::sys_days tpFirst;
1216 
1217  inFirst >> date::parse("%G-#%V-%u", tpFirst);
1218 
1219  if(!bool(inFirst)) {
1220  throw Exception("Invalid week: '" + first + "' (expected: YYYY-#WW)");
1221  }
1222 
1223  std::istringstream inSecond(second + "-1"); /* use Monday */
1224  date::sys_days tpSecond;
1225 
1226  inSecond >> date::parse("%G-#%V-%u", tpSecond);
1227 
1228  if(!bool(inSecond)) {
1229  throw Exception("Invalid week: '" + second + "' (expected: YYYY-#WW)");
1230  }
1231 
1232  // get distance between time points and reserve memory
1233  std::vector<std::string> result;
1234  const auto distance{static_cast<std::size_t>((tpSecond - tpFirst).count()) / 7};
1235 
1236  if(distance > 1) {
1237  result.reserve(distance - 1);
1238  }
1239 
1240  // get and return result
1241  for(std::size_t week{1}; week < distance; ++week) {
1242  tpFirst += date::days{daysPerWeek};
1243 
1244  result.emplace_back(getYearAndWeek(date::format("%F", tpFirst)));
1245  }
1246 
1247  return result;
1248  }
1249 
1251 
1271  inline std::vector<std::string> getDayGap(const std::string& first, const std::string& second) {
1272  // make sure the first date lies before the second date
1273  if(first > second) {
1274  return getDayGap(second, first);
1275  }
1276 
1277  // convert strings to time points
1278  std::istringstream inFirst(first);
1279  date::sys_days tpFirst;
1280 
1281  inFirst >> date::parse("%F", tpFirst);
1282 
1283  if(!bool(inFirst)) {
1284  throw Exception("Invalid date: '" + first + "' (expected: YYYY-MM-DD)");
1285  }
1286 
1287  std::istringstream inSecond(second);
1288  date::sys_days tpSecond;
1289 
1290  inSecond >> date::parse("%F", tpSecond);
1291 
1292  if(!bool(inSecond)) {
1293  throw Exception("Invalid date: '" + second + "' (expected: YYYY-MM-DD)");
1294  }
1295 
1296  // get distance between time points and reserve memory
1297  std::vector<std::string> result;
1298  const auto distance{static_cast<std::size_t>((tpSecond - tpFirst).count())};
1299 
1300  if(distance > 1) {
1301  result.reserve(distance - 1);
1302  }
1303 
1304  // get and return result
1305  for(std::size_t day{1}; day < distance; ++day) {
1306  tpFirst += date::days{1};
1307 
1308  result.emplace_back(date::format("%F", tpFirst));
1309  }
1310 
1311  return result;
1312  }
1313 
1315 
1335  inline std::vector<std::string> getMonthGap(const std::string& first, const std::string& second) {
1336  // make sure the first month lies before the second month
1337  if(first > second) {
1338  return getMonthGap(second, first);
1339  }
1340 
1341  // convert strings to time points
1342  std::istringstream inFirst(first);
1343  date::year_month tpFirst;
1344 
1345  inFirst >> date::parse("%Y-%m", tpFirst);
1346 
1347  if(!bool(inFirst)) {
1348  throw Exception("Invalid month: '" + first + "' (expected: YYYY-MM)");
1349  }
1350 
1351  std::istringstream inSecond(second);
1352  date::year_month tpSecond;
1353 
1354  inSecond >> date::parse("%Y-%m", tpSecond);
1355 
1356  if(!bool(inSecond)) {
1357  throw Exception("Invalid month: '" + second + "' (expected: YYYY-MM)");
1358  }
1359 
1360  // get distance between time points and reserve memory
1361  std::vector<std::string> result;
1362  const auto distance{static_cast<std::size_t>((tpSecond - tpFirst).count())};
1363 
1364  if(distance > 1) {
1365  result.reserve(distance - 1);
1366  }
1367 
1368  // get and return result
1369  for(std::size_t month{1}; month < distance; ++month) {
1370  tpFirst += date::months{1};
1371 
1372  result.emplace_back(date::format("%Y-%m", tpFirst));
1373  }
1374 
1375  return result;
1376  }
1377 
1379 
1399  inline std::vector<std::string> getYearGap(const std::string& first, const std::string& second) {
1400  // make sure the first year lies before the second month
1401  if(first > second) {
1402  return getYearGap(second, first);
1403  }
1404 
1405  // convert strings to time points
1406  std::istringstream inFirst(first);
1407  date::year tpFirst;
1408 
1409  inFirst >> date::parse("%Y", tpFirst);
1410 
1411  if(!bool(inFirst)) {
1412  throw Exception("Invalid year: '" + first + "' (expected: YYYY)");
1413  }
1414 
1415  std::istringstream inSecond(second);
1416  date::year tpSecond;
1417 
1418  inSecond >> date::parse("%Y", tpSecond);
1419 
1420  if(!bool(inSecond)) {
1421  throw Exception("Invalid year: '" + second + "' (expected: YYYY)");
1422  }
1423 
1424  // get distance between time points and reserve memory
1425  std::vector<std::string> result;
1426  const auto distance{static_cast<std::size_t>((tpSecond - tpFirst).count())};
1427 
1428  if(distance > 1) {
1429  result.reserve(distance - 1);
1430  }
1431 
1432  // get and return result
1433  for(std::size_t year{1}; year < distance; ++year) {
1434  tpFirst += date::years{1};
1435 
1436  result.emplace_back(date::format("%Y", tpFirst));
1437  }
1438 
1439  return result;
1440  }
1441 
1442  /*
1443  * HELPERS
1444  */
1445 
1447 
1455  template<std::size_t N> void removeOrdinals(
1456  const std::array<std::string_view, N>& suffixes,
1457  std::string& strInOut
1458  ) {
1459  std::size_t pos{};
1460 
1461  while(pos < strInOut.length()) {
1462  auto next{std::string::npos};
1463  std::size_t len{};
1464 
1465  for(const auto& suffix : suffixes) {
1466  const auto search{strInOut.find(suffix, pos)};
1467 
1468  if(search < next) {
1469  next = search;
1470  len = suffix.length();
1471  }
1472  }
1473 
1474  pos = next;
1475 
1476  if(pos == std::string::npos) {
1477  break;
1478  }
1479 
1480  if(pos > 0) {
1481  if(std::isdigit(strInOut.at(pos - 1)) != 0) {
1482  const auto end{pos + len};
1483 
1484  if(
1485  end == strInOut.length()
1486  || std::isspace(strInOut.at(end)) != 0
1487  || std::ispunct(strInOut.at(end)) != 0
1488  ) {
1489  // remove st, nd, rd or th
1490  strInOut.erase(pos, len);
1491 
1492  // skip whitespace/punctuation afterwards
1493  ++pos;
1494  }
1495  else {
1496  pos += len + 1;
1497  }
1498  }
1499  else {
1500  pos += len + 1;
1501  }
1502  }
1503  else {
1504  pos += len + 1;
1505  }
1506  }
1507  }
1508 
1510 
1527  template<std::size_t N> void removeOrdinals(
1528  std::string_view currentLocale,
1529  std::string_view localePrefix,
1530  const std::array<std::string_view, N>& suffixes,
1531  std::string& strInOut
1532  ) {
1533  if(currentLocale.length() >= localePrefix.length()) {
1534  std::string prefix(currentLocale, 0, localePrefix.length());
1535 
1536  std::transform(
1537  prefix.begin(),
1538  prefix.end(),
1539  prefix.begin(),
1540  [](const auto c) {
1541  return std::tolower(c);
1542  }
1543  );
1544 
1545  if(prefix == localePrefix) {
1546  removeOrdinals<N>(suffixes, strInOut);
1547  }
1548  }
1549  }
1550 
1552 
1563  inline void fixFrenchMonths(std::string_view locale, std::string& strInOut) {
1564  if(locale.length() >= frenchLocalePrefix.length()) {
1565  std::string prefix(locale, 0, frenchLocalePrefix.length());
1566 
1567  std::transform(
1568  prefix.begin(),
1569  prefix.end(),
1570  prefix.begin(),
1571  [](const auto c) {
1572  return std::tolower(c);
1573  }
1574  );
1575 
1576  if(prefix == frenchLocalePrefix) {
1577  Helper::Strings::replaceAll(strInOut, "avr.", "avril");
1578  }
1579  }
1580  }
1581 
1583 
1602  inline void fixRussianMonths(std::string_view locale, std::string& strInOut, std::string& formatInOut) {
1603  if(locale.length() >= russianLocalePrefix.length()) {
1604  std::string prefix(locale, 0, russianLocalePrefix.length());
1605 
1606  std::transform(
1607  prefix.begin(),
1608  prefix.end(),
1609  prefix.begin(),
1610  [](const auto c) {
1611  return std::tolower(c);
1612  }
1613  );
1614 
1615  if(prefix == russianLocalePrefix) {
1616  std::string oldString;
1617 
1618  const bool bigB{
1619  formatInOut.find("%B") != std::string::npos
1620  };
1621 
1622  if(bigB) {
1623  oldString = strInOut;
1624  }
1625 
1626  Helper::Strings::replaceAll(strInOut, "январь", "янв");
1627  Helper::Strings::replaceAll(strInOut, "Январь", "янв");
1628  Helper::Strings::replaceAll(strInOut, "ЯНВАРЬ", "янв");
1629 
1630  if(!bigB) {
1631  Helper::Strings::replaceAll(strInOut, "Янв", "янв");
1632  Helper::Strings::replaceAll(strInOut, "ЯНВ", "янв");
1633  }
1634 
1635  Helper::Strings::replaceAll(strInOut, "февраль", "фев");
1636  Helper::Strings::replaceAll(strInOut, "Февраль", "фев");
1637  Helper::Strings::replaceAll(strInOut, "ФЕВРАЛЬ", "фев");
1638 
1639  if(!bigB) {
1640  Helper::Strings::replaceAll(strInOut, "Фев", "фев");
1641  Helper::Strings::replaceAll(strInOut, "ФЕВ", "фев");
1642  }
1643 
1644  Helper::Strings::replaceAll(strInOut, "марта", "мар");
1645  Helper::Strings::replaceAll(strInOut, "Марта", "мар");
1646  Helper::Strings::replaceAll(strInOut, "МАРТА", "мар");
1647  Helper::Strings::replaceAll(strInOut, "март", "мар");
1648  Helper::Strings::replaceAll(strInOut, "Март", "мар");
1649  Helper::Strings::replaceAll(strInOut, "МАРТ", "мар");
1650 
1651  if(!bigB) {
1652  Helper::Strings::replaceAll(strInOut, "Мар", "мар");
1653  Helper::Strings::replaceAll(strInOut, "МАР", "мар");
1654  }
1655 
1656  Helper::Strings::replaceAll(strInOut, "апрель", "апр");
1657  Helper::Strings::replaceAll(strInOut, "Апрель", "апр");
1658  Helper::Strings::replaceAll(strInOut, "АПРЕЛЬ", "апр");
1659 
1660  if(!bigB) {
1661  Helper::Strings::replaceAll(strInOut, "Апр", "апр");
1662  Helper::Strings::replaceAll(strInOut, "АПР", "апр");
1663  }
1664 
1665  Helper::Strings::replaceAll(strInOut, "май", "мая");
1666  Helper::Strings::replaceAll(strInOut, "Май", "мая");
1667  Helper::Strings::replaceAll(strInOut, "МАЙ", "мая");
1668 
1669  if(!bigB) {
1670  Helper::Strings::replaceAll(strInOut, "Мая", "мая");
1671  Helper::Strings::replaceAll(strInOut, "МАЯ", "мая");
1672  }
1673 
1674  Helper::Strings::replaceAll(strInOut, "июнь", "июн");
1675  Helper::Strings::replaceAll(strInOut, "Июнь", "июн");
1676  Helper::Strings::replaceAll(strInOut, "ИЮНь", "июн");
1677 
1678  if(!bigB) {
1679  Helper::Strings::replaceAll(strInOut, "Июн", "июн");
1680  Helper::Strings::replaceAll(strInOut, "ИЮН", "июн");
1681  }
1682 
1683  Helper::Strings::replaceAll(strInOut, "июль", "июл");
1684  Helper::Strings::replaceAll(strInOut, "Июль", "июл");
1685  Helper::Strings::replaceAll(strInOut, "ИЮЛь", "июл");
1686 
1687  if(!bigB) {
1688  Helper::Strings::replaceAll(strInOut, "Июл", "июл");
1689  Helper::Strings::replaceAll(strInOut, "ИЮЛ", "июл");
1690  }
1691 
1692  Helper::Strings::replaceAll(strInOut, "августа", "авг");
1693  Helper::Strings::replaceAll(strInOut, "Августа", "авг");
1694  Helper::Strings::replaceAll(strInOut, "АВГУСТА", "авг");
1695  Helper::Strings::replaceAll(strInOut, "август", "авг");
1696  Helper::Strings::replaceAll(strInOut, "Август", "авг");
1697  Helper::Strings::replaceAll(strInOut, "АВГУСТ", "авг");
1698 
1699  if(!bigB) {
1700  Helper::Strings::replaceAll(strInOut, "Авг", "авг");
1701  Helper::Strings::replaceAll(strInOut, "АВГ", "авг");
1702  }
1703 
1704  Helper::Strings::replaceAll(strInOut, "сентябрь", "сен");
1705  Helper::Strings::replaceAll(strInOut, "Сентябрь", "сен");
1706  Helper::Strings::replaceAll(strInOut, "СЕНТЯБРЬ", "сен");
1707  Helper::Strings::replaceAll(strInOut, "сентября", "сен");
1708  Helper::Strings::replaceAll(strInOut, "Сентября", "сен");
1709  Helper::Strings::replaceAll(strInOut, "СЕНТЯБРя", "сен");
1710  Helper::Strings::replaceAll(strInOut, "Сен", "сен");
1711  Helper::Strings::replaceAll(strInOut, "СЕН", "сен");
1712  Helper::Strings::replaceAll(strInOut, "сент", "сен");
1713  Helper::Strings::replaceAll(strInOut, "сенТ", "сен");
1714 
1715  Helper::Strings::replaceAll(strInOut, "октябрь", "окт");
1716  Helper::Strings::replaceAll(strInOut, "Октябрь", "окт");
1717  Helper::Strings::replaceAll(strInOut, "ОКТЯБРЬ", "окт");
1718 
1719  if(!bigB) {
1720  Helper::Strings::replaceAll(strInOut, "Окт", "окт");
1721  Helper::Strings::replaceAll(strInOut, "ОКТ", "окт");
1722  }
1723 
1724  Helper::Strings::replaceAll(strInOut, "ноябрь", "ноя");
1725  Helper::Strings::replaceAll(strInOut, "Ноябрь", "ноя");
1726  Helper::Strings::replaceAll(strInOut, "НОЯБРЬ", "ноя");
1727 
1728  if(!bigB) {
1729  Helper::Strings::replaceAll(strInOut, "Ноя", "ноя");
1730  Helper::Strings::replaceAll(strInOut, "НОЯ", "ноя");
1731  }
1732 
1733  Helper::Strings::replaceAll(strInOut, "декабрь", "дек");
1734  Helper::Strings::replaceAll(strInOut, "Декабрь", "дек");
1735  Helper::Strings::replaceAll(strInOut, "ДЕКАБРЬ", "дек");
1736 
1737  if(!bigB) {
1738  Helper::Strings::replaceAll(strInOut, "Дек", "дек");
1739  Helper::Strings::replaceAll(strInOut, "ДЕК", "дек");
1740  }
1741 
1742  if(bigB && strInOut != oldString) {
1743  Helper::Strings::replaceAll(formatInOut, "%B", "%b");
1744  }
1745 
1746  return;
1747  }
1748  }
1749 
1750  // if the locale is English, replace the Russified "maj"/"Maj"/"MAJ" with English "May"
1751  if(
1752  formatInOut.find("%b") != std::string::npos
1753  || formatInOut.find("%B") != std::string::npos
1754  ) {
1755  if(locale.length() >= englishLocalePrefix.length()) {
1756  std::string prefix(locale, 0, russianLocalePrefix.length());
1757 
1758  std::transform(
1759  prefix.begin(),
1760  prefix.end(),
1761  prefix.begin(),
1762  [](const auto c) {
1763  return std::tolower(c);
1764  }
1765  );
1766 
1767  if(prefix == englishLocalePrefix) {
1768  Helper::Strings::replaceAll(strInOut, "maj", "May");
1769  Helper::Strings::replaceAll(strInOut, "Maj", "May");
1770  Helper::Strings::replaceAll(strInOut, "MAJ", "May");
1771  }
1772  }
1773  }
1774  }
1775 
1777 
1790  inline void fixUkrainianMonths(std::string_view locale, std::string& strInOut, std::string& formatInOut) {
1791  if(locale.length() >= ukrainianLocalePrefix.length()) {
1792  std::string prefix(locale, 0, ukrainianLocalePrefix.length());
1793 
1794  std::transform(
1795  prefix.begin(),
1796  prefix.end(),
1797  prefix.begin(),
1798  [](const auto c) {
1799  return std::tolower(c);
1800  }
1801  );
1802 
1803  if(prefix == ukrainianLocalePrefix) {
1804  const bool bigB{
1805  formatInOut.find("%B") != std::string::npos
1806  };
1807  std::string oldString;
1808 
1809  if(bigB) {
1810  oldString = strInOut;
1811  }
1812 
1813  Helper::Strings::replaceAll(strInOut, "січень", "січ");
1814  Helper::Strings::replaceAll(strInOut, "Січень", "січ");
1815  Helper::Strings::replaceAll(strInOut, "СІЧЕНЬ", "січ");
1816 
1817  if(!bigB) {
1818  Helper::Strings::replaceAll(strInOut, "Січ", "січ");
1819  Helper::Strings::replaceAll(strInOut, "СІЧ", "січ");
1820  Helper::Strings::replaceAll(strInOut, "стд", "січ");
1821  Helper::Strings::replaceAll(strInOut, "Стд", "січ");
1822  Helper::Strings::replaceAll(strInOut, "СТД", "січ");
1823  }
1824 
1825  Helper::Strings::replaceAll(strInOut, "лютий", "лют");
1826  Helper::Strings::replaceAll(strInOut, "Лютий", "лют");
1827  Helper::Strings::replaceAll(strInOut, "ЛЮТИЙ", "лют");
1828 
1829  if(!bigB) {
1830  Helper::Strings::replaceAll(strInOut, "Лют", "лют");
1831  Helper::Strings::replaceAll(strInOut, "ЛЮТ", "лют");
1832  }
1833 
1834  Helper::Strings::replaceAll(strInOut, "березень", "бер");
1835  Helper::Strings::replaceAll(strInOut, "Березень", "бер");
1836  Helper::Strings::replaceAll(strInOut, "БЕРЕЗЕНЬ", "бер");
1837 
1838  if(!bigB) {
1839  Helper::Strings::replaceAll(strInOut, "Бер", "бер");
1840  Helper::Strings::replaceAll(strInOut, "БЕР", "бер");
1841  }
1842 
1843  Helper::Strings::replaceAll(strInOut, "квітень", "кві");
1844  Helper::Strings::replaceAll(strInOut, "Квітень", "кві");
1845  Helper::Strings::replaceAll(strInOut, "КВІТЕНЬ", "кві");
1846 
1847  if(!bigB) {
1848  Helper::Strings::replaceAll(strInOut, "крс", "кві");
1849  Helper::Strings::replaceAll(strInOut, "Крс", "кві");
1850  Helper::Strings::replaceAll(strInOut, "КРС", "кві");
1851  }
1852 
1853  Helper::Strings::replaceAll(strInOut, "травень", "тра");
1854  Helper::Strings::replaceAll(strInOut, "Травень", "тра");
1855  Helper::Strings::replaceAll(strInOut, "ТРАВЕНЬ", "тра");
1856 
1857  if(!bigB) {
1858  Helper::Strings::replaceAll(strInOut, "Тра", "тра");
1859  Helper::Strings::replaceAll(strInOut, "ТРА", "тра");
1860  }
1861 
1862  Helper::Strings::replaceAll(strInOut, "червень", "чер");
1863  Helper::Strings::replaceAll(strInOut, "Червень", "чер");
1864  Helper::Strings::replaceAll(strInOut, "ЧЕРВЕНЬ", "чер");
1865 
1866  if(!bigB) {
1867  Helper::Strings::replaceAll(strInOut, "Чер", "чер");
1868  Helper::Strings::replaceAll(strInOut, "ЧЕР", "чер");
1869  }
1870 
1871  Helper::Strings::replaceAll(strInOut, "липень", "лип");
1872  Helper::Strings::replaceAll(strInOut, "Липень", "лип");
1873  Helper::Strings::replaceAll(strInOut, "ЛИПЕНЬ", "лип");
1874 
1875  if(!bigB) {
1876  Helper::Strings::replaceAll(strInOut, "Лип", "лип");
1877  Helper::Strings::replaceAll(strInOut, "ЛИП", "лип");
1878  }
1879 
1880  Helper::Strings::replaceAll(strInOut, "серпень", "сер");
1881  Helper::Strings::replaceAll(strInOut, "Серпень", "сер");
1882  Helper::Strings::replaceAll(strInOut, "СЕРПЕНЬ", "сер");
1883 
1884  if(!bigB) {
1885  Helper::Strings::replaceAll(strInOut, "Сер", "сер");
1886  Helper::Strings::replaceAll(strInOut, "СЕР", "сер");
1887  }
1888 
1889  Helper::Strings::replaceAll(strInOut, "вересень", "вер");
1890  Helper::Strings::replaceAll(strInOut, "Вересень", "вер");
1891  Helper::Strings::replaceAll(strInOut, "ВЕРЕСЕНЬ", "вер");
1892 
1893  if(!bigB) {
1894  Helper::Strings::replaceAll(strInOut, "Вер", "вер");
1895  Helper::Strings::replaceAll(strInOut, "ВЕР", "вер");
1896  Helper::Strings::replaceAll(strInOut, "врс", "вер");
1897  Helper::Strings::replaceAll(strInOut, "Врс", "вер");
1898  Helper::Strings::replaceAll(strInOut, "ВРС", "вер");
1899  }
1900 
1901  Helper::Strings::replaceAll(strInOut, "жовтень", "жов");
1902  Helper::Strings::replaceAll(strInOut, "Жовтень", "жов");
1903  Helper::Strings::replaceAll(strInOut, "ЖОВТЕНЬ", "жов");
1904 
1905  if(!bigB) {
1906  Helper::Strings::replaceAll(strInOut, "Жов", "жов");
1907  Helper::Strings::replaceAll(strInOut, "ЖОВ", "жов");
1908  Helper::Strings::replaceAll(strInOut, "жнв", "жов");
1909  Helper::Strings::replaceAll(strInOut, "Жнв", "жов");
1910  Helper::Strings::replaceAll(strInOut, "ЖНВ", "жов");
1911  }
1912 
1913  Helper::Strings::replaceAll(strInOut, "листопада", "лис");
1914  Helper::Strings::replaceAll(strInOut, "Листопада", "лис");
1915  Helper::Strings::replaceAll(strInOut, "ЛИСТОПАДА", "лис");
1916  Helper::Strings::replaceAll(strInOut, "листопад", "лис");
1917  Helper::Strings::replaceAll(strInOut, "Листопад", "лис");
1918  Helper::Strings::replaceAll(strInOut, "ЛИСТОПАД", "лис");
1919 
1920  if(!bigB) {
1921  Helper::Strings::replaceAll(strInOut, "Лис", "лис");
1922  Helper::Strings::replaceAll(strInOut, "ЛИС", "лис");
1923  }
1924 
1925  Helper::Strings::replaceAll(strInOut, "грудень", "гру");
1926  Helper::Strings::replaceAll(strInOut, "Грудень", "гру");
1927  Helper::Strings::replaceAll(strInOut, "ГРУДЕНЬ", "гру");
1928 
1929  if(!bigB) {
1930  Helper::Strings::replaceAll(strInOut, "Гру", "гру");
1931  Helper::Strings::replaceAll(strInOut, "ГРУ", "гру");
1932  }
1933 
1934  if(bigB && strInOut != oldString) {
1935  Helper::Strings::replaceAll(formatInOut, "%B", "%b");
1936  }
1937  }
1938  }
1939  }
1940 
1942 
1959  inline void fixFinnishMonths(std::string_view locale, std::string_view format, std::string& strInOut) {
1960  if(
1961  format.find("%b") != std::string::npos
1962  && locale.length() >= finnishLocalePrefix.length()
1963  ) {
1964  std::string prefix(locale, 0, finnishLocalePrefix.length());
1965 
1966  std::transform(
1967  prefix.begin(),
1968  prefix.end(),
1969  prefix.begin(),
1970  [](const auto c) {
1971  return std::tolower(c);
1972  }
1973  );
1974 
1975  if(prefix == finnishLocalePrefix) {
1976  Helper::Strings::replaceAll(strInOut, "tammik", "tammi");
1977  Helper::Strings::replaceAll(strInOut, "Tammik", "tammi");
1978  Helper::Strings::replaceAll(strInOut, "TAMMIK", "tammi");
1979 
1980  Helper::Strings::replaceAll(strInOut, "helmik", "helmi");
1981  Helper::Strings::replaceAll(strInOut, "Helmik", "helmi");
1982  Helper::Strings::replaceAll(strInOut, "HELMIK", "helmi");
1983 
1984  Helper::Strings::replaceAll(strInOut, "maalisk", "maalis");
1985  Helper::Strings::replaceAll(strInOut, "Maalisk", "maalis");
1986  Helper::Strings::replaceAll(strInOut, "MAALISK", "maalis");
1987 
1988  Helper::Strings::replaceAll(strInOut, "huhtik", "huhti");
1989  Helper::Strings::replaceAll(strInOut, "Huhtik", "huhti");
1990  Helper::Strings::replaceAll(strInOut, "HUHTIK", "huhti");
1991 
1992  Helper::Strings::replaceAll(strInOut, "toukok", "touko");
1993  Helper::Strings::replaceAll(strInOut, "Toukok", "touko");
1994  Helper::Strings::replaceAll(strInOut, "TOUKOK", "touko");
1995 
1996  Helper::Strings::replaceAll(strInOut, "kesäk", "kesä");
1997  Helper::Strings::replaceAll(strInOut, "Kesäk", "kesä");
1998  Helper::Strings::replaceAll(strInOut, "KESÄK", "kesä");
1999 
2000  Helper::Strings::replaceAll(strInOut, "heinäk", "heinä");
2001  Helper::Strings::replaceAll(strInOut, "Heinäk", "heinä");
2002  Helper::Strings::replaceAll(strInOut, "HEINÄK", "heinä");
2003 
2004  Helper::Strings::replaceAll(strInOut, "elok", "elo");
2005  Helper::Strings::replaceAll(strInOut, "Elok", "elo");
2006  Helper::Strings::replaceAll(strInOut, "ELOK", "elo");
2007 
2008  Helper::Strings::replaceAll(strInOut, "syysk", "syys");
2009  Helper::Strings::replaceAll(strInOut, "Syysk", "syys");
2010  Helper::Strings::replaceAll(strInOut, "SYYSK", "syys");
2011 
2012  Helper::Strings::replaceAll(strInOut, "marrask", "marras");
2013  Helper::Strings::replaceAll(strInOut, "Marrask", "marras");
2014  Helper::Strings::replaceAll(strInOut, "MARRASK", "marras");
2015 
2016  Helper::Strings::replaceAll(strInOut, "jouluk", "joulu");
2017  Helper::Strings::replaceAll(strInOut, "Jouluk", "joulu");
2018  Helper::Strings::replaceAll(strInOut, "JOULUK", "joulu");
2019  }
2020  }
2021  }
2022 
2024 
2028  inline void extendSingleDigits(std::string& dateTimeString) {
2029  std::size_t pos{};
2030 
2031  while(pos < dateTimeString.length()) {
2032  pos = dateTimeString.find_first_of("123456789", pos);
2033 
2034  if(pos == std::string::npos) {
2035  break;
2036  }
2037 
2038  if(
2039  (
2040  pos == 0
2041  || std::isdigit(dateTimeString[pos - 1]) == 0
2042  ) &&
2043  (
2044  pos == dateTimeString.length() - 1
2045  || std::isdigit(dateTimeString[pos + 1]) == 0
2046  )
2047  ) {
2048  // extend digit by adding leading zero
2049  dateTimeString.insert(pos, 1, '0');
2050 
2051  ++pos;
2052  }
2053 
2054  ++pos;
2055  }
2056  }
2057 
2059 
2067  inline void fixYear(std::string& sqlTimeStamp, std::string_view format) {
2068  if(
2069  format.find("%y") != std::string_view::npos
2070  && sqlTimeStamp.length() > yearLength
2071  && std::strtoul(
2072  sqlTimeStamp.substr(0, yearLength).c_str(),
2073  nullptr,
2074  base10
2075  ) < minTwoDigitYear
2076  ) {
2077  sqlTimeStamp[0] = '2';
2078  sqlTimeStamp[1] = '0';
2079  }
2080  }
2081 
2083 
2114  inline void handle12hTime(
2115  std::string& formatString,
2116  const std::string& dateTimeString,
2117  bool& outIsAm,
2118  bool& outIsPm
2119  ) {
2120  const auto formatPos{formatString.find("%p")};
2121 
2122  if(formatPos == std::string::npos) {
2123  return;
2124  }
2125 
2126  std::size_t pos{};
2127 
2128  while(pos < dateTimeString.length()) {
2129  auto newPos{pos};
2130  auto amPos1{dateTimeString.find("am", pos)};
2131  auto amPos2{dateTimeString.find("AM", pos)};
2132  auto amPos{std::min(amPos1, amPos2)};
2133 
2134  if(amPos != std::string::npos) {
2135  newPos = amPos;
2136 
2137  if(
2138  (
2139  amPos == 0
2140  || std::isspace(dateTimeString[amPos - 1]) != 0
2141  || std::ispunct(dateTimeString[amPos - 1]) != 0
2142  || std::isdigit(dateTimeString[amPos - 1]) != 0
2143  ) && (
2144  amPos == dateTimeString.length() - amPmLength
2145  || std::isspace(dateTimeString[amPos + amPmLength]) != 0
2146  || std::ispunct(dateTimeString[amPos + amPmLength]) != 0
2147  || std::isdigit(dateTimeString[amPos + amPmLength]) != 0
2148  )
2149  ) {
2150  // found am/AM -> replace it in format string
2152  formatString,
2153  "%p",
2154  dateTimeString.substr(amPos, amPmLength)
2155  );
2156 
2157  outIsAm = true;
2158 
2159  return;
2160  }
2161  }
2162 
2163  auto pmPos1{dateTimeString.find("pm", pos)};
2164  auto pmPos2{dateTimeString.find("PM", pos)};
2165  auto pmPos{std::min(pmPos1, pmPos2)};
2166 
2167  if(pmPos != std::string::npos) {
2168  if(pmPos > newPos) {
2169  newPos = pmPos;
2170  }
2171 
2172  if(
2173  (
2174  pmPos == 0
2175  || std::isspace(dateTimeString[pmPos - 1]) != 0
2176  || std::ispunct(dateTimeString[pmPos - 1]) != 0
2177  || std::isdigit(dateTimeString[pmPos - 1]) != 0
2178  ) && (
2179  pmPos == dateTimeString.length() - amPmLength
2180  || std::isspace(dateTimeString[pmPos + amPmLength]) != 0
2181  || std::ispunct(dateTimeString[pmPos + amPmLength]) != 0
2182  || std::isdigit(dateTimeString[pmPos + amPmLength]) != 0
2183  )
2184  ) {
2185  // found pm/PM -> replace it in format string
2187  formatString,
2188  "%p",
2189  dateTimeString.substr(pmPos, amPmLength)
2190  );
2191 
2192  outIsPm = true;
2193 
2194  return;
2195  }
2196  }
2197 
2198  if(newPos == pos) {
2199  break;
2200  }
2201 
2202  pos = newPos;
2203  }
2204  }
2205 
2206 } /* namespace crawlservpp::Helper::DateTime */
2207 
2208 #endif /* HELPER_DATETIME_HPP_ */
constexpr auto englishLocalePrefix
The prefix for English locales.
Definition: DateTime.hpp:116
std::vector< std::string > getDayGap(const std::string &first, const std::string &second)
Gets all days that lie inbetween two dates.
Definition: DateTime.hpp:1271
constexpr std::array russianOrdinalSuffixes
An array containing Russian ordinal suffixes to be stripped from numbers.
Definition: DateTime.hpp:100
constexpr auto year
The current year.
Definition: App.hpp:102
void convertTimeStampToSQLTimeStamp(std::string &timeStamp)
Converts a timestamp in the YYYYMMDDHHMMSS format to a MySQL timestamp in the YYYY-MM-DD HH:MM:SS for...
Definition: DateTime.hpp:693
constexpr auto base10
Base of decimal numbers.
Definition: DateTime.hpp:197
constexpr auto unixTimeFormatXOffset
The position of the beginning of a UNIX time format offset.
Definition: DateTime.hpp:87
std::vector< std::string > getMonthGap(const std::string &first, const std::string &second)
Gets all months that lie inbetween two months.
Definition: DateTime.hpp:1335
constexpr auto millisecondsPerHour
The number of milliseconds per hour used for date/time formatting.
Definition: DateTime.hpp:158
void removeOrdinals(const std::array< std::string_view, N > &suffixes, std::string &strInOut)
Removes all ordinal suffixes after numbers in the given string.
Definition: DateTime.hpp:1455
constexpr auto isoMonthPos
The position of the month in an ISO date (YYYY-MM-DD).
Definition: DateTime.hpp:188
std::string microsecondsToString(std::uint64_t microseconds)
Converts microseconds into a well-formatted string.
Definition: DateTime.hpp:859
void handle12hTime(std::string &formatString, const std::string &dateTimeString, bool &outIsAm, bool &outIsPm)
Handles 12h-time manually to avoid buggy standard library implementations.
Definition: DateTime.hpp:2114
void convertLongDateTimeToSQLTimeStamp(std::string &dateTime)
Converts a date/time formatted in a “long” format into the format YYYY-MM-DD HH:MM:SS.
Definition: DateTime.hpp:361
constexpr std::uint8_t dateWeeks
Group dates by weeks.
Definition: DateTime.hpp:209
constexpr auto minTwoDigitYear
Consider two-digit years before this year as being in the 2000s.
Definition: DateTime.hpp:194
constexpr auto ukrainianLocalePrefix
The prefix for Ukrainian locales.
Definition: DateTime.hpp:125
constexpr auto centuryFrom
The two digits from which two-digit years will be interpreted as years after 2000.
Definition: DateTime.hpp:140
constexpr auto isoMonthLength
The length of the month in an ISO date (YYYY-MM-DD).
Definition: DateTime.hpp:191
constexpr auto amPmLength
The length of the 12-h suffix (AM / PM).
Definition: DateTime.hpp:131
constexpr auto microsecondsPerHour
The number of microseconds per hour used for date/time formatting.
Definition: DateTime.hpp:155
constexpr auto longDateTime
The &#39;long&#39; format for date/times.
Definition: DateTime.hpp:72
std::vector< std::string > getDateGap(const std::string &first, const std::string &second, std::uint8_t resolution)
Gets all dates that lies between two dates.
Definition: DateTime.hpp:1154
void fixFinnishMonths(std::string_view locale, std::string_view format, std::string &strInOut)
Fixes semi-abbreviated Finnish month names (huhtik, touko, etc.), if the locale is Finnish...
Definition: DateTime.hpp:1959
#define MAIN_EXCEPTION_CLASS()
Macro used to easily define classes for general exceptions.
Definition: Exception.hpp:50
constexpr auto reducedToMonthLength
Length of date, reduced to month (YYYY-MM)
Definition: DateTime.hpp:200
void extendSingleDigits(std::string &dateTimeString)
Extends single digits (1-9) by adding a leading zero to each of them.
Definition: DateTime.hpp:2028
constexpr auto millisecondsPerMinute
The number of milliseconds per minute used for date/time formatting.
Definition: DateTime.hpp:167
constexpr auto finnishLocalePrefix
The prefix for Finnish locales.
Definition: DateTime.hpp:128
constexpr std::uint8_t dateYears
Group dates by years.
Definition: DateTime.hpp:227
constexpr auto millisecondsPerDay
The number of milliseconds per day used for date/time formatting.
Definition: DateTime.hpp:149
void fixYear(std::string &sqlTimeStamp, std::string_view format)
Changes a year before 1969 into a year after 2000, if it has been parsed from two digits...
Definition: DateTime.hpp:2067
constexpr std::array englishOrdinalSuffixes
An array containing English ordinal suffixes to be stripped from numbers.
Definition: DateTime.hpp:90
constexpr std::uint8_t dateDays
Group dates by days.
Definition: DateTime.hpp:215
constexpr auto unixTimeFormat
The keyword to use a UNIX time format.
Definition: DateTime.hpp:75
void fixFrenchMonths(std::string_view locale, std::string &strInOut)
Replaces the abbreviation avr. for the month of april (avril) in the given string, if the locale is French.
Definition: DateTime.hpp:1563
std::string now()
Formats the current date/time as string in the format YYYY-MM-DD HH:MM:SS.
Definition: DateTime.hpp:1045
constexpr auto first
Index of the first byte.
Definition: Bytes.hpp:57
constexpr auto microsecondsPerMinute
The number of microseconds per minute used for date/time formatting.
Definition: DateTime.hpp:164
constexpr std::array frenchOrdinalSuffixes
An array containing French ordinal suffix to be stripped from numbers.
Definition: DateTime.hpp:95
constexpr auto unixTimeFormatPlus
The keyword to use a UNIX time format plus an offset.
Definition: DateTime.hpp:78
constexpr auto isoDateLength
The length of a date in valid ISO format (YYYY-MM-DD).
Definition: DateTime.hpp:182
constexpr auto yearLength
The length of a year.
Definition: DateTime.hpp:185
void fixUkrainianMonths(std::string_view locale, std::string &strInOut, std::string &formatInOut)
Shortens Ukrainian month names, if the locale is Ukrainian.
Definition: DateTime.hpp:1790
constexpr auto microsecondsPerSecond
The number of microseconds per second used for date/time formatting.
Definition: DateTime.hpp:173
constexpr auto sqlTimeStampLength
The length of a formatted time stamp in the MySQL database.
Definition: DateTime.hpp:113
void fixRussianMonths(std::string_view locale, std::string &strInOut, std::string &formatInOut)
Shortens Russian month names and replaces the abbreviations май and сент, if the locale is Russian...
Definition: DateTime.hpp:1602
constexpr auto secondsPerHour
The number of seconds per hour used for date/time formatting.
Definition: DateTime.hpp:161
constexpr auto second
Index of the second byte.
Definition: Bytes.hpp:60
constexpr auto sqlTimeStamp
The date/time format used by the MySQL database (as C string).
Definition: DateTime.hpp:110
void replaceAll(std::string &strInOut, std::string_view needle, std::string_view replacement)
Replaces all occurences within a string with another string.
Definition: Strings.hpp:246
constexpr auto daysPerWeek
Number of days in a week.
Definition: DateTime.hpp:203
void convert12hTo24h(int &hour, bool isPm)
Converts an hour from the 12h to the 24h system.
Definition: DateTime.hpp:742
Class for date/time locale exception.
Definition: DateTime.hpp:337
constexpr auto secondsPerMinute
The number of seconds per minute used for date/time formatting.
Definition: DateTime.hpp:170
constexpr auto secondsPerDay
The number of seconds per day used for date/time formatting.
Definition: DateTime.hpp:152
constexpr auto microsecondsPerMillisecond
The number of microseconds per millisecond used for date/time formatting.
Definition: DateTime.hpp:179
bool isISODateInRange(std::string_view isoDate, std::string_view rangeFrom, std::string_view rangeTo)
Checks whether the given ISO date is in the given range of dates.
Definition: DateTime.hpp:1105
constexpr std::uint8_t dateMonths
Group dates by months.
Definition: DateTime.hpp:221
void convertSQLTimeStampToTimeStamp(std::string &timeStamp)
Converts a MySQL timestamp in the YYYY-MM-DD HH:MM:SS format to a timestamp in the YYYYMMDDHHMMSS for...
Definition: DateTime.hpp:712
std::string getYearAndWeek(const std::string &date)
Get the year and the ISO week number for a specific date.
Definition: DateTime.hpp:775
constexpr auto millisecondsPerSecond
The number of milliseconds per second used for date/time formatting.
Definition: DateTime.hpp:176
std::string secondsToString(std::uint64_t seconds)
Converts seconds into a well-formatted string.
Definition: DateTime.hpp:995
constexpr auto hourNoonMidnight
The hour of noon and midnight.
Definition: DateTime.hpp:137
constexpr auto microsecondsPerDay
The number of microseconds per day used for date/time formatting.
Definition: DateTime.hpp:146
void convertCustomDateTimeToSQLTimeStamp(std::string &dateTime, const std::string &customFormat)
Converts date/time with a custom format into the format YYYY-MM-DD HH:MM:SS.
Definition: DateTime.hpp:398
void reduceDate(std::string &date, std::uint8_t resolution)
Reduce a date to the specified resolution.
Definition: DateTime.hpp:807
std::string millisecondsToString(std::uint64_t milliseconds)
Converts milliseconds into a well-formatted string.
Definition: DateTime.hpp:933
constexpr auto unixTimeFormatXLength
The length of the keyword to use a UNIX time format with offset.
Definition: DateTime.hpp:84
bool isValidISODate(const std::string &isoDate)
Checks whether a string contains a valid date in the ISO format.
Definition: DateTime.hpp:1071
std::vector< std::string > getWeekGap(const std::string &first, const std::string &second)
Gets all ISO week numbers that lie inbetween two week numbers.
Definition: DateTime.hpp:1207
#define MAIN_EXCEPTION_SUBCLASS(NAME)
Macro used to easily define classes for specific exceptions.
Definition: Exception.hpp:65
Namespace for global date/time helper functions.
Definition: DateTime.hpp:60
constexpr auto yearsPerCentury
The number of years in a century.
Definition: DateTime.hpp:143
constexpr std::array ukrainianOrdinalSuffixes
An array containing Ukrainian ordinal suffixes to be stripped from numbers.
Definition: DateTime.hpp:105
constexpr auto unixTimeFormatMinus
The keyword to use a UNIX time format minus an offset.
Definition: DateTime.hpp:81
Class for date/time exceptions.
Definition: DateTime.hpp:330
std::vector< std::string > getYearGap(const std::string &first, const std::string &second)
Gets all years that lies inbetween two years.
Definition: DateTime.hpp:1399
constexpr auto frenchLocalePrefix
The prefix for French locales.
Definition: DateTime.hpp:119
constexpr auto hourChange
The number of hours to be added to a PM time, or to be subtracted from a 12th hour AM time...
Definition: DateTime.hpp:134
constexpr auto russianLocalePrefix
The prefix for Russian locales.
Definition: DateTime.hpp:122