Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
Date.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <algorithm>
7#include <ctime>
8#include <sstream>
9
10#include "DateTime.h"
11#include "Duration.h"
14#include "Stroika/Foundation/Execution/Throw.h"
15#include "Stroika/Foundation/Linguistics/MessageUtilities.h"
16
17#include "Date.h"
18
19using namespace Stroika::Foundation;
21using namespace Stroika::Foundation::Execution;
22using namespace Stroika::Foundation::Memory;
23using namespace Stroika::Foundation::Time;
24
25using namespace Time;
26
27/*
28 ********************************************************************************
29 **************************** Date::FormatException *****************************
30 ********************************************************************************
31 */
32Date::FormatException::FormatException ()
33 : Execution::RuntimeErrorException<>{"Invalid Date Format"sv}
34{
35}
36
37/*
38 ********************************************************************************
39 *********************************** Date ***************************************
40 ********************************************************************************
41 */
42Date Date::Now () noexcept
43{
44 return DateTime::Now ().GetDate ();
45}
46
47Date Date::Parse_ (const String& rep, const locale& l, const Traversal::Iterable<String>& formatPatterns, size_t* consumedCharsInStringUpTo)
48{
49 if (rep.empty ()) {
50 Execution::Throw (FormatException::kThe);
51 }
52 wstring wRep = rep.As<wstring> ();
53 const time_get<wchar_t>& tmget = use_facet<time_get<wchar_t>> (l);
54 for (const auto& formatPattern : formatPatterns) {
55 if (auto r = ParseQuietly_ (wRep, tmget, formatPattern, consumedCharsInStringUpTo)) {
56 return *r;
57 }
58 }
59 Execution::Throw (FormatException::kThe);
60}
61
62optional<Date> Date::LocaleFreeParseQuietly_kMonthDayYearFormat_ (const wstring& rep, size_t* consumedCharsInStringUpTo)
63{
64 // parse locale independent "%m/%d/%Y" - from - https://en.cppreference.com/w/cpp/locale/time_get/get - including validation
65 int year = 0;
66 int m = 0;
67 int d = 0;
68 DISABLE_COMPILER_MSC_WARNING_START (4996) // MSVC SILLY WARNING ABOUT USING swscanf_s
69 unsigned int pos{}; // doesn't count fixed characters, just % characters
70 int nItems = ::swscanf (rep.c_str (), L"%d/%d/%d%n", &m, &d, &year, &pos);
71 DISABLE_COMPILER_MSC_WARNING_END (4996)
72 if (nItems == 3) {
73 if (consumedCharsInStringUpTo != nullptr) {
74 Assert (pos + 2 < rep.length ());
75 *consumedCharsInStringUpTo = pos + 2;
76 }
77 if ((1 <= m and m <= 12) and (1 <= d and d <= 31) and (year > 0)) {
78 try {
79 // above check is NEARLY good enuf but not quite, so we need to try/catch here to do this quietly. BUT - should really do more to avoid first exception
80 return Date{Year{year}, month{static_cast<unsigned int> (m)}, day{static_cast<unsigned int> (d)},
82 }
83 catch (...) {
84 return nullopt;
85 }
86 }
87 }
88 return nullopt;
89}
90
91optional<Date> Date::ParseQuietly_ (const wstring& rep, const time_get<wchar_t>& tmget, const String& formatPattern, size_t* consumedCharsInStringUpTo)
92{
93 Require (not rep.empty ());
94 if constexpr (qCompilerAndStdLib_locale_time_get_PCTM_RequiresLeadingZero_Buggy) {
95 if (formatPattern == kMonthDayYearFormat) {
96 return LocaleFreeParseQuietly_kMonthDayYearFormat_ (rep, consumedCharsInStringUpTo);
97 }
98 }
99 Memory::StackBuffer<wchar_t> formatPattern_BackingStore;
100 auto [formatPattern_CStr, formatPattern_SV] = formatPattern.c_str (&formatPattern_BackingStore);
101 ios::iostate errState = ios::goodbit;
102 tm when{};
103 wistringstream iss{rep};
104 istreambuf_iterator<wchar_t> itbegin{iss}; // beginning of iss
105 istreambuf_iterator<wchar_t> itend; // end-of-stream
106 istreambuf_iterator<wchar_t> i =
107 tmget.get (itbegin, itend, iss, errState, &when, formatPattern_CStr, formatPattern_CStr + formatPattern_SV.size ());
108 if ((errState & ios::badbit) or (errState & ios::failbit)) [[unlikely]] {
109 return nullopt;
110 }
111 else {
112 if (consumedCharsInStringUpTo != nullptr) {
113 *consumedCharsInStringUpTo = static_cast<size_t> (distance (itbegin, i));
114 }
115 return AsDate_ (when);
116 }
117}
118
120{
121 switch (pf) {
122 case eCurrentLocale_WithZerosStripped: {
123 String tmp = Format (locale{});
124 /*
125 * This logic probably needs to be locale-specific, but this is good enuf for now...
126 * Map things like:
127 * 01:03:05 to 1:03:05
128 *
129 * and map
130 * 12/05/00 to 12/05, but DON'T map 12/15/2000 to 12/15/2000
131 */
132 static const String kZero_ = "0"sv;
133 optional<size_t> i = 0;
134 while ((i = tmp.Find (kZero_, *i))) {
135 // any 0N (where n a digit) is replaced with a single '0'
136 Assert (tmp[*i] == '0');
137 bool isLeadingZero = false;
138 if (*i + 1 < tmp.length () and tmp[*i + 1].IsDigit ()) {
139 if (*i == 0 or not tmp[*i - 1].IsDigit ()) {
140 // don't strip leading zeros if its the YEAR - the last part of a X/Y/Z combo...
141 //
142 // this test is quite inadequate...
143 if (*i + 2 < tmp.length ()) {
144 isLeadingZero = true;
145 }
146 }
147 }
148 if (isLeadingZero) {
149 tmp = tmp.substr (0, *i) + tmp.substr (*i + 1);
150 }
151 else {
152 i = *i + 1;
153 }
154 }
155 return tmp;
156 }
157 }
159 return String{};
160}
161
162String Date::Format (const locale& l) const
163{
164 return Format (l, kLocaleStandardFormat);
165}
166
167String Date::Format (const String& formatPattern) const
168{
169 // some format's locale independent
170 if (formatPattern == kISO8601Format) {
171 return "{:04}-{:02}-{:02}"_f((int)(this->GetYear ()), (unsigned int)(this->GetMonth ()), (unsigned int)(this->GetDayOfMonth ()));
172 }
173 else {
174 return Format (locale{}, formatPattern);
175 }
176}
177
178String Date::Format (const locale& l, const String& formatPattern) const
179{
180 // http://new.cplusplus.com/reference/std/locale/time_put/put/
181 ::tm when = As<::tm> ();
183 auto [formatPatternCStr, formatPatternSV] = formatPattern.c_str (&formatBuf);
184 const time_put<wchar_t>& tmput = use_facet<time_put<wchar_t>> (l);
185 wostringstream oss;
186
187#if qCompilerAndStdLib_FormatRangeRestriction_Buggy
188 WeakAssert (when.tm_year == clamp<int> (when.tm_year, -1900, 8099));
189 when.tm_year = clamp<int> (when.tm_year, -1900, 8099);
190#endif
191
192 tmput.put (oss, oss, ' ', &when, formatPatternCStr, formatPatternCStr + formatPatternSV.length ());
193 return oss.str ();
194}
195
196Date Date::AsDate_ (const ::tm& when)
197{
198 return Date{Year{when.tm_year + kTM_Year_RelativeToYear_},
199 MonthOfYear{static_cast<unsigned int> (when.tm_mon + 1), DataExchange::ValidationStrategy::eThrow},
200 DayOfMonth{static_cast<unsigned int> (when.tm_mday), DataExchange::ValidationStrategy::eThrow},
202}
203
204[[nodiscard]] Date Date::Add (days dayCount) const
205{
206 // SEE https://github.com/HowardHinnant/date/issues/178
207 return Date{chrono::sys_days{fRep_} + dayCount, DataExchange::ValidationStrategy::eThrow};
208}
209
210[[nodiscard]] Date Date::Add (const Duration& d) const
211{
212 return Add (chrono::round<days> (d));
213}
214
215Date Date::operator- (const Duration& d) const
216{
217 return Add (-d);
218}
219
220days Date::Since () const
221{
222 return Since (DateTime::GetToday (), this->As<year_month_day> ());
223}
224
225weekday Date::GetDayOfWeek () const
226{
227 /*
228 * From https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week
229 * Gauss's algorithm:
230 * ...
231 * The weekday of 1 January in year number A is
232 * R(1+5R(A-1,4)+4R(A-1,100)+6R(A-1,400),7)
233 * - where {\displaystyle R(y,m)} R(y,m) is the remainder after division of y by m,[6] or y modulo m.
234 */
235 unsigned int y = static_cast<unsigned int> (static_cast<int> (GetYear ()));
236 auto R = [] (unsigned int i, unsigned int r) { return i % r; };
237 unsigned int weekdayOfJan1 = R (1 + 5 * R (y - 1, 4) + 4 * R (y - 1, 100) + 6 * R (y - 1, 400), 7);
238 // this assumes Sunday is ZERO and the rest of the days follow it...
239
240 unsigned int month0 = static_cast<unsigned int> (GetMonth ()) - static_cast<unsigned int> (January);
241
242 static constexpr unsigned int kDayOfWeekOffsetPerMonth_[12] = {
243 3,
244 0, // February special - add one for leap year
245 3, 2, 3, 2, 3, 3, 2, 3, 2, 3,
246 };
247 unsigned int targetDayOfWeek = weekdayOfJan1;
248 for (unsigned int i = 0; i < month0; ++i) {
249 targetDayOfWeek += kDayOfWeekOffsetPerMonth_[i];
250 }
251 if (month0 > 1 and GetYear ().is_leap ()) {
252 targetDayOfWeek += 1;
253 }
254
255 // add which day of the month (offset from first)
256 targetDayOfWeek += (GetDayOfMonth () - day{1}).count ();
257
258 return DayOfWeek{R (targetDayOfWeek, 7) + Sunday.c_encoding ()};
259}
260
261/*
262 ********************************************************************************
263 *************************** Date::DayDifference ********************************
264 ********************************************************************************
265 */
266Date::SignedJulianDayNumber Time::DayDifference (const Date& lhs, const Date& rhs)
267{
268 // mostly right, but not complete correct edge cases with type/size stuff...
269 Date::JulianDayNumber l = lhs.GetJulianRep ();
270 Date::JulianDayNumber r = rhs.GetJulianRep ();
271 if (l == r) {
272 return 0;
273 }
274 if (l < r) {
275 unsigned int diff = r - l;
276 Assert (INT_MIN <= -INT_MAX);
277 if (diff >= static_cast<unsigned int> (INT_MAX)) {
278 return INT_MIN;
279 }
280 else {
281 return -static_cast<int> (diff);
282 }
283 }
284 else {
285 unsigned int diff = l - r;
286 if (diff >= static_cast<unsigned int> (INT_MAX)) {
287 return INT_MAX;
288 }
289 else {
290 return static_cast<int> (diff);
291 }
292 }
294 return 0;
295}
296
297/*
298 ********************************************************************************
299 *************************** Date::YearDifference *******************************
300 ********************************************************************************
301 */
302int Time::YearDifference (const Date& lhs, const Date& rhs)
303{
304 int diff = static_cast<int> (lhs.GetYear ()) - static_cast<int> (rhs.GetYear ());
305 if (lhs.GetMonth () < rhs.GetMonth () or (lhs.GetMonth () == rhs.GetMonth () and lhs.GetDayOfMonth () < rhs.GetDayOfMonth ())) {
306 diff--;
307 }
308 return diff;
309}
310
311float Time::YearDifferenceF (const Date& lhs, const Date& rhs)
312{
313 return DayDifference (lhs, rhs) / 365.25f; //tmphack
314}
315
316/*
317 ********************************************************************************
318 ***************************** GetFormattedAge **********************************
319 ********************************************************************************
320 */
321String Time::GetFormattedAge (const optional<Date>& birthDate, const optional<Date>& deathDate)
322{
323 if (birthDate.has_value ()) {
324 int yearDiff = deathDate.has_value () ? YearDifference (*deathDate, *birthDate) : YearDifference (DateTime::GetToday (), *birthDate);
325 return Format ("{}"_f, yearDiff);
326 }
327 else {
328 return "?"sv;
329 }
330}
331
332/*
333 ********************************************************************************
334 ************************** GetFormattedAgeWithUnit *****************************
335 ********************************************************************************
336 */
337String Time::GetFormattedAgeWithUnit (const optional<Date>& birthDate, const optional<Date>& deathDate, bool abbrevUnit)
338{
339 if (birthDate.has_value ()) {
340 int yearDiff = deathDate.has_value () ? YearDifference (*deathDate, *birthDate) : YearDifference (DateTime::GetToday (), *birthDate);
341 if (yearDiff >= 0 and yearDiff < 2) {
342 float yearDiffF = deathDate.has_value () ? YearDifferenceF (*deathDate, *birthDate) : YearDifferenceF (DateTime::GetToday (), *birthDate);
343 int months = int (yearDiffF * 12.0f + 0.4999f);
344 String unitBase = abbrevUnit ? "mo"_k : "month"_k;
345 return Format ("{} {}"_f, months, Linguistics::MessageUtilities::Manager::sThe.PluralizeNoun (unitBase, months));
346 }
347 else {
348 String unitBase = abbrevUnit ? "yr"_k : "year"_k;
349 return Format ("{} {}"_f, yearDiff, Linguistics::MessageUtilities::Manager::sThe.PluralizeNoun (unitBase, yearDiff));
350 }
351 }
352 else {
353 return "?"sv;
354 }
355}
#define WeakAssert(c)
A WeakAssert() is for things that aren't guaranteed to be true, but are overwhelmingly likely to be t...
Definition Assertions.h:438
#define AssertNotReached()
Definition Assertions.h:355
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
nonvirtual size_t length() const noexcept
Definition String.inl:1045
nonvirtual tuple< const wchar_t *, wstring_view > c_str(Memory::StackBuffer< wchar_t > *possibleBackingStore) const
Definition String.inl:1049
nonvirtual optional< size_t > Find(Character c, CompareOptions co=eWithCase) const
Definition String.inl:681
nonvirtual String substr(size_t from, size_t count=npos) const
Definition String.inl:1086
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
nonvirtual String Format(NonStandardPrintFormat pf=NonStandardPrintFormat::eDEFAULT) const
Definition Date.cpp:119
NonStandardPrintFormat
DisplayFormat is a representation which a date can be transformed in and out of.
Definition Date.h:587
nonvirtual days operator-(const Date &rhs) const
Definition Date.inl:420
make_signed_t< JulianDayNumber > SignedJulianDayNumber
Definition Date.h:340
static constexpr string_view kISO8601Format
Y-M-D format - locale independent, and ISO-8601 date format standard.
Definition Date.h:465
static Date Now() noexcept
Definition Date.cpp:42
static days Since(Date dStart, Date dEnd)
Definition Date.inl:409
static constexpr string_view kMonthDayYearFormat
classic (american) month-day-year format, but unlike D, this uses Y, so the 4-digit form of year
Definition Date.h:487
nonvirtual constexpr JulianDayNumber GetJulianRep() const
return the Julian Day Number (JDN) - corresponding to this date object (https://en....
Definition Date.inl:283
static constexpr string_view kLocaleStandardFormat
Definition Date.h:471
nonvirtual Date Add(int d) const
Definition Date.inl:393
Duration is a chrono::duration<double> (=.
Definition Duration.h:96
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:237
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43
Simple wrapper on std::chrono::day, with some helpful validation properties (assures constructed 'ok'...
Definition Date.h:163
Simple wrapper on std::chrono::weekday, with some helpful validation properties (assures constructed ...
Definition Date.h:105
Simple wrapper on std::chrono::month, with some helpful validation properties (assures constructed 'o...
Definition Date.h:131
Simple wrapper on std::chrono::year, with some helpful validation properties (assures constructed 'ok...
Definition Date.h:215