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