Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
Date.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <algorithm>
7#include <ctime>
8#include <sstream>
9
12#include "Stroika/Foundation/Execution/Throw.h"
13#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::Exception<runtime_error>{"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> ();
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 ());
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 ());
96 return LocaleFreeParseQuietly_kMonthDayYearFormat_ (rep, consumedCharsInStringUpTo);
97 }
98 }
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
107 if ((errState & ios::badbit) or (errState & ios::failbit)) [[unlikely]] {
108 return nullopt;
109 }
110 else {
111 if (consumedCharsInStringUpTo != nullptr) {
112 *consumedCharsInStringUpTo = static_cast<size_t> (static_cast<streamoff> (iss.tellg ()));
113 }
114 return AsDate_ (when);
115 }
116}
117
119{
120 switch (pf) {
121 case eCurrentLocale_WithZerosStripped: {
122 String tmp = Format (locale{});
123 /*
124 * This logic probably needs to be locale-specific, but this is good enuf for now...
125 * Map things like:
126 * 01:03:05 to 1:03:05
127 *
128 * and map
129 * 12/05/00 to 12/05, but DON'T map 12/15/2000 to 12/15/2000
130 */
131 static const String kZero_ = "0"sv;
132 optional<size_t> i = 0;
133 while ((i = tmp.Find (kZero_, *i))) {
134 // any 0N (where n a digit) is replaced with a single '0'
135 Assert (tmp[*i] == '0');
136 bool isLeadingZero = false;
137 if (*i + 1 < tmp.length () and tmp[*i + 1].IsDigit ()) {
138 if (*i == 0 or not tmp[*i - 1].IsDigit ()) {
139 // don't strip leading zeros if its the YEAR - the last part of a X/Y/Z combo...
140 //
141 // this test is quite inadequate...
142 if (*i + 2 < tmp.length ()) {
143 isLeadingZero = true;
144 }
145 }
146 }
147 if (isLeadingZero) {
148 tmp = tmp.substr (0, *i) + tmp.substr (*i + 1);
149 }
150 else {
151 i = *i + 1;
152 }
153 }
154 return tmp;
155 }
156 }
158 return String{};
159}
160
161String Date::Format (const locale& l) const
162{
164}
165
166String Date::Format (const String& formatPattern) const
167{
168 // some format's locale independent
170 return "{:04}-{:02}-{:02}"_f((int)(this->GetYear ()), (unsigned int)(this->GetMonth ()), (unsigned int)(this->GetDayOfMonth ()));
171 }
172 else {
173 return Format (locale{}, formatPattern);
174 }
175}
176
177String Date::Format (const locale& l, const String& formatPattern) const
178{
179 // http://new.cplusplus.com/reference/std/locale/time_put/put/
180 ::tm when = As<tm> ();
184 wostringstream oss;
186 return oss.str ();
187}
188
189Date Date::AsDate_ (const ::tm& when)
190{
191 return Date{Year{when.tm_year + kTM_Year_RelativeToYear_},
192 MonthOfYear{static_cast<unsigned int> (when.tm_mon + 1), DataExchange::ValidationStrategy::eThrow},
193 DayOfMonth{static_cast<unsigned int> (when.tm_mday), DataExchange::ValidationStrategy::eThrow},
195}
196
197[[nodiscard]] Date Date::Add (days dayCount) const
198{
199 // SEE https://github.com/HowardHinnant/date/issues/178
200 return Date{chrono::sys_days{fRep_} + dayCount, DataExchange::ValidationStrategy::eThrow};
201}
202
203[[nodiscard]] Date Date::Add (const Duration& d) const
204{
205 return Add (chrono::round<days> (d));
206}
207
208Date Date::operator- (const Duration& d) const
209{
210 return Add (-d);
211}
212
213days Date::Since () const
214{
215 return Since (DateTime::GetToday (), this->As<year_month_day> ());
216}
217
218weekday Date::GetDayOfWeek () const
219{
220 /*
221 * From https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week
222 * Gauss's algorithm:
223 * ...
224 * The weekday of 1 January in year number A is
225 * R(1+5R(A-1,4)+4R(A-1,100)+6R(A-1,400),7)
226 * - where {\displaystyle R(y,m)} R(y,m) is the remainder after division of y by m,[6] or y modulo m.
227 */
228 unsigned int y = static_cast<unsigned int> (static_cast<int> (GetYear ()));
229 auto R = [] (unsigned int i, unsigned int r) { return i % r; };
230 unsigned int weekdayOfJan1 = R (1 + 5 * R (y - 1, 4) + 4 * R (y - 1, 100) + 6 * R (y - 1, 400), 7);
231 // this assumes Sunday is ZERO and the rest of the days follow it...
232
233 unsigned int month0 = static_cast<unsigned int> (GetMonth ()) - static_cast<unsigned int> (January);
234
235 static constexpr unsigned int kDayOfWeekOffsetPerMonth_[12] = {
236 3,
237 0, // February special - add one for leap year
238 3, 2, 3, 2, 3, 3, 2, 3, 2, 3,
239 };
240 unsigned int targetDayOfWeek = weekdayOfJan1;
241 for (unsigned int i = 0; i < month0; ++i) {
243 }
244 if (month0 > 1 and GetYear ().is_leap ()) {
245 targetDayOfWeek += 1;
246 }
247
248 // add which day of the month (offset from first)
249 targetDayOfWeek += (GetDayOfMonth () - day{1}).count ();
250
251 return DayOfWeek{R (targetDayOfWeek, 7) + Sunday.c_encoding ()};
252}
253
254/*
255 ********************************************************************************
256 *************************** Date::DayDifference ********************************
257 ********************************************************************************
258 */
259Date::SignedJulianDayNumber Time::DayDifference (const Date& lhs, const Date& rhs)
260{
261 // mostly right, but not complete correct edge cases with type/size stuff...
262 Date::JulianDayNumber l = lhs.GetJulianRep ();
263 Date::JulianDayNumber r = rhs.GetJulianRep ();
264 if (l == r) {
265 return 0;
266 }
267 if (l < r) {
268 unsigned int diff = r - l;
269 Assert (INT_MIN <= -INT_MAX);
270 if (diff >= static_cast<unsigned int> (INT_MAX)) {
271 return INT_MIN;
272 }
273 else {
274 return -static_cast<int> (diff);
275 }
276 }
277 else {
278 unsigned int diff = l - r;
279 if (diff >= static_cast<unsigned int> (INT_MAX)) {
280 return INT_MAX;
281 }
282 else {
283 return static_cast<int> (diff);
284 }
285 }
287 return 0;
288}
289
290/*
291 ********************************************************************************
292 *************************** Date::YearDifference *******************************
293 ********************************************************************************
294 */
295int Time::YearDifference (const Date& lhs, const Date& rhs)
296{
297 int diff = static_cast<int> (lhs.GetYear ()) - static_cast<int> (rhs.GetYear ());
298 if (lhs.GetMonth () < rhs.GetMonth () or (lhs.GetMonth () == rhs.GetMonth () and lhs.GetDayOfMonth () < rhs.GetDayOfMonth ())) {
299 diff--;
300 }
301 return diff;
302}
303
304float Time::YearDifferenceF (const Date& lhs, const Date& rhs)
305{
306 return DayDifference (lhs, rhs) / 365.25f; //tmphack
307}
308
309/*
310 ********************************************************************************
311 ***************************** GetFormattedAge **********************************
312 ********************************************************************************
313 */
314String Time::GetFormattedAge (const optional<Date>& birthDate, const optional<Date>& deathDate)
315{
316 if (birthDate.has_value ()) {
317 int yearDiff = deathDate.has_value () ? YearDifference (*deathDate, *birthDate) : YearDifference (DateTime::GetToday (), *birthDate);
318 return Format ("{}"_f, yearDiff);
319 }
320 else {
321 return "?"sv;
322 }
323}
324
325/*
326 ********************************************************************************
327 ************************** GetFormattedAgeWithUnit *****************************
328 ********************************************************************************
329 */
330String Time::GetFormattedAgeWithUnit (const optional<Date>& birthDate, const optional<Date>& deathDate, bool abbrevUnit)
331{
332 if (birthDate.has_value ()) {
333 int yearDiff = deathDate.has_value () ? YearDifference (*deathDate, *birthDate) : YearDifference (DateTime::GetToday (), *birthDate);
334 if (yearDiff >= 0 and yearDiff < 2) {
335 float yearDiffF = deathDate.has_value () ? YearDifferenceF (*deathDate, *birthDate) : YearDifferenceF (DateTime::GetToday (), *birthDate);
336 int months = int (yearDiffF * 12.0f + 0.4999f);
337 String unitBase = abbrevUnit ? "mo"_k : "month"_k;
338 return Format ("{} {}"_f, months, Linguistics::MessageUtilities::Manager::sThe.PluralizeNoun (unitBase, months));
339 }
340 else {
341 String unitBase = abbrevUnit ? "yr"_k : "year"_k;
342 return Format ("{} {}"_f, yearDiff, Linguistics::MessageUtilities::Manager::sThe.PluralizeNoun (unitBase, yearDiff));
343 }
344 }
345 else {
346 return "?"sv;
347 }
348}
#define AssertNotReached()
Definition Assertions.h:358
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
Definition Exceptions.h:219
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
constexpr T As() const
Definition Date.inl:270
nonvirtual String Format(NonStandardPrintFormat pf=NonStandardPrintFormat::eDEFAULT) const
Definition Date.cpp:118
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:435
make_signed_t< JulianDayNumber > SignedJulianDayNumber
Definition Date.h:338
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:424
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:298
static constexpr string_view kLocaleStandardFormat
Definition Date.h:471
nonvirtual Date Add(int d) const
Definition Date.inl:408
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:234
void Throw(T &&e2Throw)
like the builtin C++ 'throw', but stamps in the current Activity context and does helpful,...
Definition Throw.inl:54
Simple wrapper on std::chrono::day, with some helpful validation properties (assures constructed 'ok'...
Definition Date.h:161
Simple wrapper on std::chrono::weekday, with some helpful validation properties (assures constructed ...
Definition Date.h:103
Simple wrapper on std::chrono::month, with some helpful validation properties (assures constructed 'o...
Definition Date.h:129
Simple wrapper on std::chrono::year, with some helpful validation properties (assures constructed 'ok...
Definition Date.h:213