Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix C++ code to deal with date and date-times #451

Merged
merged 1 commit into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading