From be4af9dd8af43d66df35e1a8d094d17008eb0f92 Mon Sep 17 00:00:00 2001 From: vsian <vsian_v@163.com> Date: Wed, 4 Sep 2024 14:56:50 +0800 Subject: [PATCH] add a date type DateStdT implementation using only std::chrono types --- src/common/stl.cppm | 11 +- src/parser/type/datetime/date_type_std.cpp | 270 ++++++++++++++---- src/parser/type/datetime/date_type_std.h | 60 ++++ src/parser/type/internal_types.cppm | 1 + src/parser/type/internal_types.h | 2 + .../parser/type/datetime/date_type_std.cpp | 95 ++++++ 6 files changed, 378 insertions(+), 61 deletions(-) create mode 100644 src/unit_test/parser/type/datetime/date_type_std.cpp diff --git a/src/common/stl.cppm b/src/common/stl.cppm index c51bb86ef2..ab1c3bd59a 100644 --- a/src/common/stl.cppm +++ b/src/common/stl.cppm @@ -192,9 +192,7 @@ export namespace std { using std::chrono::steady_clock; using std::chrono::time_point; - using std::chrono::year_month_day; - using std::chrono::sys_days; - using std::chrono::system_clock; + using std::chrono::ceil; using std::chrono::days; using std::tm; @@ -202,6 +200,13 @@ export namespace std { using std::mktime; using std::format; + using std::chrono::year; + using std::chrono::month; + using std::chrono::day; + + using std::chrono::year_month_day; + using std::chrono::sys_days; + using std::chrono::system_clock; } // namespace chrono using std::cout; diff --git a/src/parser/type/datetime/date_type_std.cpp b/src/parser/type/datetime/date_type_std.cpp index 94c01c5ea1..3714ebd07a 100644 --- a/src/parser/type/datetime/date_type_std.cpp +++ b/src/parser/type/datetime/date_type_std.cpp @@ -1,60 +1,214 @@ -#pragma once - -#include "interval_type.h" -#include <string> - -namespace infinity { - -struct DateTypeStd { - friend struct DateTimeType; - - DateType() = default; - - explicit constexpr DateType(int32_t date_value) : value(date_value){}; - - inline int32_t GetValue() const { return value; } - - operator int32_t() const { return value; } - - inline void FromString(const std::string_view &date_str) { FromString(date_str.data(), date_str.size()); } - - void FromString(const char *date, size_t length); - - void FromString(const char *date, size_t length, size_t &end_length); - - [[nodiscard]] std::string ToString() const; - - int32_t value{0}; - -private: - static bool ConvertFromString(const char *date_ptr, size_t length, DateType &date, size_t &end_length); - - static bool YMD2Date(int32_t year, int32_t month, int32_t day, DateType &date); - - static bool Date2YMD(int32_t days, int32_t &year, int32_t &month, int32_t &day); - - inline static bool IsLeapYear(int32_t year) { return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); } - - static bool IsDateValid(int32_t year, int32_t month, int32_t day); - -public: - inline bool operator==(const DateType &other) const { return this->value == other.value; } - - inline bool operator>=(const DateType &other) const { return this->value >= other.value; } - - inline bool operator>(const DateType &other) const { return this->value > other.value; } - - inline bool operator<=(const DateType &other) const { return this->value <= other.value; } - - inline bool operator<(const DateType &other) const { return this->value < other.value; } - -public: - // Operation - static bool Add(DateType input, IntervalType interval, DateType &output); - - static bool Subtract(DateType input, IntervalType interval, DateType &output); - - static int64_t GetDatePart(DateType input, TimeUnit unit); -}; +#include "date_type_std.h" +#include "parser_assert.h" +#include <format> +#include <chrono> + +constexpr static int32_t DAY_HOUR = 24; +constexpr static int32_t DAY_MINUTE = DAY_HOUR * 60; +constexpr static int32_t DAY_SECOND = DAY_MINUTE * 60; + +namespace infinity{ + +void DateTypeStd::FromString(const char *date_ptr, size_t length) { + size_t end_length_unused; + if (!ConvertFromString(date_ptr, length, *this, end_length_unused)) { + ParserError("Invalid date format (YYYY-MM-DD or YYYY/MM/DD)."); + } +} + +void DateTypeStd::FromString(const char *date_ptr, size_t length, size_t &end_length) { + if (!ConvertFromString(date_ptr, length, *this, end_length)) { + ParserError("Invalid date format (YYYY-MM-DD or YYYY/MM/DD)."); + } +} + +std::string DateTypeStd::ToString() const { + int32_t year{0}, month{0}, day{0}; + if (!Date2YMD(value, year, month, day)) { + ParserError(std::format("Invalid date: {}-{}-{}", year, month, day)); + } + // TODO: format for negative year? + return std::format("{:04d}-{:02d}-{:02d}", year, month, day); +} + +bool DateTypeStd::ConvertFromString(const char *date_ptr, size_t length, DateTypeStd &date, size_t &end_length) { + // trim the string + size_t pos{0}; + + // skip spaces + while (pos < length && std::isspace(date_ptr[pos])) { + ++pos; + } + + // Get year + int32_t year{0}; + bool negative_year{false}; + if (date_ptr[pos] == '-') { + negative_year = true; + ++pos; + } + while (pos < length && std::isdigit(date_ptr[pos])) { + if (std::isspace(date_ptr[pos])) { + ++pos; + continue; + } + if (std::isdigit(date_ptr[pos])) { + year = year * 10 + (date_ptr[pos] - '0'); + ++pos; + // if (year > 9999) + // return false; + continue; + } + break; + } + if (negative_year) { + year = -year; + } + if (date_ptr[pos] != '-' && date_ptr[pos] != '/') { + return false; + } + ++pos; // skip - and / + + // Get month + int32_t month{0}; + while (pos < length && std::isdigit(date_ptr[pos])) { + if (std::isspace(date_ptr[pos])) { + ++pos; + continue; + } + if (std::isdigit(date_ptr[pos])) { + month = month * 10 + (date_ptr[pos] - '0'); + ++pos; + // if (month > 12) + // return false; + continue; + } + break; + } + if (date_ptr[pos] != '-' && date_ptr[pos] != '/') { + return false; + } + ++pos; // skip - and / + + // Get day + int32_t day{0}; + while (pos < length && std::isdigit(date_ptr[pos])) { + if (std::isspace(date_ptr[pos])) { + ++pos; + continue; + } + if (std::isdigit(date_ptr[pos])) { + day = day * 10 + (date_ptr[pos] - '0'); + ++pos; + // if (day > 31) + // return false; + continue; + } + break; + } + end_length = pos; + + return YMD2Date(year, month, day, date); +} + +bool DateTypeStd::YMD2Date(int32_t year, int32_t month, int32_t day, DateTypeStd &date) { + std::chrono::year_month_day ymd ( + std::chrono::year{year}, + std::chrono::month{static_cast<unsigned int>(month)}, + std::chrono::day{static_cast<unsigned int>(day)} + ); + auto sd = std::chrono::sys_days{ymd}; + date.value = sd.time_since_epoch().count(); + return ymd.ok(); +} + +bool DateTypeStd::Date2YMD(int32_t days, int32_t &year, int32_t &month, int32_t &day) { + auto sd = std::chrono::sys_days{std::chrono::days{days}}; + auto ymd = std::chrono::year_month_day{sd}; + + year = static_cast<int>(ymd.year()); + month = static_cast<unsigned>(ymd.month()); + day = static_cast<unsigned>(ymd.day()); + return IsDateValid(year, month, day); +} + +bool DateTypeStd::IsDateValid(int32_t year, int32_t month, int32_t day) { + DateTypeStd date; + return YMD2Date(year, month, day, date); +} + +bool DateTypeStd::Add(DateTypeStd input, IntervalType interval, DateTypeStd &output) { + int32_t year{0}, month{0}, day{0}; + if (!Date2YMD(input.value, year, month, day)) { + return false; + } + switch (interval.unit) { + case kYear: { + std::chrono::sys_days output_sd(std::chrono::days{input.value} + std::chrono::duration_cast<std::chrono::days>(std::chrono::years{interval.value})); + output.value = output_sd.time_since_epoch().count(); + return true; + } + case kMonth: { + std::chrono::sys_days output_sd(std::chrono::days{input.value} + std::chrono::duration_cast<std::chrono::days>(std::chrono::months{interval.value})); + output.value = output_sd.time_since_epoch().count(); + return true; + } + case kDay: { + output.value = input.value + interval.value; + return true; + } + case kHour: { + output.value = input.value + (interval.value / DAY_HOUR); + return true; + } + case kMinute: { + output.value = input.value + (interval.value / DAY_MINUTE); + return true; + } + case kSecond: { + output.value = input.value + (interval.value / DAY_SECOND); + return true; + } + case kInvalidUnit: { + ParserError("Invalid interval unit."); + } + } + return false; +} + +bool DateTypeStd::Subtract(DateTypeStd input, IntervalType interval, DateTypeStd &output) { + interval.value = -interval.value; + return Add(input, interval, output); +} + +int64_t DateTypeStd::GetDatePart(DateTypeStd input, TimeUnit unit) { + int32_t year{}, month{}, day{}; + auto result = Date2YMD(input.value, year, month, day); + ParserAssert(result, "Invalid date value"); + switch (unit) { + case TimeUnit::kYear: { + return year; + } + case TimeUnit::kMonth: { + return month; + } + case TimeUnit::kDay: { + return day; + } + case TimeUnit::kHour: { + ParserError("Can't extract hour from date"); + } + case TimeUnit::kMinute: { + ParserError("Can't extract minute from date"); + } + case TimeUnit::kSecond: { + ParserError("Can't extract second from date"); + } + default: { + ParserError("Invalid time unit"); + } + } + return -1; +} } \ No newline at end of file diff --git a/src/parser/type/datetime/date_type_std.h b/src/parser/type/datetime/date_type_std.h index e69de29bb2..873cf643cb 100644 --- a/src/parser/type/datetime/date_type_std.h +++ b/src/parser/type/datetime/date_type_std.h @@ -0,0 +1,60 @@ +#pragma once + +#include "interval_type.h" +#include <string> + +namespace infinity { + +struct DateTypeStd { + friend struct DateTimeType; + + DateTypeStd() = default; + + explicit constexpr DateTypeStd(int32_t date_value) : value(date_value){}; + + inline int32_t GetValue() const { return value; } + + operator int32_t() const { return value; } + + inline void FromString(const std::string_view &date_str) { FromString(date_str.data(), date_str.size()); } + + void FromString(const char *date, size_t length); + + void FromString(const char *date, size_t length, size_t &end_length); + + [[nodiscard]] std::string ToString() const; + + int32_t value{0}; + +private: + static bool ConvertFromString(const char *date_ptr, size_t length, DateTypeStd &date, size_t &end_length); + + static bool YMD2Date(int32_t year, int32_t month, int32_t day, DateTypeStd &date); + + static bool Date2YMD(int32_t days, int32_t &year, int32_t &month, int32_t &day); + + inline static bool IsLeapYear(int32_t year) { return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); } + + static bool IsDateValid(int32_t year, int32_t month, int32_t day); + +public: + inline bool operator==(const DateTypeStd &other) const { return this->value == other.value; } + + inline bool operator>=(const DateTypeStd &other) const { return this->value >= other.value; } + + inline bool operator>(const DateTypeStd &other) const { return this->value > other.value; } + + inline bool operator<=(const DateTypeStd &other) const { return this->value <= other.value; } + + inline bool operator<(const DateTypeStd &other) const { return this->value < other.value; } + +public: + // Operation + static bool Add(DateTypeStd input, IntervalType interval, DateTypeStd &output); + + static bool Subtract(DateTypeStd input, IntervalType interval, DateTypeStd &output); + + static int64_t GetDatePart(DateTypeStd input, TimeUnit unit); +}; + +} \ No newline at end of file diff --git a/src/parser/type/internal_types.cppm b/src/parser/type/internal_types.cppm index 386643639b..2d860f6e8d 100644 --- a/src/parser/type/internal_types.cppm +++ b/src/parser/type/internal_types.cppm @@ -38,6 +38,7 @@ export using infinity::DateT; export using infinity::TimeT; export using infinity::DateTimeT; +export using infinity::DateStdT; export using infinity::TimestampT; export using infinity::IntervalT; diff --git a/src/parser/type/internal_types.h b/src/parser/type/internal_types.h index bcc23188fc..404351de91 100644 --- a/src/parser/type/internal_types.h +++ b/src/parser/type/internal_types.h @@ -15,6 +15,7 @@ #pragma once +#include "type/datetime/date_type_std.h" #include "type/number/float16.h" #include "type/number/bfloat16.h" #include "type/number/huge_int.h" @@ -64,6 +65,7 @@ using VarcharT = Varchar; // Date and Time using DateT = DateType; +using DateStdT = DateTypeStd; using TimeT = TimeType; using DateTimeT = DateTimeType; using TimestampT = TimestampType; diff --git a/src/unit_test/parser/type/datetime/date_type_std.cpp b/src/unit_test/parser/type/datetime/date_type_std.cpp new file mode 100644 index 0000000000..119151d350 --- /dev/null +++ b/src/unit_test/parser/type/datetime/date_type_std.cpp @@ -0,0 +1,95 @@ +#include "type/datetime/interval_type.h" +#include "gtest/gtest.h" + +import base_test; + +import infinity_exception; + +import global_resource_usage; +import third_party; + +import logger; +import stl; +import infinity_context; +import internal_types; +import parser_assert; + +using namespace infinity; +class DateTypeStdTest : public BaseTest {}; + +TEST_F(DateTypeStdTest, TestSameSize) { + using namespace infinity; + EXPECT_EQ(sizeof(DateT), sizeof(DateStdT)); +} + +TEST_F(DateTypeStdTest, TestFromString) { + using namespace infinity; + DateStdT date_std; + EXPECT_NO_THROW(date_std.FromString("2020-01-30")); + EXPECT_NO_THROW(date_std.FromString("4535-02-11")); + EXPECT_NO_THROW(date_std.FromString("2002-05-26")); + EXPECT_NO_THROW(date_std.FromString("-1587-8-1")); + EXPECT_NO_THROW(date_std.FromString("-4532/1/23")); + EXPECT_NO_THROW(date_std.FromString("2024/9/4")); + EXPECT_NO_THROW(date_std.FromString("2024-9/4")); + EXPECT_NO_THROW(date_std.FromString("2024/9-4")); + + EXPECT_THROW(date_std.FromString("2018/2/29"), ParserException); + EXPECT_THROW(date_std.FromString("20-1233/45"), ParserException); + EXPECT_THROW(date_std.FromString("-12354--56f1--ade"), ParserException); + EXPECT_THROW(date_std.FromString("1234@56.789"), ParserException); + EXPECT_THROW(date_std.FromString("qwlssmabz"), ParserException); +} + +TEST_F(DateTypeStdTest, TestAddSubstract) { + using namespace infinity; + DateStdT date_std_input; + DateStdT date_std_output; + IntervalT interval; + + EXPECT_NO_THROW(date_std_input.FromString("2020-01-30")); + EXPECT_EQ(date_std_input.ToString(), "2020-01-30"); + + interval.value = 1; + interval.unit = kMonth; + EXPECT_TRUE(DateStdT::Add(date_std_input, interval, date_std_output)); + EXPECT_EQ(date_std_output.ToString(), "2020-02-29"); + + interval.value = 1; + interval.unit = kYear; + EXPECT_TRUE(DateStdT::Add(date_std_input, interval, date_std_output)); + EXPECT_EQ(date_std_output.ToString(), "2021-01-29"); +} + +// TEST_F(DateTypeStdTest, TestLeapYear) { +// using namespace infinity; +// } + +TEST_F(DateTypeStdTest, TestNegativeYears) { + using namespace infinity; + DateStdT date; + DateStdT date_shift; + EXPECT_NO_THROW(date.FromString("-1/5/4")); + + IntervalT interval; + interval.unit = kYear; + interval.value = 3; + + EXPECT_EQ(date.ToString(), "-001-05-04"); + EXPECT_EQ(DateStdT::GetDatePart(date, kYear), -1); + EXPECT_EQ(DateStdT::GetDatePart(date, kMonth), 5); + EXPECT_EQ(DateStdT::GetDatePart(date, kDay), 4); + + EXPECT_TRUE(DateStdT::Add(date, interval, date_shift)); + EXPECT_EQ(date_shift.ToString(), "0002-05-03"); + + EXPECT_TRUE(DateStdT::Subtract(date_shift, interval, date_shift)); + EXPECT_EQ(date_shift.ToString(), "-001-05-04"); +} + +// TEST_F(DateTypeStdTest, TestComparator) { +// using namespace infinity; +// DateStdT d1; +// DateStdT d2; + +// } \ No newline at end of file