Skip to content

Commit

Permalink
Fix C++ code to deal with date and date-times (#451)
Browse files Browse the repository at this point in the history
We fix the generated code and the test snippets so that we pass the
related verification tests in the C++ SDK.
  • Loading branch information
mristin authored Mar 6, 2024
1 parent a206fe8 commit e90f027
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 66 deletions.
2 changes: 2 additions & 0 deletions aas_core_codegen/cpp/verification/_generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -3270,6 +3270,8 @@ def generate_implementation(
cpp_common.WARNING,
Stripped(
f"""\
#include "./BigInt.hpp"
#include "{include_prefix_path}/common.hpp"
#include "{include_prefix_path}/constants.hpp"
#include "{include_prefix_path}/verification.hpp"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// This code has been automatically generated by aas-core-codegen.
// Do NOT edit or append.

#include "./BigInt.hpp"
#include "aas_core/aas_3_0/common.hpp"
#include "aas_core/aas_3_0/constants.hpp"
#include "aas_core/aas_3_0/verification.hpp"

Expand Down Expand Up @@ -220,9 +222,48 @@ bool MatchesXsDateTimeUtc(
}

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

template<
typename T,
std::enable_if<
std::is_integral<T>::value
|| std::is_same<T, BigInt>::value
>* = nullptr
>
bool IsLeapYear(T year) {
// NOTE (mristin):
// We consider the years B.C. to be one-off.
// See the note at: https://www.w3.org/TR/xmlschema-2/#dateTime:
// "'-0001' is the lexical representation of the year 1 Before Common Era
// (1 BCE, sometimes written "1 BC")."
//
// Hence, -1 year in XML is 1 BCE, which is 0 year in astronomical years.
if (year < 0) {
year = -year - 1;
}

// See: See: https://en.wikipedia.org/wiki/Leap_year#Algorithm
if (year % 4 > 0)
{
return false;
}

if (year % 100 > 0)
{
return true;
}

if (year % 400 > 0)
{
return false;
}

return true;
}


bool IsLeapYear(long long year) {
// NOTE (mristin):
// We consider the years B.C. to be one-off.
Expand Down Expand Up @@ -272,17 +313,15 @@ const std::map<int, int> kDaysInMonth = {
};

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

long long year;
bool is_zero_year;
bool is_leap_year;

try {
year = std::stoll(match[1].str());
const long long year = std::stoll(match[1].str());

is_zero_year = year == 0;
is_leap_year = IsLeapYear<long long>(year);
} catch (const std::invalid_argument&) {
std::wstringstream wss;
wss
Expand All @@ -315,19 +358,11 @@ bool IsXsDate(
common::WstringToUtf8(wss.str())
);
} catch (const std::out_of_range&) {
std::wstringstream wss;
wss
<< "The year is out of range for long long integers: "
<< match[1].str()
<< (
"; we at aas-core-works planned to include handling of BigInt years "
"in the SDK, but eventually lacked the time for it. Please let the developers "
"know that you need this feature."
);

throw std::out_of_range(
common::WstringToUtf8(wss.str())
const BigInt year(
common::WstringToUtf8(match[1].str())
);
is_zero_year = year == 0;
is_leap_year = IsLeapYear<BigInt>(std::move(year));
}

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

Expand All @@ -350,7 +385,7 @@ bool IsXsDate(

const int max_days(
(month == 2)
? (IsLeapYear(year) ? 29 : 28)
? (is_leap_year ? 29 : 28)
: kDaysInMonth.at(month)
);

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

return IsXsDate(date);
return IsXsDateWithoutOffset(date);
}

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

return IsXsDate(date);
return IsXsDateWithoutOffset(date);
}

std::wregex ConstructMatchesXsDecimal() {
Expand Down Expand Up @@ -2239,6 +2274,58 @@ bool MatchesXsString(
);
}

bool IsXsDate(const std::wstring& text) {
std::wsmatch match;
const bool matched = std::regex_match(text, match, kRegexMatchesXsDate);

if (!matched) {
return false;
}

size_t cursor = 0;
if (text[0] == L'-') {
cursor = 1;
}

while (std::isdigit(text[cursor])) {
++cursor;
}

if (text[cursor] != L'-') {
throw std::logic_error(
common::Concat(
"Expected a dash after a year, but got the date text: ",
common::WstringToUtf8(text)
)
);
}
++cursor;

while (std::isdigit(text[cursor])) {
++cursor;
}

if (text[cursor] != L'-') {
throw std::logic_error(
common::Concat(
"Expected a dash after a month, but got the date text: ",
common::WstringToUtf8(text)
)
);
}
++cursor;

while (std::isdigit(text[cursor])) {
++cursor;
}

const std::wstring date_without_offset(
text.substr(0, cursor)
);

return IsXsDateWithoutOffset(date_without_offset);
}

bool IsXsDouble(const std::wstring& value) {
// NOTE (mristin):
// We need to check explicitly for the regular expression since
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,16 +240,6 @@ bool MatchesXsDateTimeUtc(
const std::wstring& text
);

/**
* \brief Check whether the given \p year is a leap year.
*
* Year 1 BCE is a leap year.
*
* \param year to be checked
* \return true if \p year is a leap year
*/
bool IsLeapYear(long long year);

/**
* \brief Check that \p text is a `xs:dateTime` with time zone set to UTC.
*
Expand Down Expand Up @@ -561,6 +551,17 @@ bool ValueConsistentWithXsdType(
types::DataTypeDefXsd value_type
);

/**
* \brief Check that \p value is a valid `xs:date`.
*
* Year 1 BCE is the last leap BCE year.
* See: https://www.w3.org/TR/xmlschema-2/#dateTime.
*
* \param value to be checked
* \return true if \p value is a valid `xs:date`
*/
bool IsXsDate(const std::wstring& text);

/**
* \brief Check that \p value is a valid `xs:double`.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ bool IsXsDateTime(
// should be used here.
std::wstring date = text.substr(0, pos);

return IsXsDate(date);
return IsXsDateWithoutOffset(date);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,46 @@
const std::wregex kRegexDatePrefix(
L"^(-?[0-9]+)-(0[1-9]|11|12)-(0[0-9]|1[0-9]|2[0-9]|30|31)"
L"^(-?[0-9]+)-(0[1-9]|1[0-2])-(0[0-9]|1[0-9]|2[0-9]|30|31)"
);

template<
typename T,
std::enable_if<
std::is_integral<T>::value
|| std::is_same<T, BigInt>::value
>* = nullptr
>
bool IsLeapYear(T year) {
// NOTE (mristin):
// We consider the years B.C. to be one-off.
// See the note at: https://www.w3.org/TR/xmlschema-2/#dateTime:
// "'-0001' is the lexical representation of the year 1 Before Common Era
// (1 BCE, sometimes written "1 BC")."
//
// Hence, -1 year in XML is 1 BCE, which is 0 year in astronomical years.
if (year < 0) {
year = -year - 1;
}

// See: See: https://en.wikipedia.org/wiki/Leap_year#Algorithm
if (year % 4 > 0)
{
return false;
}

if (year % 100 > 0)
{
return true;
}

if (year % 400 > 0)
{
return false;
}

return true;
}


bool IsLeapYear(long long year) {
// NOTE (mristin):
// We consider the years B.C. to be one-off.
Expand Down Expand Up @@ -51,17 +90,15 @@ const std::map<int, int> kDaysInMonth = {
};

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

long long year;
bool is_zero_year;
bool is_leap_year;

try {
year = std::stoll(match[1].str());
const long long year = std::stoll(match[1].str());

is_zero_year = year == 0;
is_leap_year = IsLeapYear<long long>(year);
} catch (const std::invalid_argument&) {
std::wstringstream wss;
wss
Expand All @@ -94,19 +135,11 @@ bool IsXsDate(
common::WstringToUtf8(wss.str())
);
} catch (const std::out_of_range&) {
std::wstringstream wss;
wss
<< "The year is out of range for long long integers: "
<< match[1].str()
<< (
"; we at aas-core-works planned to include handling of BigInt years "
"in the SDK, but eventually lacked the time for it. Please let the developers "
"know that you need this feature."
);

throw std::out_of_range(
common::WstringToUtf8(wss.str())
const BigInt year(
common::WstringToUtf8(match[1].str())
);
is_zero_year = year == 0;
is_leap_year = IsLeapYear<BigInt>(std::move(year));
}

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

Expand All @@ -129,7 +162,7 @@ bool IsXsDate(

const int max_days(
(month == 2)
? (IsLeapYear(year) ? 29 : 28)
? (is_leap_year ? 29 : 28)
: kDaysInMonth.at(month)
);

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

return IsXsDate(date);
return IsXsDateWithoutOffset(date);
}
Loading

0 comments on commit e90f027

Please sign in to comment.