Skip to content

Commit e90f027

Browse files
authored
Fix C++ code to deal with date and date-times (#451)
We fix the generated code and the test snippets so that we pass the related verification tests in the C++ SDK.
1 parent a206fe8 commit e90f027

File tree

8 files changed

+242
-66
lines changed

8 files changed

+242
-66
lines changed

aas_core_codegen/cpp/verification/_generate.py

+2
Original file line numberDiff line numberDiff line change
@@ -3270,6 +3270,8 @@ def generate_implementation(
32703270
cpp_common.WARNING,
32713271
Stripped(
32723272
f"""\
3273+
#include "./BigInt.hpp"
3274+
#include "{include_prefix_path}/common.hpp"
32733275
#include "{include_prefix_path}/constants.hpp"
32743276
#include "{include_prefix_path}/verification.hpp"
32753277

test_data/cpp/test_main/aas_core_meta.v3/expected_output/verification.cpp

+110-23
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// This code has been automatically generated by aas-core-codegen.
22
// Do NOT edit or append.
33

4+
#include "./BigInt.hpp"
5+
#include "aas_core/aas_3_0/common.hpp"
46
#include "aas_core/aas_3_0/constants.hpp"
57
#include "aas_core/aas_3_0/verification.hpp"
68

@@ -220,9 +222,48 @@ bool MatchesXsDateTimeUtc(
220222
}
221223

222224
const std::wregex kRegexDatePrefix(
223-
L"^(-?[0-9]+)-(0[1-9]|11|12)-(0[0-9]|1[0-9]|2[0-9]|30|31)"
225+
L"^(-?[0-9]+)-(0[1-9]|1[0-2])-(0[0-9]|1[0-9]|2[0-9]|30|31)"
224226
);
225227

228+
template<
229+
typename T,
230+
std::enable_if<
231+
std::is_integral<T>::value
232+
|| std::is_same<T, BigInt>::value
233+
>* = nullptr
234+
>
235+
bool IsLeapYear(T year) {
236+
// NOTE (mristin):
237+
// We consider the years B.C. to be one-off.
238+
// See the note at: https://www.w3.org/TR/xmlschema-2/#dateTime:
239+
// "'-0001' is the lexical representation of the year 1 Before Common Era
240+
// (1 BCE, sometimes written "1 BC")."
241+
//
242+
// Hence, -1 year in XML is 1 BCE, which is 0 year in astronomical years.
243+
if (year < 0) {
244+
year = -year - 1;
245+
}
246+
247+
// See: See: https://en.wikipedia.org/wiki/Leap_year#Algorithm
248+
if (year % 4 > 0)
249+
{
250+
return false;
251+
}
252+
253+
if (year % 100 > 0)
254+
{
255+
return true;
256+
}
257+
258+
if (year % 400 > 0)
259+
{
260+
return false;
261+
}
262+
263+
return true;
264+
}
265+
266+
226267
bool IsLeapYear(long long year) {
227268
// NOTE (mristin):
228269
// We consider the years B.C. to be one-off.
@@ -272,17 +313,15 @@ const std::map<int, int> kDaysInMonth = {
272313
};
273314

274315
/**
275-
* \brief Check that \p value is a valid `xs:date`.
316+
* \brief Check that \p value is a valid `xs:date` without the offset.
276317
*
277318
* Year 1 BCE is the last leap BCE year.
278319
* See: https://www.w3.org/TR/xmlschema-2/#dateTime.
279320
*
280321
* \param value to be checked
281322
* \return true if \p value is a valid `xs:date`
282323
*/
283-
bool IsXsDate(
284-
const std::wstring& text
285-
) {
324+
bool IsXsDateWithoutOffset(const std::wstring& text) {
286325
// NOTE (mristin):
287326
// We can not use date functions from the operation system as they do not
288327
// handle years BCE (*e.g.*, `-0003-01-02`).
@@ -301,10 +340,14 @@ bool IsXsDate(
301340
// difficult. Hence, we sacrifice the efficiency a bit for the clearer code & code
302341
// generation.
303342

304-
long long year;
343+
bool is_zero_year;
344+
bool is_leap_year;
305345

306346
try {
307-
year = std::stoll(match[1].str());
347+
const long long year = std::stoll(match[1].str());
348+
349+
is_zero_year = year == 0;
350+
is_leap_year = IsLeapYear<long long>(year);
308351
} catch (const std::invalid_argument&) {
309352
std::wstringstream wss;
310353
wss
@@ -315,19 +358,11 @@ bool IsXsDate(
315358
common::WstringToUtf8(wss.str())
316359
);
317360
} catch (const std::out_of_range&) {
318-
std::wstringstream wss;
319-
wss
320-
<< "The year is out of range for long long integers: "
321-
<< match[1].str()
322-
<< (
323-
"; we at aas-core-works planned to include handling of BigInt years "
324-
"in the SDK, but eventually lacked the time for it. Please let the developers "
325-
"know that you need this feature."
326-
);
327-
328-
throw std::out_of_range(
329-
common::WstringToUtf8(wss.str())
361+
const BigInt year(
362+
common::WstringToUtf8(match[1].str())
330363
);
364+
is_zero_year = year == 0;
365+
is_leap_year = IsLeapYear<BigInt>(std::move(year));
331366
}
332367

333368
const int month = std::stoi(match[2].str());
@@ -336,7 +371,7 @@ bool IsXsDate(
336371
// NOTE (mristin):
337372
// We do not accept year zero, see the note at:
338373
// https://www.w3.org/TR/xmlschema-2/#dateTime
339-
if (year == 0) {
374+
if (is_zero_year) {
340375
return false;
341376
}
342377

@@ -350,7 +385,7 @@ bool IsXsDate(
350385

351386
const int max_days(
352387
(month == 2)
353-
? (IsLeapYear(year) ? 29 : 28)
388+
? (is_leap_year ? 29 : 28)
354389
: kDaysInMonth.at(month)
355390
);
356391

@@ -386,7 +421,7 @@ bool IsXsDateTimeUtc(
386421
// should be used here.
387422
std::wstring date = text.substr(0, pos);
388423

389-
return IsXsDate(date);
424+
return IsXsDateWithoutOffset(date);
390425
}
391426

392427
std::wregex ConstructMatchesMimeType() {
@@ -1610,7 +1645,7 @@ bool IsXsDateTime(
16101645
// should be used here.
16111646
std::wstring date = text.substr(0, pos);
16121647

1613-
return IsXsDate(date);
1648+
return IsXsDateWithoutOffset(date);
16141649
}
16151650

16161651
std::wregex ConstructMatchesXsDecimal() {
@@ -2239,6 +2274,58 @@ bool MatchesXsString(
22392274
);
22402275
}
22412276

2277+
bool IsXsDate(const std::wstring& text) {
2278+
std::wsmatch match;
2279+
const bool matched = std::regex_match(text, match, kRegexMatchesXsDate);
2280+
2281+
if (!matched) {
2282+
return false;
2283+
}
2284+
2285+
size_t cursor = 0;
2286+
if (text[0] == L'-') {
2287+
cursor = 1;
2288+
}
2289+
2290+
while (std::isdigit(text[cursor])) {
2291+
++cursor;
2292+
}
2293+
2294+
if (text[cursor] != L'-') {
2295+
throw std::logic_error(
2296+
common::Concat(
2297+
"Expected a dash after a year, but got the date text: ",
2298+
common::WstringToUtf8(text)
2299+
)
2300+
);
2301+
}
2302+
++cursor;
2303+
2304+
while (std::isdigit(text[cursor])) {
2305+
++cursor;
2306+
}
2307+
2308+
if (text[cursor] != L'-') {
2309+
throw std::logic_error(
2310+
common::Concat(
2311+
"Expected a dash after a month, but got the date text: ",
2312+
common::WstringToUtf8(text)
2313+
)
2314+
);
2315+
}
2316+
++cursor;
2317+
2318+
while (std::isdigit(text[cursor])) {
2319+
++cursor;
2320+
}
2321+
2322+
const std::wstring date_without_offset(
2323+
text.substr(0, cursor)
2324+
);
2325+
2326+
return IsXsDateWithoutOffset(date_without_offset);
2327+
}
2328+
22422329
bool IsXsDouble(const std::wstring& value) {
22432330
// NOTE (mristin):
22442331
// We need to check explicitly for the regular expression since

test_data/cpp/test_main/aas_core_meta.v3/expected_output/verification.hpp

+11-10
Original file line numberDiff line numberDiff line change
@@ -240,16 +240,6 @@ bool MatchesXsDateTimeUtc(
240240
const std::wstring& text
241241
);
242242

243-
/**
244-
* \brief Check whether the given \p year is a leap year.
245-
*
246-
* Year 1 BCE is a leap year.
247-
*
248-
* \param year to be checked
249-
* \return true if \p year is a leap year
250-
*/
251-
bool IsLeapYear(long long year);
252-
253243
/**
254244
* \brief Check that \p text is a `xs:dateTime` with time zone set to UTC.
255245
*
@@ -561,6 +551,17 @@ bool ValueConsistentWithXsdType(
561551
types::DataTypeDefXsd value_type
562552
);
563553

554+
/**
555+
* \brief Check that \p value is a valid `xs:date`.
556+
*
557+
* Year 1 BCE is the last leap BCE year.
558+
* See: https://www.w3.org/TR/xmlschema-2/#dateTime.
559+
*
560+
* \param value to be checked
561+
* \return true if \p value is a valid `xs:date`
562+
*/
563+
bool IsXsDate(const std::wstring& text);
564+
564565
/**
565566
* \brief Check that \p value is a valid `xs:double`.
566567
*

test_data/cpp/test_main/aas_core_meta.v3/input/snippets/verification/is_xs_date_time.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,5 @@ bool IsXsDateTime(
2323
// should be used here.
2424
std::wstring date = text.substr(0, pos);
2525

26-
return IsXsDate(date);
26+
return IsXsDateWithoutOffset(date);
2727
}

test_data/cpp/test_main/aas_core_meta.v3/input/snippets/verification/is_xs_date_time_UTC.cpp

+55-22
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,46 @@
11
const std::wregex kRegexDatePrefix(
2-
L"^(-?[0-9]+)-(0[1-9]|11|12)-(0[0-9]|1[0-9]|2[0-9]|30|31)"
2+
L"^(-?[0-9]+)-(0[1-9]|1[0-2])-(0[0-9]|1[0-9]|2[0-9]|30|31)"
33
);
44

5+
template<
6+
typename T,
7+
std::enable_if<
8+
std::is_integral<T>::value
9+
|| std::is_same<T, BigInt>::value
10+
>* = nullptr
11+
>
12+
bool IsLeapYear(T year) {
13+
// NOTE (mristin):
14+
// We consider the years B.C. to be one-off.
15+
// See the note at: https://www.w3.org/TR/xmlschema-2/#dateTime:
16+
// "'-0001' is the lexical representation of the year 1 Before Common Era
17+
// (1 BCE, sometimes written "1 BC")."
18+
//
19+
// Hence, -1 year in XML is 1 BCE, which is 0 year in astronomical years.
20+
if (year < 0) {
21+
year = -year - 1;
22+
}
23+
24+
// See: See: https://en.wikipedia.org/wiki/Leap_year#Algorithm
25+
if (year % 4 > 0)
26+
{
27+
return false;
28+
}
29+
30+
if (year % 100 > 0)
31+
{
32+
return true;
33+
}
34+
35+
if (year % 400 > 0)
36+
{
37+
return false;
38+
}
39+
40+
return true;
41+
}
42+
43+
544
bool IsLeapYear(long long year) {
645
// NOTE (mristin):
746
// We consider the years B.C. to be one-off.
@@ -51,17 +90,15 @@ const std::map<int, int> kDaysInMonth = {
5190
};
5291

5392
/**
54-
* \brief Check that \p value is a valid `xs:date`.
93+
* \brief Check that \p value is a valid `xs:date` without the offset.
5594
*
5695
* Year 1 BCE is the last leap BCE year.
5796
* See: https://www.w3.org/TR/xmlschema-2/#dateTime.
5897
*
5998
* \param value to be checked
6099
* \return true if \p value is a valid `xs:date`
61100
*/
62-
bool IsXsDate(
63-
const std::wstring& text
64-
) {
101+
bool IsXsDateWithoutOffset(const std::wstring& text) {
65102
// NOTE (mristin):
66103
// We can not use date functions from the operation system as they do not
67104
// handle years BCE (*e.g.*, `-0003-01-02`).
@@ -80,10 +117,14 @@ bool IsXsDate(
80117
// difficult. Hence, we sacrifice the efficiency a bit for the clearer code & code
81118
// generation.
82119

83-
long long year;
120+
bool is_zero_year;
121+
bool is_leap_year;
84122

85123
try {
86-
year = std::stoll(match[1].str());
124+
const long long year = std::stoll(match[1].str());
125+
126+
is_zero_year = year == 0;
127+
is_leap_year = IsLeapYear<long long>(year);
87128
} catch (const std::invalid_argument&) {
88129
std::wstringstream wss;
89130
wss
@@ -94,19 +135,11 @@ bool IsXsDate(
94135
common::WstringToUtf8(wss.str())
95136
);
96137
} catch (const std::out_of_range&) {
97-
std::wstringstream wss;
98-
wss
99-
<< "The year is out of range for long long integers: "
100-
<< match[1].str()
101-
<< (
102-
"; we at aas-core-works planned to include handling of BigInt years "
103-
"in the SDK, but eventually lacked the time for it. Please let the developers "
104-
"know that you need this feature."
105-
);
106-
107-
throw std::out_of_range(
108-
common::WstringToUtf8(wss.str())
138+
const BigInt year(
139+
common::WstringToUtf8(match[1].str())
109140
);
141+
is_zero_year = year == 0;
142+
is_leap_year = IsLeapYear<BigInt>(std::move(year));
110143
}
111144

112145
const int month = std::stoi(match[2].str());
@@ -115,7 +148,7 @@ bool IsXsDate(
115148
// NOTE (mristin):
116149
// We do not accept year zero, see the note at:
117150
// https://www.w3.org/TR/xmlschema-2/#dateTime
118-
if (year == 0) {
151+
if (is_zero_year) {
119152
return false;
120153
}
121154

@@ -129,7 +162,7 @@ bool IsXsDate(
129162

130163
const int max_days(
131164
(month == 2)
132-
? (IsLeapYear(year) ? 29 : 28)
165+
? (is_leap_year ? 29 : 28)
133166
: kDaysInMonth.at(month)
134167
);
135168

@@ -165,5 +198,5 @@ bool IsXsDateTimeUtc(
165198
// should be used here.
166199
std::wstring date = text.substr(0, pos);
167200

168-
return IsXsDate(date);
201+
return IsXsDateWithoutOffset(date);
169202
}

0 commit comments

Comments
 (0)