From 91759b3e3f1878cae74e90b9e7a392b6053a1a76 Mon Sep 17 00:00:00 2001 From: Karim Shamazov Date: Thu, 13 Nov 2025 12:19:01 +0300 Subject: [PATCH 01/21] stash --- runtime-light/stdlib/stdlib.cmake | 1 + .../stdlib/time/datetime-interface.h | 16 ++++++++++ runtime-light/stdlib/time/datetime.cpp | 12 +++++++ runtime-light/stdlib/time/datetime.h | 32 +++++++++++++++++++ 4 files changed, 61 insertions(+) create mode 100644 runtime-light/stdlib/time/datetime-interface.h create mode 100644 runtime-light/stdlib/time/datetime.cpp create mode 100644 runtime-light/stdlib/time/datetime.h diff --git a/runtime-light/stdlib/stdlib.cmake b/runtime-light/stdlib/stdlib.cmake index 824e92cd33..0f6d897e2f 100644 --- a/runtime-light/stdlib/stdlib.cmake +++ b/runtime-light/stdlib/stdlib.cmake @@ -38,6 +38,7 @@ prepend( string/string-state.cpp system/system-functions.cpp system/system-state.cpp + time/datetime.cpp time/time-functions.cpp time/time-state.cpp time/timelib-functions.cpp diff --git a/runtime-light/stdlib/time/datetime-interface.h b/runtime-light/stdlib/time/datetime-interface.h new file mode 100644 index 0000000000..906332aad8 --- /dev/null +++ b/runtime-light/stdlib/time/datetime-interface.h @@ -0,0 +1,16 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2022 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include "kphp/timelib/timelib.h" + +#include "runtime-common/core/class-instance/refcountable-php-classes.h" + +struct C$DateTimeInterface : public refcountable_polymorphic_php_classes_virt<> { + timelib_time* time{nullptr}; + + virtual const char* get_class() const noexcept = 0; + virtual int get_hash() const noexcept = 0; +}; diff --git a/runtime-light/stdlib/time/datetime.cpp b/runtime-light/stdlib/time/datetime.cpp new file mode 100644 index 0000000000..3ea84ee742 --- /dev/null +++ b/runtime-light/stdlib/time/datetime.cpp @@ -0,0 +1,12 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2020 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#include "runtime-light/stdlib/time/datetime.h" + +#include "runtime-light/allocator/allocator.h" + +C$DateTime::~C$DateTime() { + kphp::memory::libc_alloc_guard{}, timelib_time_dtor(time); + time = nullptr; +} diff --git a/runtime-light/stdlib/time/datetime.h b/runtime-light/stdlib/time/datetime.h new file mode 100644 index 0000000000..e00df2e17a --- /dev/null +++ b/runtime-light/stdlib/time/datetime.h @@ -0,0 +1,32 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2022 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include +#include + +#include "common/algorithms/hashes.h" +#include "runtime-common/core/runtime-core.h" +#include "runtime-common/stdlib/visitors/dummy-visitor-methods.h" +#include "runtime-light/stdlib/time/datetime-interface.h" + +struct C$DateTime : public C$DateTimeInterface, private DummyVisitorMethods { + using DummyVisitorMethods::accept; + + const char* get_class() const noexcept override { + return R"(DateTime)"; + } + + int32_t get_hash() const noexcept override { + std::string_view name_view{C$DateTime::get_class()}; + return static_cast(vk::murmur_hash(name_view.data(), name_view.size())); + } + + ~C$DateTime() override; +}; + +constexpr std::string_view NOW{"now"}; + + From c35762c9104f8ef8e4a9d49c808db7e64c8e20fd Mon Sep 17 00:00:00 2001 From: Karim Shamazov Date: Thu, 13 Nov 2025 12:19:17 +0300 Subject: [PATCH 02/21] stash --- runtime-light/stdlib/time/datetime.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/runtime-light/stdlib/time/datetime.h b/runtime-light/stdlib/time/datetime.h index e00df2e17a..ca1dc52af5 100644 --- a/runtime-light/stdlib/time/datetime.h +++ b/runtime-light/stdlib/time/datetime.h @@ -28,5 +28,3 @@ struct C$DateTime : public C$DateTimeInterface, private DummyVisitorMethods { }; constexpr std::string_view NOW{"now"}; - - From 01f2476bb5ab3a67d41d2ff0a56f31e5c9088f38 Mon Sep 17 00:00:00 2001 From: Karim Shamazov Date: Wed, 10 Dec 2025 16:09:54 +0300 Subject: [PATCH 03/21] stash --- .../kphp-light/stdlib/time-functions.txt | 68 ++- .../core/core-types/decl/string_decl.inl | 1 + runtime-common/stdlib/string/string-context.h | 3 + runtime-light/stdlib/stdlib.cmake | 5 +- runtime-light/stdlib/time/dateinterval.cpp | 53 +++ runtime-light/stdlib/time/dateinterval.h | 37 ++ runtime-light/stdlib/time/datetime.cpp | 35 +- runtime-light/stdlib/time/datetime.h | 32 +- .../stdlib/time/datetimeimmutable.cpp | 139 ++++++ runtime-light/stdlib/time/datetimeimmutable.h | 56 +++ ...tetime-interface.h => datetimeinterface.h} | 3 +- runtime-light/stdlib/time/datetimezone.cpp | 25 ++ runtime-light/stdlib/time/datetimezone.h | 34 ++ runtime-light/stdlib/time/time-functions.cpp | 12 +- runtime-light/stdlib/time/time-functions.h | 10 +- .../stdlib/time/timelib-functions.cpp | 267 ++++++++++- runtime-light/stdlib/time/timelib-functions.h | 419 ++++++++++++++++++ .../13_create_interval_from_date_string.php | 2 +- tests/phpt/datetime/15_interval_format.php | 2 +- 19 files changed, 1128 insertions(+), 75 deletions(-) create mode 100644 runtime-light/stdlib/time/dateinterval.cpp create mode 100644 runtime-light/stdlib/time/dateinterval.h create mode 100644 runtime-light/stdlib/time/datetimeimmutable.cpp create mode 100644 runtime-light/stdlib/time/datetimeimmutable.h rename runtime-light/stdlib/time/{datetime-interface.h => datetimeinterface.h} (86%) create mode 100644 runtime-light/stdlib/time/datetimezone.cpp create mode 100644 runtime-light/stdlib/time/datetimezone.h diff --git a/builtin-functions/kphp-light/stdlib/time-functions.txt b/builtin-functions/kphp-light/stdlib/time-functions.txt index 82c83195c0..47401950f5 100644 --- a/builtin-functions/kphp-light/stdlib/time-functions.txt +++ b/builtin-functions/kphp-light/stdlib/time-functions.txt @@ -30,27 +30,19 @@ function set_timer(int $timeout, callable():void $callback) ::: void; function checkdate ($month ::: int, $day ::: int, $year ::: int) ::: bool; -// ===== UNSUPPORTED ===== - -/** @kphp-generate-stub-class */ class DateInterval { - /** @kphp-extern-func-info can_throw stub generation-required */ + /** @kphp-extern-func-info can_throw */ public function __construct(string $duration); - /** @kphp-extern-func-info stub generation-required */ public static function createFromDateString(string $datetime): ?DateInterval; - /** @kphp-extern-func-info stub generation-required */ public function format(string $format): string; } -/** @kphp-generate-stub-class */ class DateTimeZone { - /** @kphp-extern-func-info can_throw stub generation-required */ + /** @kphp-extern-func-info can_throw */ public function __construct(string $timezone); - /** @kphp-extern-func-info stub generation-required */ public function getName(): string; } -/** @kphp-generate-stub-class */ interface DateTimeInterface { /* Constants */ const ATOM = "Y-m-d\TH:i:sP"; @@ -86,74 +78,64 @@ interface DateTimeInterface { public function getTimestamp(): int; } -/** @kphp-generate-stub-class */ -class DateTime implements DateTimeInterface { - /** @kphp-extern-func-info can_throw stub generation-required */ +class DateTimeImmutable implements DateTimeInterface { + /** @kphp-extern-func-info can_throw */ public function __construct(string $datetime = "now", ?DateTimeZone $timezone = null); - /** @kphp-extern-func-info stub generation-required */ - public function add(DateInterval $interval): DateTime; - /** @kphp-extern-func-info stub generation-required */ - public static function createFromFormat(string $format, string $datetime, ?DateTimeZone $timezone = null): ?DateTime; - /** @kphp-extern-func-info stub generation-required */ - public static function createFromImmutable(DateTimeImmutable $object): DateTime; + public function add(DateInterval $interval): DateTimeImmutable; + public static function createFromFormat(string $format, string $datetime, ?DateTimeZone $timezone = null): ?DateTimeImmutable; + public static function createFromMutable(DateTime $object): DateTimeImmutable; /** @kphp-extern-func-info stub generation-required */ public static function getLastErrors(): array|false; + public function modify(string $modifier): ?DateTimeImmutable; /** @kphp-extern-func-info stub generation-required */ - public function modify(string $modifier): ?DateTime; - /** @kphp-extern-func-info stub generation-required */ - public function setDate(int $year, int $month, int $day): DateTime; + public function setDate(int $year, int $month, int $day): DateTimeImmutable; /** @kphp-extern-func-info stub generation-required */ - public function setISODate(int $year, int $week, int $dayOfWeek = 1): DateTime; + public function setISODate(int $year, int $week, int $dayOfWeek = 1): DateTimeImmutable; /** @kphp-extern-func-info stub generation-required */ public function setTime( int $hour, int $minute, int $second = 0, int $microsecond = 0 - ): DateTime; - /** @kphp-extern-func-info stub generation-required */ - public function setTimestamp(int $timestamp): DateTime; - /** @kphp-extern-func-info stub generation-required */ - public function sub(DateInterval $interval): DateTime; + ): DateTimeImmutable; + public function setTimestamp(int $timestamp): DateTimeImmutable; /** @kphp-extern-func-info stub generation-required */ + public function sub(DateInterval $interval): DateTimeImmutable; public function diff(DateTimeInterface $targetObject, bool $absolute = false): DateInterval; - /** @kphp-extern-func-info stub generation-required */ public function format(string $format): string; /** @kphp-extern-func-info stub generation-required */ public function getOffset(): int; - /** @kphp-extern-func-info stub generation-required */ public function getTimestamp(): int; } -/** @kphp-generate-stub-class */ -class DateTimeImmutable implements DateTimeInterface { - /** @kphp-extern-func-info can_throw stub generation-required */ +class DateTime implements DateTimeInterface { + /** @kphp-extern-func-info can_throw */ public function __construct(string $datetime = "now", ?DateTimeZone $timezone = null); /** @kphp-extern-func-info stub generation-required */ - public function add(DateInterval $interval): DateTimeImmutable; + public function add(DateInterval $interval): DateTime; /** @kphp-extern-func-info stub generation-required */ - public static function createFromFormat(string $format, string $datetime, ?DateTimeZone $timezone = null): ?DateTimeImmutable; + public static function createFromFormat(string $format, string $datetime, ?DateTimeZone $timezone = null): ?DateTime; /** @kphp-extern-func-info stub generation-required */ - public static function createFromMutable(DateTime $object): DateTimeImmutable; + public static function createFromImmutable(DateTimeImmutable $object): DateTime; /** @kphp-extern-func-info stub generation-required */ public static function getLastErrors(): array|false; /** @kphp-extern-func-info stub generation-required */ - public function modify(string $modifier): ?DateTimeImmutable; + public function modify(string $modifier): ?DateTime; /** @kphp-extern-func-info stub generation-required */ - public function setDate(int $year, int $month, int $day): DateTimeImmutable; + public function setDate(int $year, int $month, int $day): DateTime; /** @kphp-extern-func-info stub generation-required */ - public function setISODate(int $year, int $week, int $dayOfWeek = 1): DateTimeImmutable; + public function setISODate(int $year, int $week, int $dayOfWeek = 1): DateTime; /** @kphp-extern-func-info stub generation-required */ public function setTime( int $hour, int $minute, int $second = 0, int $microsecond = 0 - ): DateTimeImmutable; + ): DateTime; /** @kphp-extern-func-info stub generation-required */ - public function setTimestamp(int $timestamp): DateTimeImmutable; + public function setTimestamp(int $timestamp): DateTime; /** @kphp-extern-func-info stub generation-required */ - public function sub(DateInterval $interval): DateTimeImmutable; + public function sub(DateInterval $interval): DateTime; /** @kphp-extern-func-info stub generation-required */ public function diff(DateTimeInterface $targetObject, bool $absolute = false): DateInterval; /** @kphp-extern-func-info stub generation-required */ @@ -164,6 +146,8 @@ class DateTimeImmutable implements DateTimeInterface { public function getTimestamp(): int; } +// ===== UNSUPPORTED ===== + define('DATE_ATOM', "Y-m-d\TH:i:sP"); define('DATE_COOKIE', "l, d-M-y H:i:s T"); define('DATE_ISO8601', "Y-m-d\TH:i:sO"); diff --git a/runtime-common/core/core-types/decl/string_decl.inl b/runtime-common/core/core-types/decl/string_decl.inl index 211d804b0e..18eb597009 100644 --- a/runtime-common/core/core-types/decl/string_decl.inl +++ b/runtime-common/core/core-types/decl/string_decl.inl @@ -40,6 +40,7 @@ struct ArrayBucketDummyStrTag; class string { public: using size_type = string_size_type; + using value_type = char; static constexpr size_type npos = (size_type)-1; private: diff --git a/runtime-common/stdlib/string/string-context.h b/runtime-common/stdlib/string/string-context.h index 4a300ef257..134c2f7dc9 100644 --- a/runtime-common/stdlib/string/string-context.h +++ b/runtime-common/stdlib/string/string-context.h @@ -25,6 +25,7 @@ inline constexpr std::string_view COMMA_ = ","; inline constexpr std::string_view BACKSLASH_ = "\\"; inline constexpr std::string_view QUOTE_ = "\""; inline constexpr std::string_view NEWLINE_ = "\n"; +inline constexpr std::string_view NOW_ = "now"; inline constexpr std::string_view SPACE_ = " "; inline constexpr std::string_view WHAT_ = " \n\r\t\v\0"sv; inline constexpr std::string_view ONE_ = "1"; @@ -60,6 +61,7 @@ struct StringLibConstants final : vk::not_copyable { string BACKSLASH_STR{string_context_impl_::BACKSLASH_.data(), static_cast(string_context_impl_::BACKSLASH_.size())}; string QUOTE_STR{string_context_impl_::QUOTE_.data(), static_cast(string_context_impl_::QUOTE_.size())}; string NEWLINE_STR{string_context_impl_::NEWLINE_.data(), static_cast(string_context_impl_::NEWLINE_.size())}; + string NOW_STR{string_context_impl_::NOW_.data(), static_cast(string_context_impl_::NOW_.size())}; string SPACE_STR{string_context_impl_::SPACE_.data(), static_cast(string_context_impl_::SPACE_.size())}; string WHAT_STR{string_context_impl_::WHAT_.data(), static_cast(string_context_impl_::WHAT_.size())}; string ONE_STR{string_context_impl_::ONE_.data(), static_cast(string_context_impl_::ONE_.size())}; @@ -85,6 +87,7 @@ struct StringLibConstants final : vk::not_copyable { BACKSLASH_STR.set_reference_counter_to(ExtraRefCnt::for_global_const); QUOTE_STR.set_reference_counter_to(ExtraRefCnt::for_global_const); NEWLINE_STR.set_reference_counter_to(ExtraRefCnt::for_global_const); + NOW_STR.set_reference_counter_to(ExtraRefCnt::for_global_const); SPACE_STR.set_reference_counter_to(ExtraRefCnt::for_global_const); WHAT_STR.set_reference_counter_to(ExtraRefCnt::for_global_const); ONE_STR.set_reference_counter_to(ExtraRefCnt::for_global_const); diff --git a/runtime-light/stdlib/stdlib.cmake b/runtime-light/stdlib/stdlib.cmake index 0f6d897e2f..bbe470da11 100644 --- a/runtime-light/stdlib/stdlib.cmake +++ b/runtime-light/stdlib/stdlib.cmake @@ -38,7 +38,10 @@ prepend( string/string-state.cpp system/system-functions.cpp system/system-state.cpp - time/datetime.cpp + time/dateinterval.cpp + time/datetime.cpp + time/datetimeimmutable.cpp + time/datetimezone.cpp time/time-functions.cpp time/time-state.cpp time/timelib-functions.cpp diff --git a/runtime-light/stdlib/time/dateinterval.cpp b/runtime-light/stdlib/time/dateinterval.cpp new file mode 100644 index 0000000000..21c93dddf9 --- /dev/null +++ b/runtime-light/stdlib/time/dateinterval.cpp @@ -0,0 +1,53 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2020 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#include "runtime-light/stdlib/time/dateinterval.h" + +#include +#include + +#include "common/containers/final_action.h" +#include "runtime-common/core/allocator/script-allocator.h" +#include "runtime-common/core/runtime-core.h" +#include "runtime-light/stdlib/diagnostics/exception-functions.h" +#include "runtime-light/stdlib/diagnostics/logs.h" + +C$DateInterval::~C$DateInterval() { + if (rel_time != nullptr) { + kphp::timelib::destruct(rel_time); + rel_time = nullptr; + } +} + +class_instance f$DateInterval$$__construct(const class_instance& self, const string& duration) noexcept { + auto expected_rel_time{kphp::timelib::construct_interval({duration.c_str(), duration.size()})}; + if (!expected_rel_time.has_value()) [[unlikely]] { + string err_msg; + format_to(std::back_inserter(err_msg), "DateInterval::__construct(): failed to parse interval ({}): {}", duration.c_str(), expected_rel_time.error()); + THROW_EXCEPTION(kphp::exception::make_throwable(err_msg)); + return {}; + } + self->rel_time = *expected_rel_time; + return self; +} + +class_instance f$DateInterval$$createFromDateString(const string& datetime) noexcept { + auto expected_time{kphp::timelib::construct_time({datetime.c_str(), datetime.size()})}; + if (!expected_time.has_value()) [[unlikely]] { + kphp::log::warning("DateInterval::createFromDateString(): failed to parse datetime ({}): {}", datetime.c_str(), expected_time.error()); + return {}; + } + timelib_time* time{*expected_time}; + vk::final_action time_deleter{[time] { kphp::timelib::destruct(time); }}; + class_instance date_interval; + date_interval.alloc(); + date_interval->rel_time = kphp::timelib::clone(std::addressof(time->relative)); + return date_interval; +} + +string f$DateInterval$$format(const class_instance& self, const string& format) noexcept { + string str; + kphp::timelib::date_interval_format_to(std::back_inserter(str), {format.c_str(), format.size()}, self->rel_time); + return str; +} diff --git a/runtime-light/stdlib/time/dateinterval.h b/runtime-light/stdlib/time/dateinterval.h new file mode 100644 index 0000000000..7421092803 --- /dev/null +++ b/runtime-light/stdlib/time/dateinterval.h @@ -0,0 +1,37 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2022 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include +#include + +#include "common/algorithms/hashes.h" +#include "runtime-common/core/class-instance/refcountable-php-classes.h" +#include "runtime-common/core/runtime-core.h" +#include "runtime-common/stdlib/visitors/dummy-visitor-methods.h" +#include "runtime-light/stdlib/time/timelib-functions.h" + +struct C$DateInterval : public refcountable_polymorphic_php_classes_virt<>, private DummyVisitorMethods { + using DummyVisitorMethods::accept; + + timelib_rel_time* rel_time{nullptr}; + + virtual const char* get_class() const noexcept { + return R"(DateInterval)"; + } + + virtual int32_t get_hash() const noexcept { + std::string_view name_view{C$DateInterval::get_class()}; + return static_cast(vk::murmur_hash(name_view.data(), name_view.size())); + } + + ~C$DateInterval() override; +}; + +class_instance f$DateInterval$$__construct(const class_instance& self, const string& duration) noexcept; + +class_instance f$DateInterval$$createFromDateString(const string& datetime) noexcept; + +string f$DateInterval$$format(const class_instance& self, const string& format) noexcept; diff --git a/runtime-light/stdlib/time/datetime.cpp b/runtime-light/stdlib/time/datetime.cpp index 3ea84ee742..a0c0076812 100644 --- a/runtime-light/stdlib/time/datetime.cpp +++ b/runtime-light/stdlib/time/datetime.cpp @@ -4,9 +4,38 @@ #include "runtime-light/stdlib/time/datetime.h" -#include "runtime-light/allocator/allocator.h" +#include "runtime-light/stdlib/diagnostics/exception-functions.h" +#include "runtime-light/stdlib/time/timelib-functions.h" C$DateTime::~C$DateTime() { - kphp::memory::libc_alloc_guard{}, timelib_time_dtor(time); - time = nullptr; + if (time != nullptr) { + kphp::timelib::destruct(time); + time = nullptr; + } +} + +class_instance f$DateTime$$__construct(const class_instance& self, const string& datetime, + const class_instance& timezone) noexcept { + const auto& str_to_parse{datetime.empty() ? StringLibConstants::get().NOW_STR : datetime}; + auto expected_time{kphp::timelib::construct_time(std::string_view{str_to_parse.c_str(), str_to_parse.size()})}; + if (!expected_time.has_value()) [[unlikely]] { + string err_msg; + format_to(std::back_inserter(err_msg), "DateTime::__construct(): failed to parse datetime ({}): {}", datetime.c_str(), expected_time.error()); + THROW_EXCEPTION(kphp::exception::make_throwable(err_msg)); + } + + timelib_time* time{*expected_time}; + timelib_tzinfo* tzi{!timezone.is_null() ? timezone->tzi : nullptr}; + if (tzi == nullptr) { + if (time->tz_info != nullptr) { + tzi = time->tz_info; + } else if (auto* default_tzi{kphp::timelib::get_timezone_info(TimeInstanceState::get().default_timezone.c_str())}; default_tzi != nullptr) { + tzi = default_tzi; + } + } + + kphp::timelib::fill_holes(time, tzi); + + self->time = time; + return self; } diff --git a/runtime-light/stdlib/time/datetime.h b/runtime-light/stdlib/time/datetime.h index ca1dc52af5..12db0be25b 100644 --- a/runtime-light/stdlib/time/datetime.h +++ b/runtime-light/stdlib/time/datetime.h @@ -9,8 +9,12 @@ #include "common/algorithms/hashes.h" #include "runtime-common/core/runtime-core.h" +#include "runtime-common/stdlib/string/string-context.h" #include "runtime-common/stdlib/visitors/dummy-visitor-methods.h" -#include "runtime-light/stdlib/time/datetime-interface.h" +#include "runtime-light/stdlib/time/dateinterval.h" +#include "runtime-light/stdlib/time/datetimeimmutable.h" +#include "runtime-light/stdlib/time/datetimeinterface.h" +#include "runtime-light/stdlib/time/datetimezone.h" struct C$DateTime : public C$DateTimeInterface, private DummyVisitorMethods { using DummyVisitorMethods::accept; @@ -27,4 +31,28 @@ struct C$DateTime : public C$DateTimeInterface, private DummyVisitorMethods { ~C$DateTime() override; }; -constexpr std::string_view NOW{"now"}; +class_instance f$DateTime$$__construct(const class_instance& self, const string& datetime = StringLibConstants::get().NOW_STR, + const class_instance& timezone = Optional{}) noexcept; + +class_instance f$DateTime$$add(const class_instance& self, const class_instance& interval) noexcept; + +class_instance f$DateTime$$createFromFormat(const string& format, const string& datetime, + const class_instance& timezone = Optional{}) noexcept; + +class_instance f$DateTime$$createFromImmutable(const class_instance& object) noexcept; + +class_instance f$DateTime$$modify(const class_instance& self, const string& modifier) noexcept; + +class_instance f$DateTime$$setDate(const class_instance& self, int64_t year, int64_t month, int64_t day) noexcept; + +class_instance f$DateTime$$setTime(const class_instance& self, int64_t hour, int64_t minute, int64_t second = 0, + int64_t microsecond = 0) noexcept; + +class_instance f$DateTime$$setTimestamp(const class_instance& self, int64_t timestamp) noexcept; + +class_instance f$DateTime$$diff(const class_instance& self, const class_instance& target_object, + bool absolute = false) noexcept; + +string f$DateTime$$format(const class_instance& self, const string& format) noexcept; + +int64_t f$DateTime$$getTimestamp(const class_instance& self) noexcept; diff --git a/runtime-light/stdlib/time/datetimeimmutable.cpp b/runtime-light/stdlib/time/datetimeimmutable.cpp new file mode 100644 index 0000000000..74eb32c17c --- /dev/null +++ b/runtime-light/stdlib/time/datetimeimmutable.cpp @@ -0,0 +1,139 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2020 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#include "runtime-light/stdlib/time/datetimeimmutable.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/containers/final_action.h" +#include "runtime-common/core/runtime-core.h" +#include "runtime-light/stdlib/diagnostics/exception-functions.h" +#include "runtime-light/stdlib/diagnostics/logs.h" +#include "runtime-light/stdlib/time/dateinterval.h" +#include "runtime-light/stdlib/time/datetime.h" +#include "runtime-light/stdlib/time/time-functions.h" +#include "runtime-light/stdlib/time/timelib-functions.h" + +namespace { + +class_instance clone_immutable(const class_instance& origin) noexcept { + class_instance clone; + clone.alloc(); + clone->time = kphp::timelib::clone(origin->time); + return clone; +} + +} // namespace + +C$DateTimeImmutable::~C$DateTimeImmutable() { + if (time != nullptr) { + kphp::timelib::destruct(time); + time = nullptr; + } +} + +class_instance f$DateTimeImmutable$$__construct(const class_instance& self, const string& datetime, + const class_instance& timezone) noexcept { + const auto& str_to_parse{datetime.empty() ? StringLibConstants::get().NOW_STR : datetime}; + auto expected_time{kphp::timelib::construct_time(std::string_view{str_to_parse.c_str(), str_to_parse.size()})}; + if (!expected_time.has_value()) [[unlikely]] { + string err_msg; + format_to(std::back_inserter(err_msg), "DateTimeImmutable::__construct(): failed to parse datetime ({}): {}", datetime.c_str(), expected_time.error()); + THROW_EXCEPTION(kphp::exception::make_throwable(err_msg)); + } + + timelib_time* time{*expected_time}; + timelib_tzinfo* tzi{!timezone.is_null() ? timezone->tzi : nullptr}; + if (tzi == nullptr) { + if (time->tz_info != nullptr) { + tzi = time->tz_info; + } else if (auto* default_tzi{kphp::timelib::get_timezone_info(TimeInstanceState::get().default_timezone.c_str())}; default_tzi != nullptr) { + tzi = default_tzi; + } + } + + kphp::timelib::fill_holes(time, tzi); + + self->time = time; + return self; +} + +class_instance f$DateTimeImmutable$$add(const class_instance& self, + const class_instance& interval) noexcept { + auto new_date = clone_immutable(self); + new_date->time = kphp::timelib::add(new_date->time, interval->rel_time); + return new_date; +} + +class_instance f$DateTimeImmutable$$createFromFormat(const string& format, const string& datetime, + const class_instance& timezone) noexcept { + auto expected_time{kphp::timelib::construct_time({datetime.c_str(), datetime.size()}, format.c_str())}; + if (!expected_time.has_value()) [[unlikely]] { + return {}; + } + auto time{std::move(*expected_time)}; + timelib_tzinfo* tzi{!timezone.is_null() ? timezone->tzi : nullptr}; + if (tzi == nullptr) { + if (time.tz_info != nullptr) { + tzi = time.tz_info; + } else if (auto* default_tzi{kphp::timelib::get_timezone_info(TimeInstanceState::get().default_timezone.c_str())}; default_tzi != nullptr) { + tzi = default_tzi; + } + } + + kphp::timelib::fill_holes(time, tzi); + + class_instance date_time; + date_time.alloc(); + date_time->time = time; + return date_time; +} + +class_instance f$DateTimeImmutable$$createFromMutable(const class_instance& object) noexcept { + class_instance clone; + clone.alloc(); + clone->time = kphp::timelib::clone(object->time); + return clone; +} + +class_instance f$DateTimeImmutable$$modify(const class_instance& self, const string& modifier) noexcept { + auto new_date = clone_immutable(self); + auto expected_success = kphp::timelib::modify(new_date->time, {modifier.c_str(), modifier.size()}); + if (!expected_success.has_value()) [[unlikely]] { + kphp::log::warning("DateTimeImmutable::modify(): failed to parse modifier ({}): {}", modifier.c_str(), expected_success.error()); + return {}; + } + return new_date; +} + +class_instance f$DateTimeImmutable$$setTimestamp(const class_instance& self, int64_t timestamp) noexcept { + auto new_date = clone_immutable(self); + kphp::timelib::date_timestamp_set(new_date->time, timestamp); + return new_date; +} + +class_instance f$DateTimeImmutable$$diff(const class_instance& self, + const class_instance& target_object, bool absolute) noexcept { + class_instance interval; + interval.alloc(); + interval->rel_time = kphp::timelib::date_diff(self->time, target_object.get()->time, absolute); + return interval; +} + +string f$DateTimeImmutable$$format(const class_instance& self, const string& format) noexcept { + string str; + kphp::timelib::date_format_to_localtime(std::back_inserter(str), {format.c_str(), format.size()}, self->time); + return str; +} + +int64_t f$DateTimeImmutable$$getTimestamp(const class_instance& self) noexcept { + return kphp::timelib::date_timestamp_get(*(self->time)); +} diff --git a/runtime-light/stdlib/time/datetimeimmutable.h b/runtime-light/stdlib/time/datetimeimmutable.h new file mode 100644 index 0000000000..c41e36056a --- /dev/null +++ b/runtime-light/stdlib/time/datetimeimmutable.h @@ -0,0 +1,56 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2022 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include +#include + +#include "common/algorithms/hashes.h" +#include "runtime-common/core/runtime-core.h" +#include "runtime-common/stdlib/string/string-context.h" +#include "runtime-common/stdlib/visitors/dummy-visitor-methods.h" +#include "runtime-light/stdlib/time/datetimeinterface.h" +#include "runtime-light/stdlib/time/datetimezone.h" + +struct C$DateInterval; +struct C$DateTime; + +struct C$DateTimeImmutable : public C$DateTimeInterface, private DummyVisitorMethods { + using DummyVisitorMethods::accept; + + const char* get_class() const noexcept override { + return R"(DateTimeImmutable)"; + } + + int32_t get_hash() const noexcept override { + std::string_view name_view{C$DateTimeImmutable::get_class()}; + return static_cast(vk::murmur_hash(name_view.data(), name_view.size())); + } + + ~C$DateTimeImmutable() override; +}; + +class_instance f$DateTimeImmutable$$__construct(const class_instance& self, + const string& datetime = StringLibConstants::get().NOW_STR, + const class_instance& timezone = Optional{}) noexcept; + +class_instance f$DateTimeImmutable$$add(const class_instance& self, + const class_instance& interval) noexcept; + +class_instance f$DateTimeImmutable$$createFromFormat(const string& format, const string& datetime, + const class_instance& timezone = Optional{}) noexcept; + +class_instance f$DateTimeImmutable$$createFromMutable(const class_instance& object) noexcept; + +class_instance f$DateTimeImmutable$$modify(const class_instance& self, const string& modifier) noexcept; + +class_instance f$DateTimeImmutable$$setTimestamp(const class_instance& self, int64_t timestamp) noexcept; + +class_instance f$DateTimeImmutable$$diff(const class_instance& self, + const class_instance& target_object, bool absolute = false) noexcept; + +string f$DateTimeImmutable$$format(const class_instance& self, const string& format) noexcept; + +int64_t f$DateTimeImmutable$$getTimestamp(const class_instance& self) noexcept; diff --git a/runtime-light/stdlib/time/datetime-interface.h b/runtime-light/stdlib/time/datetimeinterface.h similarity index 86% rename from runtime-light/stdlib/time/datetime-interface.h rename to runtime-light/stdlib/time/datetimeinterface.h index 906332aad8..93674004a0 100644 --- a/runtime-light/stdlib/time/datetime-interface.h +++ b/runtime-light/stdlib/time/datetimeinterface.h @@ -5,11 +5,12 @@ #pragma once #include "kphp/timelib/timelib.h" +#include "timelib-functions.h" #include "runtime-common/core/class-instance/refcountable-php-classes.h" struct C$DateTimeInterface : public refcountable_polymorphic_php_classes_virt<> { - timelib_time* time{nullptr}; + kphp::timelib::time_t time{nullptr}; virtual const char* get_class() const noexcept = 0; virtual int get_hash() const noexcept = 0; diff --git a/runtime-light/stdlib/time/datetimezone.cpp b/runtime-light/stdlib/time/datetimezone.cpp new file mode 100644 index 0000000000..f79787d432 --- /dev/null +++ b/runtime-light/stdlib/time/datetimezone.cpp @@ -0,0 +1,25 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2020 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#include "runtime-light/stdlib/time/datetimezone.h" +#include "runtime-light/stdlib/diagnostics/exception-functions.h" +#include "runtime-light/stdlib/diagnostics/logs.h" + +class_instance f$DateTimeZone$$__construct(const class_instance& self, const string& timezone) noexcept { + if (!timezone.empty()) { + int errc{}; // it's intentionally declared as 'int' since timelib_parse_tzfile accepts 'int' + self->tzi = kphp::timelib::get_timezone_info(timezone.c_str(), timelib_builtin_db(), std::addressof(errc)); + if (self->tzi == nullptr) [[unlikely]] { + string err_msg; + format_to(std::back_inserter(err_msg), "DateTimeZone::__construct(): can't get timezone info: timezone -> {}, error -> {}", timezone.c_str(), + timelib_get_error_message(errc)); + THROW_EXCEPTION(kphp::exception::make_throwable(err_msg)); + } + } + return self; +} + +string f$DateTimeZone$$getName(const class_instance& self) noexcept { + return string{self->tzi->name}; +} diff --git a/runtime-light/stdlib/time/datetimezone.h b/runtime-light/stdlib/time/datetimezone.h new file mode 100644 index 0000000000..d7f58b08c3 --- /dev/null +++ b/runtime-light/stdlib/time/datetimezone.h @@ -0,0 +1,34 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2022 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include +#include + +#include "common/algorithms/hashes.h" +#include "runtime-common/core/class-instance/refcountable-php-classes.h" +#include "runtime-common/core/runtime-core.h" +#include "runtime-common/stdlib/visitors/dummy-visitor-methods.h" +#include "runtime-light/stdlib/time/timelib-functions.h" + +struct C$DateTimeZone : public refcountable_polymorphic_php_classes_virt<>, private DummyVisitorMethods { + using DummyVisitorMethods::accept; + + timelib_tzinfo* tzi{nullptr}; + + virtual const char* get_class() const noexcept { + return R"(DateTimeZone)"; + } + + virtual int32_t get_hash() const noexcept { + std::string_view name_view{C$DateTimeZone::get_class()}; + return static_cast(vk::murmur_hash(name_view.data(), name_view.size())); + } + + ~C$DateTimeZone() override = default; +}; + +class_instance f$DateTimeZone$$__construct(const class_instance& self, const string& timezone) noexcept; +string f$DateTimeZone$$getName(const class_instance& self) noexcept; diff --git a/runtime-light/stdlib/time/time-functions.cpp b/runtime-light/stdlib/time/time-functions.cpp index 8b1d1e942e..b2c079d3bc 100644 --- a/runtime-light/stdlib/time/time-functions.cpp +++ b/runtime-light/stdlib/time/time-functions.cpp @@ -97,13 +97,13 @@ string date(const string& format, const tm& t, int64_t timestamp, bool local) no SB << static_cast(day / 10 + '0') << static_cast(day % 10 + '0'); break; case 'D': - SB << DAY_SHORT_NAMES[day_of_week].data(); + SB << timelib::DAY_SHORT_NAMES[day_of_week].data(); break; case 'j': SB << day; break; case 'l': - SB << DAY_FULL_NAMES[day_of_week].data(); + SB << timelib::DAY_FULL_NAMES[day_of_week].data(); break; case 'N': SB << (day_of_week == 0 ? '7' : static_cast(day_of_week + '0')); @@ -141,13 +141,13 @@ string date(const string& format, const tm& t, int64_t timestamp, bool local) no SB << static_cast('0' + iso_week / 10) << static_cast('0' + iso_week % 10); break; case 'F': - SB << MON_FULL_NAMES[month - 1].data(); + SB << timelib::MON_FULL_NAMES[month - 1].data(); break; case 'm': SB << static_cast(month / 10 + '0') << static_cast(month % 10 + '0'); break; case 'M': - SB << MON_SHORT_NAMES[month - 1].data(); + SB << timelib::MON_SHORT_NAMES[month - 1].data(); break; case 'n': SB << month; @@ -265,12 +265,12 @@ string date(const string& format, const tm& t, int64_t timestamp, bool local) no } break; case 'r': - SB << DAY_SHORT_NAMES[day_of_week].data(); + SB << timelib::DAY_SHORT_NAMES[day_of_week].data(); SB << ", "; SB << static_cast(day / 10 + '0'); SB << static_cast(day % 10 + '0'); SB << ' '; - SB << MON_SHORT_NAMES[month - 1].data(); + SB << timelib::MON_SHORT_NAMES[month - 1].data(); SB << ' '; SB << year; SB << ' '; diff --git a/runtime-light/stdlib/time/time-functions.h b/runtime-light/stdlib/time/time-functions.h index b3284cbfc4..b1e37ce213 100644 --- a/runtime-light/stdlib/time/time-functions.h +++ b/runtime-light/stdlib/time/time-functions.h @@ -20,12 +20,6 @@ namespace kphp::time::impl { -constexpr inline std::array MON_FULL_NAMES = {"January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December"}; -constexpr inline std::array MON_SHORT_NAMES = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; -constexpr inline std::array DAY_FULL_NAMES = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; -constexpr inline std::array DAY_SHORT_NAMES = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; - constexpr inline int64_t CHECKDATE_YEAR_MIN = 1; constexpr inline int64_t CHECKDATE_YEAR_MAX = 32767; @@ -108,8 +102,8 @@ inline array f$getdate(int64_t timestamp = std::numeric_limits:: array result{array_size{11, false}}; - auto weekday{kphp::time::impl::DAY_FULL_NAMES[t.tm_wday]}; - auto month{kphp::time::impl::MON_FULL_NAMES[t.tm_mon]}; + auto weekday{kphp::timelib::DAY_FULL_NAMES[t.tm_wday]}; + auto month{kphp::timelib::MON_FULL_NAMES[t.tm_mon]}; result.set_value(string{"seconds", 7}, t.tm_sec); result.set_value(string{"minutes", 7}, t.tm_min); diff --git a/runtime-light/stdlib/time/timelib-functions.cpp b/runtime-light/stdlib/time/timelib-functions.cpp index 52bf32f091..8d313562b9 100644 --- a/runtime-light/stdlib/time/timelib-functions.cpp +++ b/runtime-light/stdlib/time/timelib-functions.cpp @@ -4,10 +4,16 @@ #include "runtime-light/stdlib/time/timelib-functions.h" +#include +#include #include +#include +#include +#include #include #include #include +#include #include "kphp/timelib/timelib.h" @@ -17,6 +23,178 @@ namespace kphp::timelib { +error::~error() { + if (err != nullptr) { + destruct(err); + } +} + +std::expected construct_time(std::string_view time_sv) noexcept { + timelib_error_container* errors{}; + time_t time{(kphp::memory::libc_alloc_guard{}, + timelib_strtotime(time_sv.data(), time_sv.size(), std::addressof(errors), timelib_builtin_db(), kphp::timelib::get_timezone_info))}; + if (errors->error_count != 0) [[unlikely]] { + destruct(*time); + return std::unexpected{error{errors}}; + } + + destruct(*errors); + return time; +} + +std::expected construct_time(std::string_view time_sv, const char* format) noexcept { + timelib_error_container* err{nullptr}; + time_t t{(kphp::memory::libc_alloc_guard{}, timelib_parse_from_format(format, time_sv.data(), time_sv.size(), std::addressof(err), + timelib_builtin_db(), kphp::timelib::get_timezone_info))}; + + if (err && err->error_count) { + destruct(*t); + return std::unexpected{error{err}}; + } + destruct(*err); + + return t; +} + +time_offset_t construct_time_offset(timelib_time* t) noexcept { + if (t->zone_type == TIMELIB_ZONETYPE_ABBR) { + time_offset_t offset{(kphp::memory::libc_alloc_guard{}, timelib_time_offset_ctor())}; + offset->offset = (t->z + (t->dst * 3600)); + offset->leap_secs = 0; + offset->is_dst = t->dst; + offset->abbr = (kphp::memory::libc_alloc_guard{}, timelib_strdup(t->tz_abbr)); + return offset; + } + if (t->zone_type == TIMELIB_ZONETYPE_OFFSET) { + time_offset_t offset{(kphp::memory::libc_alloc_guard{}, timelib_time_offset_ctor())}; + offset->offset = (t->z); + offset->leap_secs = 0; + offset->is_dst = 0; + offset->abbr = (kphp::memory::libc_alloc_guard{}, static_cast(timelib_malloc(9))); // GMT±xxxx\0 + // set upper bound to 99 just to ensure that 'hours_offset' fits in {:02} + auto hours_offset{std::min(std::abs(offset->offset / 3600), 99)}; + std::format_to_n(offset->abbr, 9, "GMT{}{:02}{:02}", (offset->offset < 0) ? '-' : '+', hours_offset, std::abs((offset->offset % 3600) / 60)); + return offset; + } + return time_offset_t{timelib_get_time_zone_info(t->sse, t->tz_info)}; +} + +std::expected construct_interval(std::string_view format) noexcept { + timelib_time* b{nullptr}; + timelib_time* e{nullptr}; + vk::final_action e_deleter{[e]() { kphp::memory::libc_alloc_guard{}, free(e); }}; + vk::final_action b_deleter{[b]() { kphp::memory::libc_alloc_guard{}, free(b); }}; + timelib_rel_time* p{nullptr}; + int r{}; + timelib_error_container* errors{nullptr}; + + kphp::memory::libc_alloc_guard{}, + timelib_strtointerval(format.data(), format.size(), std::addressof(b), std::addressof(e), std::addressof(p), std::addressof(r), std::addressof(errors)); + + if (errors->error_count > 0) { + if (p != nullptr) { + destruct(*p); + } + return std::unexpected{error{errors}}; + } + destruct(*errors); + + if (p != nullptr) { + return rel_time_t{p}; + } + + if (b != nullptr && e != nullptr) { + timelib_update_ts(b, nullptr); + timelib_update_ts(e, nullptr); + return kphp::memory::libc_alloc_guard{}, rel_time_t{timelib_diff(b, e)}; + } + + return std::unexpected{error{nullptr}}; +} + +timelib_time* add(timelib_time* t, timelib_rel_time* interval) noexcept { + timelib_time* new_time{(kphp::memory::libc_alloc_guard{}, timelib_add(t, interval))}; + kphp::memory::libc_alloc_guard{}, timelib_time_dtor(t); + return new_time; +} + +timelib_rel_time* clone(timelib_rel_time* rt) noexcept { + return kphp::memory::libc_alloc_guard{}, timelib_rel_time_clone(rt); +} + +timelib_time* clone(timelib_time* t) noexcept { + return kphp::memory::libc_alloc_guard{}, timelib_time_clone(t); +} + +timelib_rel_time* date_diff(timelib_time* time1, timelib_time* time2, bool absolute) noexcept { + timelib_update_ts(time1, nullptr); + timelib_update_ts(time2, nullptr); + + timelib_rel_time* diff{(kphp::memory::libc_alloc_guard{}, timelib_diff(time1, time2))}; + if (absolute) { + diff->invert = 0; + } + return diff; +} + +std::string_view date_full_day_name(timelib_sll y, timelib_sll m, timelib_sll d) noexcept { + timelib_sll day_of_week = timelib_day_of_week(y, m, d); + if (day_of_week < 0) { + return "Unknown"; + } + return DAY_FULL_NAMES[day_of_week]; +} + +std::string_view date_short_day_name(timelib_sll y, timelib_sll m, timelib_sll d) noexcept { + timelib_sll day_of_week = timelib_day_of_week(y, m, d); + if (day_of_week < 0) { + return "Unknown"; + } + return DAY_SHORT_NAMES[day_of_week]; +} + +int64_t date_timestamp_get(timelib_time& t) noexcept { + timelib_update_ts(std::addressof(t), nullptr); + + int error = 0; + timelib_long timestamp = timelib_date_to_int(std::addressof(t), &error); + // the 'error' should be always 0 on x64 platform + log::assertion(error == 0); + + return timestamp; +} + +void date_timestamp_set(timelib_time* t, int64_t timestamp) noexcept { + kphp::memory::libc_alloc_guard{}, timelib_unixtime2local(t, static_cast(timestamp)); + timelib_update_ts(t, nullptr); + t->us = 0; +} + +const char* english_suffix(timelib_sll number) noexcept { + if (number >= 10 && number <= 19) { + return "th"; + } else { + switch (number % 10) { + case 1: + return "st"; + case 2: + return "nd"; + case 3: + return "rd"; + } + } + return "th"; +} + +timelib_tzinfo* get_timezone_info(const char* timezone) noexcept { + int errc{}; // it's intentionally declared as 'int' since timelib_parse_tzfile accepts 'int' + auto* tzinfo{kphp::timelib::get_timezone_info(timezone, timelib_builtin_db(), std::addressof(errc))}; + if (tzinfo == nullptr) [[unlikely]] { + kphp::log::warning("can't get timezone info: timezone -> {}, error -> {}", timezone, timelib_get_error_message(errc)); + } + return tzinfo; +} + timelib_tzinfo* get_timezone_info(const char* timezone, const timelib_tzdb* tzdb, int* errc) noexcept { std::string_view timezone_view{timezone}; auto* tzinfo{TimeImageState::get().timelib_zone_cache.get(timezone_view)}; @@ -75,6 +253,10 @@ int64_t gmmktime(std::optional hou, std::optional min, std::op return now->sse; } +bool is_leap_year(int32_t year) noexcept { + return (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0); +} + std::optional mktime(std::optional hou, std::optional min, std::optional sec, std::optional mon, std::optional day, std::optional yea) noexcept { auto* now{(kphp::memory::libc_alloc_guard{}, timelib_time_ctor())}; @@ -126,6 +308,74 @@ std::optional mktime(std::optional hou, std::optional return now->sse; } +std::expected modify(timelib_time* t, std::string_view modifier) noexcept { + auto expected_tmp_time{construct_time(modifier)}; + + if (!expected_tmp_time.has_value()) [[unlikely]] { + return std::unexpected{std::move(expected_tmp_time).error()}; + } + + timelib_time* tmp_time{*expected_tmp_time}; + + vk::final_action tmp_time_deleter{[tmp_time] { destruct(tmp_time); }}; + + std::memcpy(&t->relative, &tmp_time->relative, sizeof(timelib_rel_time)); + t->have_relative = tmp_time->have_relative; + t->sse_uptodate = 0; + + if (tmp_time->y != TIMELIB_UNSET) { + t->y = tmp_time->y; + } + if (tmp_time->m != TIMELIB_UNSET) { + t->m = tmp_time->m; + } + if (tmp_time->d != TIMELIB_UNSET) { + t->d = tmp_time->d; + } + + if (tmp_time->h != TIMELIB_UNSET) { + t->h = tmp_time->h; + if (tmp_time->i != TIMELIB_UNSET) { + t->i = tmp_time->i; + if (tmp_time->s != TIMELIB_UNSET) { + t->s = tmp_time->s; + } else { + t->s = 0; + } + } else { + t->i = 0; + t->s = 0; + } + } + + if (tmp_time->us != TIMELIB_UNSET) { + t->us = tmp_time->us; + } + + timelib_update_ts(t, nullptr); + timelib_update_from_sse(t); + t->have_relative = 0; + std::memset(&t->relative, 0, sizeof(t->relative)); + return {}; +} + +timelib_time& now(timelib_tzinfo* tzi) noexcept { + timelib_time& res{(kphp::memory::libc_alloc_guard{}, *timelib_time_ctor())}; + + res.tz_info = tzi; + res.zone_type = TIMELIB_ZONETYPE_ID; + + namespace chrono = std::chrono; + const auto time_since_epoch{chrono::system_clock::now().time_since_epoch()}; + const auto sec{chrono::duration_cast(time_since_epoch).count()}; + const auto usec{chrono::duration_cast(time_since_epoch % chrono::seconds{1}).count()}; + + timelib_unixtime2local(std::addressof(res), static_cast(sec)); + res.us = usec; + + return res; +} + std::optional strtotime(std::string_view timezone, std::string_view datetime, int64_t timestamp) noexcept { if (datetime.empty()) [[unlikely]] { kphp::log::warning("datetime can't be empty"); @@ -139,27 +389,24 @@ std::optional strtotime(std::string_view timezone, std::string_view dat return {}; } - kphp::memory::libc_alloc_guard _{}; - timelib_time* now{timelib_time_ctor()}; const vk::final_action now_deleter{[now] noexcept { timelib_time_dtor(now); }}; now->tz_info = tzinfo; now->zone_type = TIMELIB_ZONETYPE_ID; timelib_unixtime2local(now, timestamp); - timelib_error_container* errors{}; - timelib_time* time{timelib_strtotime(datetime.data(), datetime.size(), std::addressof(errors), timelib_builtin_db(), kphp::timelib::get_timezone_info)}; - const vk::final_action time_deleter{[time] noexcept { timelib_time_dtor(time); }}; - const vk::final_action errors_deleter{[errors] noexcept { timelib_error_container_dtor(errors); }}; - if (errors->error_count != 0) [[unlikely]] { + auto expected_time{construct_time(datetime)}; + if (!expected_time.has_value()) [[unlikely]] { + auto* errors{expected_time.error().err}; kphp::log::warning("got {} errors in timelib_strtotime", errors->error_count); // TODO should we logs all the errors? return {}; } + time_t time{std::move(*expected_time)}; errc = 0; - timelib_fill_holes(time, now, TIMELIB_NO_CLONE); - timelib_update_ts(time, tzinfo); - int64_t result_timestamp{timelib_date_to_int(time, std::addressof(errc))}; + timelib_fill_holes(time.get(), now, TIMELIB_NO_CLONE); + timelib_update_ts(time.get(), tzinfo); + int64_t result_timestamp{timelib_date_to_int(time.get(), std::addressof(errc))}; if (errc != 0) [[unlikely]] { kphp::log::warning("can't convert date to int: error -> {}", timelib_get_error_message(errc)); return {}; diff --git a/runtime-light/stdlib/time/timelib-functions.h b/runtime-light/stdlib/time/timelib-functions.h index 480309b8c4..b25037b69e 100644 --- a/runtime-light/stdlib/time/timelib-functions.h +++ b/runtime-light/stdlib/time/timelib-functions.h @@ -4,14 +4,108 @@ #pragma once +#include #include +#include +#include +#include #include #include #include "kphp/timelib/timelib.h" +#include "common/containers/final_action.h" +#include "runtime-common/core/std/containers.h" +#include "runtime-light/allocator/allocator.h" + namespace kphp::timelib { +constexpr inline std::array MON_FULL_NAMES = {"January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December"}; +constexpr inline std::array MON_SHORT_NAMES = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; +constexpr inline std::array DAY_FULL_NAMES = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; +constexpr inline std::array DAY_SHORT_NAMES = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + +struct error { + timelib_error_container* err{nullptr}; + + explicit error(timelib_error_container* err) noexcept + : err{err} {} + + error(const error&) = delete; + error& operator=(const error&) = delete; + + error(error&& other) noexcept { + std::swap(err, other.err); + } + error& operator=(error&& other) noexcept { + std::swap(err, other.err); + return *this; + } + + ~error(); +}; + +struct destructor { + void operator()(timelib_error_container* ec) const noexcept { + kphp::memory::libc_alloc_guard{}, timelib_error_container_dtor(ec); + } + + void operator()(timelib_rel_time* rt) const noexcept { + kphp::memory::libc_alloc_guard{}, timelib_rel_time_dtor(rt); + } + + void operator()(timelib_time* t) const noexcept { + kphp::memory::libc_alloc_guard{}, timelib_time_dtor(t); + } + + void operator()(timelib_time_offset* to) const noexcept { + kphp::memory::libc_alloc_guard{}, timelib_time_offset_dtor(to); + } +}; + +using error_container_t = std::unique_ptr; +using rel_time_t = std::unique_ptr; +using time_t = std::unique_ptr; +using time_offset_t = std::unique_ptr; + +std::expected construct_time(std::string_view time_sv) noexcept; +std::expected construct_time(std::string_view time_sv, const char* format) noexcept; +time_offset_t construct_time_offset(timelib_time* t) noexcept; +std::expected construct_interval(std::string_view format) noexcept; + +timelib_time* add(timelib_time* t, timelib_rel_time* interval) noexcept; + +timelib_rel_time* clone(timelib_rel_time* rt) noexcept; +timelib_time* clone(timelib_time* t) noexcept; + +timelib_rel_time* date_diff(timelib_time* time1, timelib_time* time2, bool absolute) noexcept; + +std::string_view date_full_day_name(timelib_sll y, timelib_sll m, timelib_sll d) noexcept; + +std::string_view date_short_day_name(timelib_sll y, timelib_sll m, timelib_sll d) noexcept; + +int64_t date_timestamp_get(timelib_time& t) noexcept; + +void date_timestamp_set(timelib_time* t, int64_t timestamp) noexcept; + +const char* english_suffix(timelib_sll number) noexcept; + +template +void fill_holes(timelib_time& t, timelib_time* filler) noexcept { + int options = TIMELIB_NO_CLONE; + if constexpr (override_time) { + options |= TIMELIB_OVERRIDE_TIME; + } + + timelib_fill_holes(std::addressof(t), filler, options); + timelib_update_ts(std::addressof(t), filler->tz_info); + timelib_update_from_sse(std::addressof(t)); + + t.have_relative = 0; +} + +timelib_tzinfo* get_timezone_info(const char* timezone) noexcept; /** * @brief Retrieves a pointer to a `timelib_tzinfo` structure for a given time zone name. * @@ -29,11 +123,336 @@ timelib_tzinfo* get_timezone_info(const char* timezone, const timelib_tzdb* tzdb int64_t gmmktime(std::optional hou, std::optional min, std::optional sec, std::optional mon, std::optional day, std::optional yea) noexcept; +bool is_leap_year(int32_t year) noexcept; + std::optional mktime(std::optional hou, std::optional min, std::optional sec, std::optional mon, std::optional day, std::optional yea) noexcept; +std::expected modify(timelib_time* t, std::string_view modifier) noexcept; + +timelib_time& now(timelib_tzinfo* tzi) noexcept; + std::optional strtotime(std::string_view timezone, std::string_view datetime, int64_t timestamp) noexcept; bool valid_date(int64_t year, int64_t month, int64_t day) noexcept; +template +OutputIt date_format_to(OutputIt out, std::string_view format, timelib_time* t, bool localtime) noexcept { + if (format.empty()) { + return out; + } + + time_offset_t offset{localtime ? construct_time_offset(t) : nullptr}; + + bool weekYearSet{false}; + timelib_sll isoweek{}; + timelib_sll isoyear{}; + + for (std::size_t i = 0; i < format.size(); ++i) { + bool rfc_colon{false}; + switch (format[i]) { + // day + case 'd': + out = std::format_to(out, "{:02}", t->d); + break; + case 'D': + out = std::format_to(out, "{}", date_short_day_name(t->y, t->m, t->d)); + break; + case 'j': + out = std::format_to(out, "{}", t->d); + break; + case 'l': + out = std::format_to(out, "{}", date_full_day_name(t->y, t->m, t->d)); + break; + case 'S': + out = std::format_to(out, "{}", english_suffix(t->d)); + break; + case 'w': + out = std::format_to(out, "{}", timelib_day_of_week(t->y, t->m, t->d)); + break; + case 'N': + out = std::format_to(out, "{}", timelib_iso_day_of_week(t->y, t->m, t->d)); + break; + case 'z': + out = std::format_to(out, "{}", timelib_day_of_year(t->y, t->m, t->d)); + break; + + // week + case 'W': + if (!weekYearSet) { + timelib_isoweek_from_date(t->y, t->m, t->d, &isoweek, &isoyear); + weekYearSet = true; + } + out = std::format_to(out, "{:02}", isoweek); + break; // iso weeknr + case 'o': + if (!weekYearSet) { + timelib_isoweek_from_date(t->y, t->m, t->d, &isoweek, &isoyear); + weekYearSet = 1; + } + out = std::format_to(out, "{}", isoyear); + break; // iso year + + // month + case 'F': + out = std::format_to(out, "{}", MON_FULL_NAMES[t->m - 1]); + break; + case 'm': + out = std::format_to(out, "{:02}", t->m); + break; + case 'M': + out = std::format_to(out, "{}", MON_SHORT_NAMES[t->m - 1]); + break; + case 'n': + out = std::format_to(out, "{}", t->m); + break; + case 't': + out = std::format_to(out, "{}", timelib_days_in_month(t->y, t->m)); + break; + + // year + case 'L': + out = std::format_to(out, "{}", is_leap_year(t->y)); + break; + case 'y': + out = std::format_to(out, "{:02}", t->y % 100); + break; + case 'Y': + out = std::format_to(out, "{}{:04}", t->y < 0 ? "-" : "", std::abs(t->y)); + break; + + // time + case 'a': + out = std::format_to(out, "{}", t->h >= 12 ? "pm" : "am"); + break; + case 'A': + out = std::format_to(out, "{}", t->h >= 12 ? "PM" : "AM"); + break; + case 'B': { + auto retval{(((t->sse) - ((t->sse) - (((t->sse) % 86400) + 3600))) * 10)}; + if (retval < 0) { + retval += 864000; + } + // Make sure to do this on a positive int to avoid rounding errors + retval = (retval / 864) % 1000; + out = std::format_to(out, "{:03}", retval); + break; + } + case 'g': + out = std::format_to(out, "{}", (t->h % 12) ? t->h % 12 : 12); + break; + case 'G': + out = std::format_to(out, "{}", t->h); + break; + case 'h': + out = std::format_to(out, "{:02}", (t->h % 12) ? t->h % 12 : 12); + break; + case 'H': + out = std::format_to(out, "{:02}", t->h); + break; + case 'i': + out = std::format_to(out, "{:02}", t->i); + break; + case 's': + out = std::format_to(out, "{:02}", t->s); + break; + case 'u': + out = std::format_to(out, "{:06}", t->us); + break; + case 'v': + out = std::format_to(out, "{:03}", t->us / 1000); + break; + + // timezone + case 'I': + out = std::format_to(out, "{}", localtime ? offset->is_dst : 0); + break; + case 'P': + rfc_colon = true; + [[fallthrough]]; + case 'O': + out = std::format_to(out, "{}{:02}{}{:02}", localtime ? ((offset->offset < 0) ? '-' : '+') : '+', localtime ? std::abs(offset->offset / 3600) : 0, + rfc_colon ? ":" : "", localtime ? std::abs((offset->offset % 3600) / 60) : 0); + break; + case 'T': + out = std::format_to(out, "{}", localtime ? offset->abbr : "GMT"); + break; + case 'e': + if (!localtime) { + out = std::format_to(out, "UTC"); + } else { + switch (t->zone_type) { + case TIMELIB_ZONETYPE_ID: + out = std::format_to(out, "{}", t->tz_info->name); + break; + case TIMELIB_ZONETYPE_ABBR: + out = std::format_to(out, "{}", offset->abbr); + break; + case TIMELIB_ZONETYPE_OFFSET: + out = std::format_to(out, "{}{:02}:{:02}", ((offset->offset < 0) ? '-' : '+'), abs(offset->offset / 3600), abs((offset->offset % 3600) / 60)); + break; + } + } + break; + case 'Z': + out = std::format_to(out, "{}", localtime ? offset->offset : 0); + break; + + // full date/time + case 'c': + out = std::format_to(out, "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}{}{:02}:{:02}", t->y, t->m, t->d, t->h, t->i, t->s, + localtime ? ((offset->offset < 0) ? '-' : '+') : '+', localtime ? std::abs(offset->offset / 3600) : 0, + localtime ? std::abs((offset->offset % 3600) / 60) : 0); + break; + case 'r': + out = std::format_to(out, "{:3}, {:02} {:3} {:04} {:02}:{:02}:{:02} {}{:02}{:02}", date_short_day_name(t->y, t->m, t->d), t->d, MON_SHORT_NAMES[t->m - 1], + t->y, t->h, t->i, t->s, localtime ? ((offset->offset < 0) ? '-' : '+') : '+', localtime ? abs(offset->offset / 3600) : 0, + localtime ? abs((offset->offset % 3600) / 60) : 0); + break; + case 'U': + out = std::format_to(out, "{}", t->sse); + break; + + case '\\': + if (i < format.size()) { + ++i; + } + [[fallthrough]]; + + default: + *out++ = format[i]; + break; + } + } + + return out; +} + +template +OutputIt date_format_to_localtime(OutputIt out, std::string_view format, timelib_time* t) noexcept { + return date_format_to(out, format, t, t->is_localtime); +} + +template +OutputIt date_interval_format_to(OutputIt out, std::string_view format, timelib_rel_time* t) noexcept { + if (format.empty()) { + return out; + } + + bool have_format_spec{}; + + for (auto c : format) { + if (have_format_spec) { + switch (c) { + case 'Y': + out = std::format_to(out, "{:02}", t->y); + break; + case 'y': + out = std::format_to(out, "{}", t->y); + break; + + case 'M': + out = std::format_to(out, "{:02}", t->m); + break; + case 'm': + out = std::format_to(out, "{}", t->m); + break; + + case 'D': + out = std::format_to(out, "{:02}", t->d); + break; + case 'd': + out = std::format_to(out, "{}", t->d); + break; + + case 'H': + out = std::format_to(out, "{:02}", t->h); + break; + case 'h': + out = std::format_to(out, "{}", t->h); + break; + + case 'I': + out = std::format_to(out, "{:02}", t->i); + break; + case 'i': + out = std::format_to(out, "{}", t->i); + break; + + case 'S': + out = std::format_to(out, "{:02}", t->s); + break; + case 's': + out = std::format_to(out, "{}", t->s); + break; + + case 'F': + out = std::format_to(out, "{:06}", t->us); + break; + case 'f': + out = std::format_to(out, "{}", t->us); + break; + + case 'a': { + if (static_cast(t->days) != TIMELIB_UNSET) { + out = std::format_to(out, "{}", t->days); + } else { + out = std::format_to(out, "(unknown)"); + } + } break; + case 'r': + out = std::format_to(out, "{}", t->invert ? "-" : ""); + break; + case 'R': + *out++ = (t->invert ? '-' : '+'); + break; + + case '%': + *out++ = '%'; + default: + *out++ = '%'; + *out++ = c; + break; + } + have_format_spec = false; + } else { + if (c == '%') { + have_format_spec = true; + } else { + *out++ = c; + } + } + } + return out; +} + +template +void fill_holes(timelib_time& time, timelib_tzinfo* tzi) noexcept { + time_t now_time{std::addressof(now(tzi))}; + + fill_holes(time, now_time.get()); +} + } // namespace kphp::timelib + +template<> +struct std::formatter { + constexpr auto parse(format_parse_context& ctx) const { + auto it = ctx.begin(); + + if (it != ctx.end() && *it != '}') { + throw format_error("Invalid format args for kphp::timelib::error."); + } + + return it; + } + + auto format(const kphp::timelib::error& error, auto& ctx) const noexcept { + if (error.err != nullptr) { + // spit out the first library error message, at least + return format_to(ctx.out(), "at position {} ({}): {}", error.err->error_messages[0].position, error.err->error_messages[0].character, + error.err->error_messages[0].message); + } + return format_to(ctx.out(), "unknown error"); + } +}; diff --git a/tests/phpt/datetime/13_create_interval_from_date_string.php b/tests/phpt/datetime/13_create_interval_from_date_string.php index 5b5760e4c5..de6ab71102 100644 --- a/tests/phpt/datetime/13_create_interval_from_date_string.php +++ b/tests/phpt/datetime/13_create_interval_from_date_string.php @@ -1,4 +1,4 @@ -@ok k2_skip +@ok Date: Thu, 11 Dec 2025 00:35:10 +0300 Subject: [PATCH 04/21] stash --- runtime-light/stdlib/time/dateinterval.cpp | 16 +- runtime-light/stdlib/time/dateinterval.h | 4 +- runtime-light/stdlib/time/datetime.cpp | 13 +- runtime-light/stdlib/time/datetime.h | 2 - .../stdlib/time/datetimeimmutable.cpp | 41 ++-- runtime-light/stdlib/time/datetimeimmutable.h | 2 - runtime-light/stdlib/time/datetimeinterface.h | 2 +- runtime-light/stdlib/time/time-functions.cpp | 44 ++-- runtime-light/stdlib/time/time-functions.h | 6 +- .../stdlib/time/timelib-functions.cpp | 151 ++++++------- runtime-light/stdlib/time/timelib-functions.h | 209 ++++++++---------- 11 files changed, 209 insertions(+), 281 deletions(-) diff --git a/runtime-light/stdlib/time/dateinterval.cpp b/runtime-light/stdlib/time/dateinterval.cpp index 21c93dddf9..d0e0bf465c 100644 --- a/runtime-light/stdlib/time/dateinterval.cpp +++ b/runtime-light/stdlib/time/dateinterval.cpp @@ -13,13 +13,6 @@ #include "runtime-light/stdlib/diagnostics/exception-functions.h" #include "runtime-light/stdlib/diagnostics/logs.h" -C$DateInterval::~C$DateInterval() { - if (rel_time != nullptr) { - kphp::timelib::destruct(rel_time); - rel_time = nullptr; - } -} - class_instance f$DateInterval$$__construct(const class_instance& self, const string& duration) noexcept { auto expected_rel_time{kphp::timelib::construct_interval({duration.c_str(), duration.size()})}; if (!expected_rel_time.has_value()) [[unlikely]] { @@ -28,7 +21,7 @@ class_instance f$DateInterval$$__construct(const class_instance< THROW_EXCEPTION(kphp::exception::make_throwable(err_msg)); return {}; } - self->rel_time = *expected_rel_time; + self->rel_time = std::move(*expected_rel_time); return self; } @@ -38,16 +31,15 @@ class_instance f$DateInterval$$createFromDateString(const string kphp::log::warning("DateInterval::createFromDateString(): failed to parse datetime ({}): {}", datetime.c_str(), expected_time.error()); return {}; } - timelib_time* time{*expected_time}; - vk::final_action time_deleter{[time] { kphp::timelib::destruct(time); }}; + kphp::timelib::time_t time{std::move(*expected_time)}; class_instance date_interval; date_interval.alloc(); - date_interval->rel_time = kphp::timelib::clone(std::addressof(time->relative)); + date_interval->rel_time = kphp::timelib::clone(time->relative); return date_interval; } string f$DateInterval$$format(const class_instance& self, const string& format) noexcept { string str; - kphp::timelib::date_interval_format_to(std::back_inserter(str), {format.c_str(), format.size()}, self->rel_time); + kphp::timelib::date_interval_format_to(std::back_inserter(str), {format.c_str(), format.size()}, *self->rel_time); return str; } diff --git a/runtime-light/stdlib/time/dateinterval.h b/runtime-light/stdlib/time/dateinterval.h index 7421092803..75c9061b7b 100644 --- a/runtime-light/stdlib/time/dateinterval.h +++ b/runtime-light/stdlib/time/dateinterval.h @@ -16,7 +16,7 @@ struct C$DateInterval : public refcountable_polymorphic_php_classes_virt<>, private DummyVisitorMethods { using DummyVisitorMethods::accept; - timelib_rel_time* rel_time{nullptr}; + kphp::timelib::rel_time_t rel_time{nullptr}; virtual const char* get_class() const noexcept { return R"(DateInterval)"; @@ -26,8 +26,6 @@ struct C$DateInterval : public refcountable_polymorphic_php_classes_virt<>, priv std::string_view name_view{C$DateInterval::get_class()}; return static_cast(vk::murmur_hash(name_view.data(), name_view.size())); } - - ~C$DateInterval() override; }; class_instance f$DateInterval$$__construct(const class_instance& self, const string& duration) noexcept; diff --git a/runtime-light/stdlib/time/datetime.cpp b/runtime-light/stdlib/time/datetime.cpp index a0c0076812..6a9374481f 100644 --- a/runtime-light/stdlib/time/datetime.cpp +++ b/runtime-light/stdlib/time/datetime.cpp @@ -7,13 +7,6 @@ #include "runtime-light/stdlib/diagnostics/exception-functions.h" #include "runtime-light/stdlib/time/timelib-functions.h" -C$DateTime::~C$DateTime() { - if (time != nullptr) { - kphp::timelib::destruct(time); - time = nullptr; - } -} - class_instance f$DateTime$$__construct(const class_instance& self, const string& datetime, const class_instance& timezone) noexcept { const auto& str_to_parse{datetime.empty() ? StringLibConstants::get().NOW_STR : datetime}; @@ -24,7 +17,7 @@ class_instance f$DateTime$$__construct(const class_instance(err_msg)); } - timelib_time* time{*expected_time}; + kphp::timelib::time_t time{std::move(*expected_time)}; timelib_tzinfo* tzi{!timezone.is_null() ? timezone->tzi : nullptr}; if (tzi == nullptr) { if (time->tz_info != nullptr) { @@ -34,8 +27,8 @@ class_instance f$DateTime$$__construct(const class_instancetime = time; + self->time = std::move(time); return self; } diff --git a/runtime-light/stdlib/time/datetime.h b/runtime-light/stdlib/time/datetime.h index 12db0be25b..55c4c10287 100644 --- a/runtime-light/stdlib/time/datetime.h +++ b/runtime-light/stdlib/time/datetime.h @@ -27,8 +27,6 @@ struct C$DateTime : public C$DateTimeInterface, private DummyVisitorMethods { std::string_view name_view{C$DateTime::get_class()}; return static_cast(vk::murmur_hash(name_view.data(), name_view.size())); } - - ~C$DateTime() override; }; class_instance f$DateTime$$__construct(const class_instance& self, const string& datetime = StringLibConstants::get().NOW_STR, diff --git a/runtime-light/stdlib/time/datetimeimmutable.cpp b/runtime-light/stdlib/time/datetimeimmutable.cpp index 74eb32c17c..60b7fe3f9b 100644 --- a/runtime-light/stdlib/time/datetimeimmutable.cpp +++ b/runtime-light/stdlib/time/datetimeimmutable.cpp @@ -27,19 +27,12 @@ namespace { class_instance clone_immutable(const class_instance& origin) noexcept { class_instance clone; clone.alloc(); - clone->time = kphp::timelib::clone(origin->time); + clone->time = kphp::timelib::clone(*origin->time); return clone; } } // namespace -C$DateTimeImmutable::~C$DateTimeImmutable() { - if (time != nullptr) { - kphp::timelib::destruct(time); - time = nullptr; - } -} - class_instance f$DateTimeImmutable$$__construct(const class_instance& self, const string& datetime, const class_instance& timezone) noexcept { const auto& str_to_parse{datetime.empty() ? StringLibConstants::get().NOW_STR : datetime}; @@ -50,7 +43,7 @@ class_instance f$DateTimeImmutable$$__construct(const class THROW_EXCEPTION(kphp::exception::make_throwable(err_msg)); } - timelib_time* time{*expected_time}; + kphp::timelib::time_t time{std::move(*expected_time)}; timelib_tzinfo* tzi{!timezone.is_null() ? timezone->tzi : nullptr}; if (tzi == nullptr) { if (time->tz_info != nullptr) { @@ -60,16 +53,16 @@ class_instance f$DateTimeImmutable$$__construct(const class } } - kphp::timelib::fill_holes(time, tzi); + kphp::timelib::fill_holes_with_now(*time, tzi); - self->time = time; + self->time = std::move(time); return self; } class_instance f$DateTimeImmutable$$add(const class_instance& self, const class_instance& interval) noexcept { - auto new_date = clone_immutable(self); - new_date->time = kphp::timelib::add(new_date->time, interval->rel_time); + auto new_date{clone_immutable(self)}; + new_date->time = kphp::timelib::add(*new_date->time, *interval->rel_time); return new_date; } @@ -82,31 +75,31 @@ class_instance f$DateTimeImmutable$$createFromFormat(const auto time{std::move(*expected_time)}; timelib_tzinfo* tzi{!timezone.is_null() ? timezone->tzi : nullptr}; if (tzi == nullptr) { - if (time.tz_info != nullptr) { - tzi = time.tz_info; + if (time->tz_info != nullptr) { + tzi = time->tz_info; } else if (auto* default_tzi{kphp::timelib::get_timezone_info(TimeInstanceState::get().default_timezone.c_str())}; default_tzi != nullptr) { tzi = default_tzi; } } - kphp::timelib::fill_holes(time, tzi); + kphp::timelib::fill_holes_with_now(*time, tzi); class_instance date_time; date_time.alloc(); - date_time->time = time; + date_time->time = std::move(time); return date_time; } class_instance f$DateTimeImmutable$$createFromMutable(const class_instance& object) noexcept { class_instance clone; clone.alloc(); - clone->time = kphp::timelib::clone(object->time); + clone->time = kphp::timelib::clone(*object->time); return clone; } class_instance f$DateTimeImmutable$$modify(const class_instance& self, const string& modifier) noexcept { - auto new_date = clone_immutable(self); - auto expected_success = kphp::timelib::modify(new_date->time, {modifier.c_str(), modifier.size()}); + auto new_date{clone_immutable(self)}; + auto expected_success{kphp::timelib::modify(*new_date->time, {modifier.c_str(), modifier.size()})}; if (!expected_success.has_value()) [[unlikely]] { kphp::log::warning("DateTimeImmutable::modify(): failed to parse modifier ({}): {}", modifier.c_str(), expected_success.error()); return {}; @@ -115,8 +108,8 @@ class_instance f$DateTimeImmutable$$modify(const class_inst } class_instance f$DateTimeImmutable$$setTimestamp(const class_instance& self, int64_t timestamp) noexcept { - auto new_date = clone_immutable(self); - kphp::timelib::date_timestamp_set(new_date->time, timestamp); + auto new_date{clone_immutable(self)}; + kphp::timelib::date_timestamp_set(*new_date->time, timestamp); return new_date; } @@ -124,13 +117,13 @@ class_instance f$DateTimeImmutable$$diff(const class_instance& target_object, bool absolute) noexcept { class_instance interval; interval.alloc(); - interval->rel_time = kphp::timelib::date_diff(self->time, target_object.get()->time, absolute); + interval->rel_time = kphp::timelib::date_diff(*self->time, *target_object.get()->time, absolute); return interval; } string f$DateTimeImmutable$$format(const class_instance& self, const string& format) noexcept { string str; - kphp::timelib::date_format_to_localtime(std::back_inserter(str), {format.c_str(), format.size()}, self->time); + kphp::timelib::date_format_to_localtime(std::back_inserter(str), {format.c_str(), format.size()}, *self->time); return str; } diff --git a/runtime-light/stdlib/time/datetimeimmutable.h b/runtime-light/stdlib/time/datetimeimmutable.h index c41e36056a..0ef736016d 100644 --- a/runtime-light/stdlib/time/datetimeimmutable.h +++ b/runtime-light/stdlib/time/datetimeimmutable.h @@ -28,8 +28,6 @@ struct C$DateTimeImmutable : public C$DateTimeInterface, private DummyVisitorMet std::string_view name_view{C$DateTimeImmutable::get_class()}; return static_cast(vk::murmur_hash(name_view.data(), name_view.size())); } - - ~C$DateTimeImmutable() override; }; class_instance f$DateTimeImmutable$$__construct(const class_instance& self, diff --git a/runtime-light/stdlib/time/datetimeinterface.h b/runtime-light/stdlib/time/datetimeinterface.h index 93674004a0..6b5abf902f 100644 --- a/runtime-light/stdlib/time/datetimeinterface.h +++ b/runtime-light/stdlib/time/datetimeinterface.h @@ -13,5 +13,5 @@ struct C$DateTimeInterface : public refcountable_polymorphic_php_classes_virt<> kphp::timelib::time_t time{nullptr}; virtual const char* get_class() const noexcept = 0; - virtual int get_hash() const noexcept = 0; + virtual int32_t get_hash() const noexcept = 0; }; diff --git a/runtime-light/stdlib/time/time-functions.cpp b/runtime-light/stdlib/time/time-functions.cpp index b2c079d3bc..a36c191bd2 100644 --- a/runtime-light/stdlib/time/time-functions.cpp +++ b/runtime-light/stdlib/time/time-functions.cpp @@ -18,10 +18,10 @@ namespace { constexpr std::array suffix = {"st", "nd", "rd", "th"}; -void iso_week_number(int y, int doy, int weekday, int& iw, int& iy) noexcept { - int y_leap = std::chrono::year(y).is_leap(); - int prev_y_leap = std::chrono::year(y - 1).is_leap(); - int jan1weekday = (weekday - (doy % 7) + 7) % 7; +void iso_week_number(int32_t y, int32_t doy, int32_t weekday, int32_t& iw, int32_t& iy) noexcept { + auto y_leap{std::chrono::year(y).is_leap()}; + auto prev_y_leap{std::chrono::year(y - 1).is_leap()}; + auto jan1weekday{(weekday - (doy % 7) + 7) % 7}; if (weekday == 0) { weekday = 7; @@ -42,7 +42,7 @@ void iso_week_number(int y, int doy, int weekday, int& iw, int& iy) noexcept { } /* Find if Y M D falls in YearNumber Y+1, WeekNumber 1 */ if (iy == y) { - int i = y_leap ? 366 : 365; + auto i{y_leap ? 366 : 365}; if ((i - (doy - y_leap + 1)) < (4 - weekday)) { iy = y + 1; iw = 1; @@ -51,7 +51,7 @@ void iso_week_number(int y, int doy, int weekday, int& iw, int& iy) noexcept { } /* Find if Y M D falls in YearNumber Y, WeekNumber 1 through 53 */ if (iy == y) { - int j = doy + (7 - weekday) + jan1weekday; + auto j{doy + (7 - weekday) + jan1weekday}; iw = j / 7; if (jan1weekday > 4) { iw -= 1; @@ -77,21 +77,21 @@ int64_t fix_year(int64_t year) noexcept { string date(const string& format, const tm& t, int64_t timestamp, bool local) noexcept { string_buffer& SB{RuntimeContext::get().static_SB}; - int year = t.tm_year + 1900; - int month = t.tm_mon + 1; - int day = t.tm_mday; - int hour = t.tm_hour; - int hour12 = (hour + 11) % 12 + 1; - int minute = t.tm_min; - int second = t.tm_sec; - int day_of_week = t.tm_wday; - int day_of_year = t.tm_yday; - int64_t internet_time = 0; - int iso_week = 0; - int iso_year = 0; + auto year{t.tm_year + 1900}; + auto month{t.tm_mon + 1}; + auto day{t.tm_mday}; + auto hour{t.tm_hour}; + auto hour12{(hour + 11) % 12 + 1}; + auto minute{t.tm_min}; + auto second{t.tm_sec}; + auto day_of_week{t.tm_wday}; + auto day_of_year{t.tm_yday}; + int64_t internet_time{0}; + auto iso_week{0}; + auto iso_year{0}; SB.clean(); - for (int i = 0; i < static_cast(format.size()); i++) { + for (auto i{0}; i < format.size(); i++) { switch (format[i]) { case 'd': SB << static_cast(day / 10 + '0') << static_cast(day % 10 + '0'); @@ -109,7 +109,7 @@ string date(const string& format, const tm& t, int64_t timestamp, bool local) no SB << (day_of_week == 0 ? '7' : static_cast(day_of_week + '0')); break; case 'S': { - int c = INT_MAX; + auto c{INT_MAX}; switch (day) { case 1: case 21: @@ -157,7 +157,7 @@ string date(const string& format, const tm& t, int64_t timestamp, bool local) no std::chrono::year_month_day_last(std::chrono::year(year), std::chrono::month_day_last{std::chrono::month(month) / std::chrono::last}).day()); break; case 'L': - SB << static_cast(std::chrono::year(year).is_leap()); + SB << static_cast(std::chrono::year(year).is_leap()); break; case 'o': iso_week_number(year, day_of_year, day_of_week, iso_week, iso_year); @@ -211,7 +211,7 @@ string date(const string& format, const tm& t, int64_t timestamp, bool local) no } break; case 'I': - SB << static_cast(t.tm_isdst > 0); + SB << static_cast(t.tm_isdst > 0); break; case 'O': if (local) { diff --git a/runtime-light/stdlib/time/time-functions.h b/runtime-light/stdlib/time/time-functions.h index b1e37ce213..c7308070d8 100644 --- a/runtime-light/stdlib/time/time-functions.h +++ b/runtime-light/stdlib/time/time-functions.h @@ -20,8 +20,8 @@ namespace kphp::time::impl { -constexpr inline int64_t CHECKDATE_YEAR_MIN = 1; -constexpr inline int64_t CHECKDATE_YEAR_MAX = 32767; +constexpr inline int64_t CHECKDATE_YEAR_MIN{1}; +constexpr inline int64_t CHECKDATE_YEAR_MAX{32767}; int64_t fix_year(int64_t year) noexcept; @@ -49,7 +49,7 @@ inline string f$_microtime_string() noexcept { const auto seconds{duration_cast(time_since_epoch).count()}; const auto nanoseconds{duration_cast(time_since_epoch).count() % 1'000'000'000}; - static constexpr size_t default_buffer_size = 60; + static constexpr size_t default_buffer_size{60}; char buf[default_buffer_size]; const auto len{snprintf(buf, default_buffer_size, "0.%09lld %lld", nanoseconds, seconds)}; return {buf, static_cast(len)}; diff --git a/runtime-light/stdlib/time/timelib-functions.cpp b/runtime-light/stdlib/time/timelib-functions.cpp index 8d313562b9..424f8ceb80 100644 --- a/runtime-light/stdlib/time/timelib-functions.cpp +++ b/runtime-light/stdlib/time/timelib-functions.cpp @@ -23,51 +23,43 @@ namespace kphp::timelib { -error::~error() { - if (err != nullptr) { - destruct(err); - } -} - -std::expected construct_time(std::string_view time_sv) noexcept { +std::expected construct_time(std::string_view time_sv) noexcept { timelib_error_container* errors{}; time_t time{(kphp::memory::libc_alloc_guard{}, - timelib_strtotime(time_sv.data(), time_sv.size(), std::addressof(errors), timelib_builtin_db(), kphp::timelib::get_timezone_info))}; + timelib_strtotime(time_sv.data(), time_sv.size(), std::addressof(errors), timelib_builtin_db(), kphp::timelib::get_timezone_info))}; if (errors->error_count != 0) [[unlikely]] { - destruct(*time); - return std::unexpected{error{errors}}; + return std::unexpected{error_container_t{errors}}; } - destruct(*errors); + error_container_t{errors}; return time; } -std::expected construct_time(std::string_view time_sv, const char* format) noexcept { +std::expected construct_time(std::string_view time_sv, const char* format) noexcept { timelib_error_container* err{nullptr}; - time_t t{(kphp::memory::libc_alloc_guard{}, timelib_parse_from_format(format, time_sv.data(), time_sv.size(), std::addressof(err), - timelib_builtin_db(), kphp::timelib::get_timezone_info))}; + time_t t{(kphp::memory::libc_alloc_guard{}, + timelib_parse_from_format(format, time_sv.data(), time_sv.size(), std::addressof(err), timelib_builtin_db(), kphp::timelib::get_timezone_info))}; if (err && err->error_count) { - destruct(*t); - return std::unexpected{error{err}}; + return std::unexpected{error_container_t{err}}; } - destruct(*err); + error_container_t{err}; return t; } -time_offset_t construct_time_offset(timelib_time* t) noexcept { - if (t->zone_type == TIMELIB_ZONETYPE_ABBR) { +time_offset_t construct_time_offset(timelib_time& t) noexcept { + if (t.zone_type == TIMELIB_ZONETYPE_ABBR) { time_offset_t offset{(kphp::memory::libc_alloc_guard{}, timelib_time_offset_ctor())}; - offset->offset = (t->z + (t->dst * 3600)); + offset->offset = (t.z + (t.dst * 3600)); offset->leap_secs = 0; - offset->is_dst = t->dst; - offset->abbr = (kphp::memory::libc_alloc_guard{}, timelib_strdup(t->tz_abbr)); + offset->is_dst = t.dst; + offset->abbr = (kphp::memory::libc_alloc_guard{}, timelib_strdup(t.tz_abbr)); return offset; } - if (t->zone_type == TIMELIB_ZONETYPE_OFFSET) { + if (t.zone_type == TIMELIB_ZONETYPE_OFFSET) { time_offset_t offset{(kphp::memory::libc_alloc_guard{}, timelib_time_offset_ctor())}; - offset->offset = (t->z); + offset->offset = (t.z); offset->leap_secs = 0; offset->is_dst = 0; offset->abbr = (kphp::memory::libc_alloc_guard{}, static_cast(timelib_malloc(9))); // GMT±xxxx\0 @@ -76,28 +68,26 @@ time_offset_t construct_time_offset(timelib_time* t) noexcept { std::format_to_n(offset->abbr, 9, "GMT{}{:02}{:02}", (offset->offset < 0) ? '-' : '+', hours_offset, std::abs((offset->offset % 3600) / 60)); return offset; } - return time_offset_t{timelib_get_time_zone_info(t->sse, t->tz_info)}; + return time_offset_t{timelib_get_time_zone_info(t.sse, t.tz_info)}; } -std::expected construct_interval(std::string_view format) noexcept { +std::expected construct_interval(std::string_view format) noexcept { timelib_time* b{nullptr}; timelib_time* e{nullptr}; vk::final_action e_deleter{[e]() { kphp::memory::libc_alloc_guard{}, free(e); }}; vk::final_action b_deleter{[b]() { kphp::memory::libc_alloc_guard{}, free(b); }}; timelib_rel_time* p{nullptr}; - int r{}; + int r{}; // it's intentionally declared as 'int' since timelib_strtointerval accepts 'int' timelib_error_container* errors{nullptr}; kphp::memory::libc_alloc_guard{}, timelib_strtointerval(format.data(), format.size(), std::addressof(b), std::addressof(e), std::addressof(p), std::addressof(r), std::addressof(errors)); if (errors->error_count > 0) { - if (p != nullptr) { - destruct(*p); - } - return std::unexpected{error{errors}}; + rel_time_t{p}; + return std::unexpected{error_container_t{errors}}; } - destruct(*errors); + error_container_t{errors}; if (p != nullptr) { return rel_time_t{p}; @@ -109,28 +99,27 @@ std::expected construct_interval(std::string_view format) noe return kphp::memory::libc_alloc_guard{}, rel_time_t{timelib_diff(b, e)}; } - return std::unexpected{error{nullptr}}; + return std::unexpected{error_container_t{nullptr}}; } -timelib_time* add(timelib_time* t, timelib_rel_time* interval) noexcept { - timelib_time* new_time{(kphp::memory::libc_alloc_guard{}, timelib_add(t, interval))}; - kphp::memory::libc_alloc_guard{}, timelib_time_dtor(t); +time_t add(timelib_time& t, timelib_rel_time& interval) noexcept { + time_t new_time{(kphp::memory::libc_alloc_guard{}, timelib_add(std::addressof(t), std::addressof(interval)))}; return new_time; } -timelib_rel_time* clone(timelib_rel_time* rt) noexcept { - return kphp::memory::libc_alloc_guard{}, timelib_rel_time_clone(rt); +rel_time_t clone(timelib_rel_time& rt) noexcept { + return rel_time_t{(kphp::memory::libc_alloc_guard{}, timelib_rel_time_clone(std::addressof(rt)))}; } -timelib_time* clone(timelib_time* t) noexcept { - return kphp::memory::libc_alloc_guard{}, timelib_time_clone(t); +time_t clone(timelib_time& t) noexcept { + return time_t{(kphp::memory::libc_alloc_guard{}, timelib_time_clone(std::addressof(t)))}; } -timelib_rel_time* date_diff(timelib_time* time1, timelib_time* time2, bool absolute) noexcept { - timelib_update_ts(time1, nullptr); - timelib_update_ts(time2, nullptr); +rel_time_t date_diff(timelib_time& time1, timelib_time& time2, bool absolute) noexcept { + timelib_update_ts(std::addressof(time1), nullptr); + timelib_update_ts(std::addressof(time2), nullptr); - timelib_rel_time* diff{(kphp::memory::libc_alloc_guard{}, timelib_diff(time1, time2))}; + rel_time_t diff{(kphp::memory::libc_alloc_guard{}, timelib_diff(std::addressof(time1), std::addressof(time2)))}; if (absolute) { diff->invert = 0; } @@ -138,7 +127,7 @@ timelib_rel_time* date_diff(timelib_time* time1, timelib_time* time2, bool absol } std::string_view date_full_day_name(timelib_sll y, timelib_sll m, timelib_sll d) noexcept { - timelib_sll day_of_week = timelib_day_of_week(y, m, d); + timelib_sll day_of_week{timelib_day_of_week(y, m, d)}; if (day_of_week < 0) { return "Unknown"; } @@ -146,7 +135,7 @@ std::string_view date_full_day_name(timelib_sll y, timelib_sll m, timelib_sll d) } std::string_view date_short_day_name(timelib_sll y, timelib_sll m, timelib_sll d) noexcept { - timelib_sll day_of_week = timelib_day_of_week(y, m, d); + timelib_sll day_of_week{timelib_day_of_week(y, m, d)}; if (day_of_week < 0) { return "Unknown"; } @@ -156,21 +145,21 @@ std::string_view date_short_day_name(timelib_sll y, timelib_sll m, timelib_sll d int64_t date_timestamp_get(timelib_time& t) noexcept { timelib_update_ts(std::addressof(t), nullptr); - int error = 0; - timelib_long timestamp = timelib_date_to_int(std::addressof(t), &error); + int error{}; // it's intentionally declared as 'int' since timelib_date_to_int accepts 'int' + timelib_long timestamp{timelib_date_to_int(std::addressof(t), &error)}; // the 'error' should be always 0 on x64 platform log::assertion(error == 0); return timestamp; } -void date_timestamp_set(timelib_time* t, int64_t timestamp) noexcept { - kphp::memory::libc_alloc_guard{}, timelib_unixtime2local(t, static_cast(timestamp)); - timelib_update_ts(t, nullptr); - t->us = 0; +void date_timestamp_set(timelib_time& t, int64_t timestamp) noexcept { + kphp::memory::libc_alloc_guard{}, timelib_unixtime2local(std::addressof(t), static_cast(timestamp)); + timelib_update_ts(std::addressof(t), nullptr); + t.us = 0; } -const char* english_suffix(timelib_sll number) noexcept { +std::string_view english_suffix(timelib_sll number) noexcept { if (number >= 10 && number <= 19) { return "th"; } else { @@ -308,70 +297,68 @@ std::optional mktime(std::optional hou, std::optional return now->sse; } -std::expected modify(timelib_time* t, std::string_view modifier) noexcept { +std::expected modify(timelib_time& t, std::string_view modifier) noexcept { auto expected_tmp_time{construct_time(modifier)}; if (!expected_tmp_time.has_value()) [[unlikely]] { - return std::unexpected{std::move(expected_tmp_time).error()}; + return std::unexpected{std::move(expected_tmp_time.error())}; } - timelib_time* tmp_time{*expected_tmp_time}; - - vk::final_action tmp_time_deleter{[tmp_time] { destruct(tmp_time); }}; + time_t tmp_time{std::move(*expected_tmp_time)}; - std::memcpy(&t->relative, &tmp_time->relative, sizeof(timelib_rel_time)); - t->have_relative = tmp_time->have_relative; - t->sse_uptodate = 0; + std::memcpy(std::addressof(t.relative), std::addressof(tmp_time->relative), sizeof(timelib_rel_time)); + t.have_relative = tmp_time->have_relative; + t.sse_uptodate = 0; if (tmp_time->y != TIMELIB_UNSET) { - t->y = tmp_time->y; + t.y = tmp_time->y; } if (tmp_time->m != TIMELIB_UNSET) { - t->m = tmp_time->m; + t.m = tmp_time->m; } if (tmp_time->d != TIMELIB_UNSET) { - t->d = tmp_time->d; + t.d = tmp_time->d; } if (tmp_time->h != TIMELIB_UNSET) { - t->h = tmp_time->h; + t.h = tmp_time->h; if (tmp_time->i != TIMELIB_UNSET) { - t->i = tmp_time->i; + t.i = tmp_time->i; if (tmp_time->s != TIMELIB_UNSET) { - t->s = tmp_time->s; + t.s = tmp_time->s; } else { - t->s = 0; + t.s = 0; } } else { - t->i = 0; - t->s = 0; + t.i = 0; + t.s = 0; } } if (tmp_time->us != TIMELIB_UNSET) { - t->us = tmp_time->us; + t.us = tmp_time->us; } - timelib_update_ts(t, nullptr); - timelib_update_from_sse(t); - t->have_relative = 0; - std::memset(&t->relative, 0, sizeof(t->relative)); + timelib_update_ts(std::addressof(t), nullptr); + timelib_update_from_sse(std::addressof(t)); + t.have_relative = 0; + std::memset(std::addressof(t.relative), 0, sizeof(t.relative)); return {}; } -timelib_time& now(timelib_tzinfo* tzi) noexcept { - timelib_time& res{(kphp::memory::libc_alloc_guard{}, *timelib_time_ctor())}; +time_t now(timelib_tzinfo* tzi) noexcept { + time_t res{(kphp::memory::libc_alloc_guard{}, timelib_time_ctor())}; - res.tz_info = tzi; - res.zone_type = TIMELIB_ZONETYPE_ID; + res->tz_info = tzi; + res->zone_type = TIMELIB_ZONETYPE_ID; namespace chrono = std::chrono; const auto time_since_epoch{chrono::system_clock::now().time_since_epoch()}; const auto sec{chrono::duration_cast(time_since_epoch).count()}; const auto usec{chrono::duration_cast(time_since_epoch % chrono::seconds{1}).count()}; - timelib_unixtime2local(std::addressof(res), static_cast(sec)); - res.us = usec; + timelib_unixtime2local(res.get(), static_cast(sec)); + res->us = usec; return res; } @@ -397,7 +384,7 @@ std::optional strtotime(std::string_view timezone, std::string_view dat auto expected_time{construct_time(datetime)}; if (!expected_time.has_value()) [[unlikely]] { - auto* errors{expected_time.error().err}; + auto* errors{expected_time.error().get()}; kphp::log::warning("got {} errors in timelib_strtotime", errors->error_count); // TODO should we logs all the errors? return {}; } diff --git a/runtime-light/stdlib/time/timelib-functions.h b/runtime-light/stdlib/time/timelib-functions.h index b25037b69e..f8611bde37 100644 --- a/runtime-light/stdlib/time/timelib-functions.h +++ b/runtime-light/stdlib/time/timelib-functions.h @@ -26,26 +26,6 @@ constexpr inline std::array MON_SHORT_NAMES = {"Jan", "Feb constexpr inline std::array DAY_FULL_NAMES = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; constexpr inline std::array DAY_SHORT_NAMES = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; -struct error { - timelib_error_container* err{nullptr}; - - explicit error(timelib_error_container* err) noexcept - : err{err} {} - - error(const error&) = delete; - error& operator=(const error&) = delete; - - error(error&& other) noexcept { - std::swap(err, other.err); - } - error& operator=(error&& other) noexcept { - std::swap(err, other.err); - return *this; - } - - ~error(); -}; - struct destructor { void operator()(timelib_error_container* ec) const noexcept { kphp::memory::libc_alloc_guard{}, timelib_error_container_dtor(ec); @@ -69,17 +49,17 @@ using rel_time_t = std::unique_ptr; using time_t = std::unique_ptr; using time_offset_t = std::unique_ptr; -std::expected construct_time(std::string_view time_sv) noexcept; -std::expected construct_time(std::string_view time_sv, const char* format) noexcept; -time_offset_t construct_time_offset(timelib_time* t) noexcept; -std::expected construct_interval(std::string_view format) noexcept; +std::expected construct_time(std::string_view time_sv) noexcept; +std::expected construct_time(std::string_view time_sv, const char* format) noexcept; +time_offset_t construct_time_offset(timelib_time& t) noexcept; +std::expected construct_interval(std::string_view format) noexcept; -timelib_time* add(timelib_time* t, timelib_rel_time* interval) noexcept; +time_t add(timelib_time& t, timelib_rel_time& interval) noexcept; -timelib_rel_time* clone(timelib_rel_time* rt) noexcept; -timelib_time* clone(timelib_time* t) noexcept; +rel_time_t clone(timelib_rel_time& rt) noexcept; +time_t clone(timelib_time& t) noexcept; -timelib_rel_time* date_diff(timelib_time* time1, timelib_time* time2, bool absolute) noexcept; +rel_time_t date_diff(timelib_time& time1, timelib_time& time2, bool absolute) noexcept; std::string_view date_full_day_name(timelib_sll y, timelib_sll m, timelib_sll d) noexcept; @@ -87,23 +67,9 @@ std::string_view date_short_day_name(timelib_sll y, timelib_sll m, timelib_sll d int64_t date_timestamp_get(timelib_time& t) noexcept; -void date_timestamp_set(timelib_time* t, int64_t timestamp) noexcept; - -const char* english_suffix(timelib_sll number) noexcept; +void date_timestamp_set(timelib_time& t, int64_t timestamp) noexcept; -template -void fill_holes(timelib_time& t, timelib_time* filler) noexcept { - int options = TIMELIB_NO_CLONE; - if constexpr (override_time) { - options |= TIMELIB_OVERRIDE_TIME; - } - - timelib_fill_holes(std::addressof(t), filler, options); - timelib_update_ts(std::addressof(t), filler->tz_info); - timelib_update_from_sse(std::addressof(t)); - - t.have_relative = 0; -} +std::string_view english_suffix(timelib_sll number) noexcept; timelib_tzinfo* get_timezone_info(const char* timezone) noexcept; /** @@ -128,16 +94,16 @@ bool is_leap_year(int32_t year) noexcept; std::optional mktime(std::optional hou, std::optional min, std::optional sec, std::optional mon, std::optional day, std::optional yea) noexcept; -std::expected modify(timelib_time* t, std::string_view modifier) noexcept; +std::expected modify(timelib_time& t, std::string_view modifier) noexcept; -timelib_time& now(timelib_tzinfo* tzi) noexcept; +time_t now(timelib_tzinfo* tzi) noexcept; std::optional strtotime(std::string_view timezone, std::string_view datetime, int64_t timestamp) noexcept; bool valid_date(int64_t year, int64_t month, int64_t day) noexcept; template -OutputIt date_format_to(OutputIt out, std::string_view format, timelib_time* t, bool localtime) noexcept { +OutputIt date_format_to(OutputIt out, std::string_view format, timelib_time& t, bool localtime) noexcept { if (format.empty()) { return out; } @@ -148,46 +114,46 @@ OutputIt date_format_to(OutputIt out, std::string_view format, timelib_time* t, timelib_sll isoweek{}; timelib_sll isoyear{}; - for (std::size_t i = 0; i < format.size(); ++i) { + for (std::size_t i{0}; i < format.size(); ++i) { bool rfc_colon{false}; switch (format[i]) { // day case 'd': - out = std::format_to(out, "{:02}", t->d); + out = std::format_to(out, "{:02}", t.d); break; case 'D': - out = std::format_to(out, "{}", date_short_day_name(t->y, t->m, t->d)); + out = std::format_to(out, "{}", date_short_day_name(t.y, t.m, t.d)); break; case 'j': - out = std::format_to(out, "{}", t->d); + out = std::format_to(out, "{}", t.d); break; case 'l': - out = std::format_to(out, "{}", date_full_day_name(t->y, t->m, t->d)); + out = std::format_to(out, "{}", date_full_day_name(t.y, t.m, t.d)); break; case 'S': - out = std::format_to(out, "{}", english_suffix(t->d)); + out = std::format_to(out, "{}", english_suffix(t.d)); break; case 'w': - out = std::format_to(out, "{}", timelib_day_of_week(t->y, t->m, t->d)); + out = std::format_to(out, "{}", timelib_day_of_week(t.y, t.m, t.d)); break; case 'N': - out = std::format_to(out, "{}", timelib_iso_day_of_week(t->y, t->m, t->d)); + out = std::format_to(out, "{}", timelib_iso_day_of_week(t.y, t.m, t.d)); break; case 'z': - out = std::format_to(out, "{}", timelib_day_of_year(t->y, t->m, t->d)); + out = std::format_to(out, "{}", timelib_day_of_year(t.y, t.m, t.d)); break; // week case 'W': if (!weekYearSet) { - timelib_isoweek_from_date(t->y, t->m, t->d, &isoweek, &isoyear); + timelib_isoweek_from_date(t.y, t.m, t.d, &isoweek, &isoyear); weekYearSet = true; } out = std::format_to(out, "{:02}", isoweek); break; // iso weeknr case 'o': if (!weekYearSet) { - timelib_isoweek_from_date(t->y, t->m, t->d, &isoweek, &isoyear); + timelib_isoweek_from_date(t.y, t.m, t.d, &isoweek, &isoyear); weekYearSet = 1; } out = std::format_to(out, "{}", isoyear); @@ -195,41 +161,41 @@ OutputIt date_format_to(OutputIt out, std::string_view format, timelib_time* t, // month case 'F': - out = std::format_to(out, "{}", MON_FULL_NAMES[t->m - 1]); + out = std::format_to(out, "{}", MON_FULL_NAMES[t.m - 1]); break; case 'm': - out = std::format_to(out, "{:02}", t->m); + out = std::format_to(out, "{:02}", t.m); break; case 'M': - out = std::format_to(out, "{}", MON_SHORT_NAMES[t->m - 1]); + out = std::format_to(out, "{}", MON_SHORT_NAMES[t.m - 1]); break; case 'n': - out = std::format_to(out, "{}", t->m); + out = std::format_to(out, "{}", t.m); break; case 't': - out = std::format_to(out, "{}", timelib_days_in_month(t->y, t->m)); + out = std::format_to(out, "{}", timelib_days_in_month(t.y, t.m)); break; // year case 'L': - out = std::format_to(out, "{}", is_leap_year(t->y)); + out = std::format_to(out, "{}", is_leap_year(t.y)); break; case 'y': - out = std::format_to(out, "{:02}", t->y % 100); + out = std::format_to(out, "{:02}", t.y % 100); break; case 'Y': - out = std::format_to(out, "{}{:04}", t->y < 0 ? "-" : "", std::abs(t->y)); + out = std::format_to(out, "{}{:04}", t.y < 0 ? "-" : "", std::abs(t.y)); break; // time case 'a': - out = std::format_to(out, "{}", t->h >= 12 ? "pm" : "am"); + out = std::format_to(out, "{}", t.h >= 12 ? "pm" : "am"); break; case 'A': - out = std::format_to(out, "{}", t->h >= 12 ? "PM" : "AM"); + out = std::format_to(out, "{}", t.h >= 12 ? "PM" : "AM"); break; case 'B': { - auto retval{(((t->sse) - ((t->sse) - (((t->sse) % 86400) + 3600))) * 10)}; + auto retval{(((t.sse) - ((t.sse) - (((t.sse) % 86400) + 3600))) * 10)}; if (retval < 0) { retval += 864000; } @@ -239,28 +205,28 @@ OutputIt date_format_to(OutputIt out, std::string_view format, timelib_time* t, break; } case 'g': - out = std::format_to(out, "{}", (t->h % 12) ? t->h % 12 : 12); + out = std::format_to(out, "{}", (t.h % 12) ? t.h % 12 : 12); break; case 'G': - out = std::format_to(out, "{}", t->h); + out = std::format_to(out, "{}", t.h); break; case 'h': - out = std::format_to(out, "{:02}", (t->h % 12) ? t->h % 12 : 12); + out = std::format_to(out, "{:02}", (t.h % 12) ? t.h % 12 : 12); break; case 'H': - out = std::format_to(out, "{:02}", t->h); + out = std::format_to(out, "{:02}", t.h); break; case 'i': - out = std::format_to(out, "{:02}", t->i); + out = std::format_to(out, "{:02}", t.i); break; case 's': - out = std::format_to(out, "{:02}", t->s); + out = std::format_to(out, "{:02}", t.s); break; case 'u': - out = std::format_to(out, "{:06}", t->us); + out = std::format_to(out, "{:06}", t.us); break; case 'v': - out = std::format_to(out, "{:03}", t->us / 1000); + out = std::format_to(out, "{:03}", t.us / 1000); break; // timezone @@ -281,9 +247,9 @@ OutputIt date_format_to(OutputIt out, std::string_view format, timelib_time* t, if (!localtime) { out = std::format_to(out, "UTC"); } else { - switch (t->zone_type) { + switch (t.zone_type) { case TIMELIB_ZONETYPE_ID: - out = std::format_to(out, "{}", t->tz_info->name); + out = std::format_to(out, "{}", t.tz_info->name); break; case TIMELIB_ZONETYPE_ABBR: out = std::format_to(out, "{}", offset->abbr); @@ -300,17 +266,17 @@ OutputIt date_format_to(OutputIt out, std::string_view format, timelib_time* t, // full date/time case 'c': - out = std::format_to(out, "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}{}{:02}:{:02}", t->y, t->m, t->d, t->h, t->i, t->s, + out = std::format_to(out, "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}{}{:02}:{:02}", t.y, t.m, t.d, t.h, t.i, t.s, localtime ? ((offset->offset < 0) ? '-' : '+') : '+', localtime ? std::abs(offset->offset / 3600) : 0, localtime ? std::abs((offset->offset % 3600) / 60) : 0); break; case 'r': - out = std::format_to(out, "{:3}, {:02} {:3} {:04} {:02}:{:02}:{:02} {}{:02}{:02}", date_short_day_name(t->y, t->m, t->d), t->d, MON_SHORT_NAMES[t->m - 1], - t->y, t->h, t->i, t->s, localtime ? ((offset->offset < 0) ? '-' : '+') : '+', localtime ? abs(offset->offset / 3600) : 0, + out = std::format_to(out, "{:3}, {:02} {:3} {:04} {:02}:{:02}:{:02} {}{:02}{:02}", date_short_day_name(t.y, t.m, t.d), t.d, MON_SHORT_NAMES[t.m - 1], t.y, + t.h, t.i, t.s, localtime ? ((offset->offset < 0) ? '-' : '+') : '+', localtime ? abs(offset->offset / 3600) : 0, localtime ? abs((offset->offset % 3600) / 60) : 0); break; case 'U': - out = std::format_to(out, "{}", t->sse); + out = std::format_to(out, "{}", t.sse); break; case '\\': @@ -329,12 +295,12 @@ OutputIt date_format_to(OutputIt out, std::string_view format, timelib_time* t, } template -OutputIt date_format_to_localtime(OutputIt out, std::string_view format, timelib_time* t) noexcept { - return date_format_to(out, format, t, t->is_localtime); +OutputIt date_format_to_localtime(OutputIt out, std::string_view format, timelib_time& t) noexcept { + return date_format_to(out, format, t, t.is_localtime); } template -OutputIt date_interval_format_to(OutputIt out, std::string_view format, timelib_rel_time* t) noexcept { +OutputIt date_interval_format_to(OutputIt out, std::string_view format, timelib_rel_time& t) noexcept { if (format.empty()) { return out; } @@ -345,66 +311,66 @@ OutputIt date_interval_format_to(OutputIt out, std::string_view format, timelib_ if (have_format_spec) { switch (c) { case 'Y': - out = std::format_to(out, "{:02}", t->y); + out = std::format_to(out, "{:02}", t.y); break; case 'y': - out = std::format_to(out, "{}", t->y); + out = std::format_to(out, "{}", t.y); break; case 'M': - out = std::format_to(out, "{:02}", t->m); + out = std::format_to(out, "{:02}", t.m); break; case 'm': - out = std::format_to(out, "{}", t->m); + out = std::format_to(out, "{}", t.m); break; case 'D': - out = std::format_to(out, "{:02}", t->d); + out = std::format_to(out, "{:02}", t.d); break; case 'd': - out = std::format_to(out, "{}", t->d); + out = std::format_to(out, "{}", t.d); break; case 'H': - out = std::format_to(out, "{:02}", t->h); + out = std::format_to(out, "{:02}", t.h); break; case 'h': - out = std::format_to(out, "{}", t->h); + out = std::format_to(out, "{}", t.h); break; case 'I': - out = std::format_to(out, "{:02}", t->i); + out = std::format_to(out, "{:02}", t.i); break; case 'i': - out = std::format_to(out, "{}", t->i); + out = std::format_to(out, "{}", t.i); break; case 'S': - out = std::format_to(out, "{:02}", t->s); + out = std::format_to(out, "{:02}", t.s); break; case 's': - out = std::format_to(out, "{}", t->s); + out = std::format_to(out, "{}", t.s); break; case 'F': - out = std::format_to(out, "{:06}", t->us); + out = std::format_to(out, "{:06}", t.us); break; case 'f': - out = std::format_to(out, "{}", t->us); + out = std::format_to(out, "{}", t.us); break; case 'a': { - if (static_cast(t->days) != TIMELIB_UNSET) { - out = std::format_to(out, "{}", t->days); + if (t.days != TIMELIB_UNSET) { + out = std::format_to(out, "{}", t.days); } else { out = std::format_to(out, "(unknown)"); } } break; case 'r': - out = std::format_to(out, "{}", t->invert ? "-" : ""); + out = std::format_to(out, "{}", t.invert ? "-" : ""); break; case 'R': - *out++ = (t->invert ? '-' : '+'); + *out++ = (t.invert ? '-' : '+'); break; case '%': @@ -427,31 +393,34 @@ OutputIt date_interval_format_to(OutputIt out, std::string_view format, timelib_ } template -void fill_holes(timelib_time& time, timelib_tzinfo* tzi) noexcept { - time_t now_time{std::addressof(now(tzi))}; +void fill_holes_with_now(timelib_time& time, timelib_tzinfo* tzi) noexcept { + time_t now_time{now(tzi)}; - fill_holes(time, now_time.get()); + auto options{TIMELIB_NO_CLONE}; + if constexpr (override_time) { + options |= TIMELIB_OVERRIDE_TIME; + } + + timelib_fill_holes(std::addressof(time), now_time.get(), options); + timelib_update_ts(std::addressof(time), now_time->tz_info); + timelib_update_from_sse(std::addressof(time)); + + time.have_relative = 0; } } // namespace kphp::timelib template<> -struct std::formatter { - constexpr auto parse(format_parse_context& ctx) const { - auto it = ctx.begin(); - - if (it != ctx.end() && *it != '}') { - throw format_error("Invalid format args for kphp::timelib::error."); - } - - return it; +struct std::formatter { + constexpr auto parse(auto& ctx) const { + return ctx.begin(); } - auto format(const kphp::timelib::error& error, auto& ctx) const noexcept { - if (error.err != nullptr) { + auto format(const kphp::timelib::error_container_t& error, auto& ctx) const noexcept { + if (error != nullptr) { // spit out the first library error message, at least - return format_to(ctx.out(), "at position {} ({}): {}", error.err->error_messages[0].position, error.err->error_messages[0].character, - error.err->error_messages[0].message); + return format_to(ctx.out(), "at position {} ({}): {}", error->error_messages[0].position, error->error_messages[0].character, + error->error_messages[0].message); } return format_to(ctx.out(), "unknown error"); } From 6c3b902ff68e63d30e31896e3eab9449dc7aaf2b Mon Sep 17 00:00:00 2001 From: Karim Shamazov Date: Thu, 11 Dec 2025 01:49:13 +0300 Subject: [PATCH 05/21] fixes --- runtime-light/stdlib/time/dateinterval.cpp | 2 +- runtime-light/stdlib/time/datetime.cpp | 49 +++++++++++++++++++ .../stdlib/time/datetimeimmutable.cpp | 8 +-- .../stdlib/time/timelib-functions.cpp | 19 ++++--- runtime-light/stdlib/time/timelib-functions.h | 25 +++++----- 5 files changed, 78 insertions(+), 25 deletions(-) diff --git a/runtime-light/stdlib/time/dateinterval.cpp b/runtime-light/stdlib/time/dateinterval.cpp index d0e0bf465c..aba8b005ef 100644 --- a/runtime-light/stdlib/time/dateinterval.cpp +++ b/runtime-light/stdlib/time/dateinterval.cpp @@ -40,6 +40,6 @@ class_instance f$DateInterval$$createFromDateString(const string string f$DateInterval$$format(const class_instance& self, const string& format) noexcept { string str; - kphp::timelib::date_interval_format_to(std::back_inserter(str), {format.c_str(), format.size()}, *self->rel_time); + kphp::timelib::format_to(std::back_inserter(str), {format.c_str(), format.size()}, *self->rel_time); return str; } diff --git a/runtime-light/stdlib/time/datetime.cpp b/runtime-light/stdlib/time/datetime.cpp index 6a9374481f..b1594952db 100644 --- a/runtime-light/stdlib/time/datetime.cpp +++ b/runtime-light/stdlib/time/datetime.cpp @@ -32,3 +32,52 @@ class_instance f$DateTime$$__construct(const class_instancetime = std::move(time); return self; } + +class_instance f$DateTime$$add(const class_instance& self, const class_instance& interval) noexcept { + self->time = kphp::timelib::add(*self->time, *interval->rel_time); + return self; +} + +class_instance f$DateTime$$createFromFormat(const string& format, const string& datetime, const class_instance& timezone) noexcept { + auto expected_time{kphp::timelib::construct_time({datetime.c_str(), datetime.size()}, format.c_str())}; + if (!expected_time.has_value()) [[unlikely]] { + return {}; + } + auto time{std::move(*expected_time)}; + timelib_tzinfo* tzi{!timezone.is_null() ? timezone->tzi : nullptr}; + if (tzi == nullptr) { + if (time->tz_info != nullptr) { + tzi = time->tz_info; + } else if (auto* default_tzi{kphp::timelib::get_timezone_info(TimeInstanceState::get().default_timezone.c_str())}; default_tzi != nullptr) { + tzi = default_tzi; + } + } + + kphp::timelib::fill_holes_with_now(*time, tzi); + + class_instance date_time; + date_time.alloc(); + date_time->time = std::move(time); + return date_time; +} + +class_instance f$DateTime$$createFromImmutable(const class_instance& object) noexcept { + class_instance clone; + clone.alloc(); + clone->time = kphp::timelib::clone(*object->time); + return clone; +} + +class_instance f$DateTime$$modify(const class_instance& self, const string& modifier) noexcept { + auto expected_success{kphp::timelib::modify(*self->time, {modifier.c_str(), modifier.size()})}; + if (!expected_success.has_value()) [[unlikely]] { + kphp::log::warning("DateTime::modify(): failed to parse modifier ({}): {}", modifier.c_str(), expected_success.error()); + return {}; + } + return self; +} + +class_instance f$DateTime$$setDate(const class_instance& self, int64_t year, int64_t month, int64_t day) noexcept { + kphp::timelib::set_date(*self->time, year, month, day); + return self; +} diff --git a/runtime-light/stdlib/time/datetimeimmutable.cpp b/runtime-light/stdlib/time/datetimeimmutable.cpp index 60b7fe3f9b..b753e3f316 100644 --- a/runtime-light/stdlib/time/datetimeimmutable.cpp +++ b/runtime-light/stdlib/time/datetimeimmutable.cpp @@ -109,7 +109,7 @@ class_instance f$DateTimeImmutable$$modify(const class_inst class_instance f$DateTimeImmutable$$setTimestamp(const class_instance& self, int64_t timestamp) noexcept { auto new_date{clone_immutable(self)}; - kphp::timelib::date_timestamp_set(*new_date->time, timestamp); + kphp::timelib::set_timestamp(*new_date->time, timestamp); return new_date; } @@ -117,16 +117,16 @@ class_instance f$DateTimeImmutable$$diff(const class_instance& target_object, bool absolute) noexcept { class_instance interval; interval.alloc(); - interval->rel_time = kphp::timelib::date_diff(*self->time, *target_object.get()->time, absolute); + interval->rel_time = kphp::timelib::diff(*self->time, *target_object.get()->time, absolute); return interval; } string f$DateTimeImmutable$$format(const class_instance& self, const string& format) noexcept { string str; - kphp::timelib::date_format_to_localtime(std::back_inserter(str), {format.c_str(), format.size()}, *self->time); + kphp::timelib::format_to(std::back_inserter(str), {format.c_str(), format.size()}, *self->time); return str; } int64_t f$DateTimeImmutable$$getTimestamp(const class_instance& self) noexcept { - return kphp::timelib::date_timestamp_get(*(self->time)); + return kphp::timelib::get_timestamp(*(self->time)); } diff --git a/runtime-light/stdlib/time/timelib-functions.cpp b/runtime-light/stdlib/time/timelib-functions.cpp index 424f8ceb80..888cd7d309 100644 --- a/runtime-light/stdlib/time/timelib-functions.cpp +++ b/runtime-light/stdlib/time/timelib-functions.cpp @@ -115,7 +115,7 @@ time_t clone(timelib_time& t) noexcept { return time_t{(kphp::memory::libc_alloc_guard{}, timelib_time_clone(std::addressof(t)))}; } -rel_time_t date_diff(timelib_time& time1, timelib_time& time2, bool absolute) noexcept { +rel_time_t diff(timelib_time& time1, timelib_time& time2, bool absolute) noexcept { timelib_update_ts(std::addressof(time1), nullptr); timelib_update_ts(std::addressof(time2), nullptr); @@ -142,18 +142,18 @@ std::string_view date_short_day_name(timelib_sll y, timelib_sll m, timelib_sll d return DAY_SHORT_NAMES[day_of_week]; } -int64_t date_timestamp_get(timelib_time& t) noexcept { +int64_t get_timestamp(timelib_time& t) noexcept { timelib_update_ts(std::addressof(t), nullptr); int error{}; // it's intentionally declared as 'int' since timelib_date_to_int accepts 'int' - timelib_long timestamp{timelib_date_to_int(std::addressof(t), &error)}; + timelib_long timestamp{timelib_date_to_int(std::addressof(t), std::addressof(error))}; // the 'error' should be always 0 on x64 platform log::assertion(error == 0); return timestamp; } -void date_timestamp_set(timelib_time& t, int64_t timestamp) noexcept { +void set_timestamp(timelib_time& t, int64_t timestamp) noexcept { kphp::memory::libc_alloc_guard{}, timelib_unixtime2local(std::addressof(t), static_cast(timestamp)); timelib_update_ts(std::addressof(t), nullptr); t.us = 0; @@ -242,10 +242,6 @@ int64_t gmmktime(std::optional hou, std::optional min, std::op return now->sse; } -bool is_leap_year(int32_t year) noexcept { - return (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0); -} - std::optional mktime(std::optional hou, std::optional min, std::optional sec, std::optional mon, std::optional day, std::optional yea) noexcept { auto* now{(kphp::memory::libc_alloc_guard{}, timelib_time_ctor())}; @@ -363,6 +359,13 @@ time_t now(timelib_tzinfo* tzi) noexcept { return res; } +void set_date(timelib_time& t, int64_t y, int64_t m, int64_t d) noexcept { + t.y = y; + t.m = m; + t.d = d; + timelib_update_ts(std::addressof(t), nullptr); +} + std::optional strtotime(std::string_view timezone, std::string_view datetime, int64_t timestamp) noexcept { if (datetime.empty()) [[unlikely]] { kphp::log::warning("datetime can't be empty"); diff --git a/runtime-light/stdlib/time/timelib-functions.h b/runtime-light/stdlib/time/timelib-functions.h index f8611bde37..c1c8f24cc2 100644 --- a/runtime-light/stdlib/time/timelib-functions.h +++ b/runtime-light/stdlib/time/timelib-functions.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -59,15 +60,15 @@ time_t add(timelib_time& t, timelib_rel_time& interval) noexcept; rel_time_t clone(timelib_rel_time& rt) noexcept; time_t clone(timelib_time& t) noexcept; -rel_time_t date_diff(timelib_time& time1, timelib_time& time2, bool absolute) noexcept; +rel_time_t diff(timelib_time& time1, timelib_time& time2, bool absolute) noexcept; std::string_view date_full_day_name(timelib_sll y, timelib_sll m, timelib_sll d) noexcept; std::string_view date_short_day_name(timelib_sll y, timelib_sll m, timelib_sll d) noexcept; -int64_t date_timestamp_get(timelib_time& t) noexcept; +int64_t get_timestamp(timelib_time& t) noexcept; -void date_timestamp_set(timelib_time& t, int64_t timestamp) noexcept; +void set_timestamp(timelib_time& t, int64_t timestamp) noexcept; std::string_view english_suffix(timelib_sll number) noexcept; @@ -89,8 +90,6 @@ timelib_tzinfo* get_timezone_info(const char* timezone, const timelib_tzdb* tzdb int64_t gmmktime(std::optional hou, std::optional min, std::optional sec, std::optional mon, std::optional day, std::optional yea) noexcept; -bool is_leap_year(int32_t year) noexcept; - std::optional mktime(std::optional hou, std::optional min, std::optional sec, std::optional mon, std::optional day, std::optional yea) noexcept; @@ -98,12 +97,14 @@ std::expected modify(timelib_time& t, std::string_view time_t now(timelib_tzinfo* tzi) noexcept; +void set_date(timelib_time& t, int64_t y, int64_t m, int64_t d) noexcept; + std::optional strtotime(std::string_view timezone, std::string_view datetime, int64_t timestamp) noexcept; bool valid_date(int64_t year, int64_t month, int64_t day) noexcept; template -OutputIt date_format_to(OutputIt out, std::string_view format, timelib_time& t, bool localtime) noexcept { +OutputIt format_to(OutputIt out, std::string_view format, timelib_time& t, bool localtime) noexcept { if (format.empty()) { return out; } @@ -146,14 +147,14 @@ OutputIt date_format_to(OutputIt out, std::string_view format, timelib_time& t, // week case 'W': if (!weekYearSet) { - timelib_isoweek_from_date(t.y, t.m, t.d, &isoweek, &isoyear); + timelib_isoweek_from_date(t.y, t.m, t.d, std::addressof(isoweek), std::addressof(isoyear)); weekYearSet = true; } out = std::format_to(out, "{:02}", isoweek); break; // iso weeknr case 'o': if (!weekYearSet) { - timelib_isoweek_from_date(t.y, t.m, t.d, &isoweek, &isoyear); + timelib_isoweek_from_date(t.y, t.m, t.d, std::addressof(isoweek), std::addressof(isoyear)); weekYearSet = 1; } out = std::format_to(out, "{}", isoyear); @@ -178,7 +179,7 @@ OutputIt date_format_to(OutputIt out, std::string_view format, timelib_time& t, // year case 'L': - out = std::format_to(out, "{}", is_leap_year(t.y)); + out = std::format_to(out, "{:d}", std::chrono::year(t.y).is_leap()); break; case 'y': out = std::format_to(out, "{:02}", t.y % 100); @@ -295,12 +296,12 @@ OutputIt date_format_to(OutputIt out, std::string_view format, timelib_time& t, } template -OutputIt date_format_to_localtime(OutputIt out, std::string_view format, timelib_time& t) noexcept { - return date_format_to(out, format, t, t.is_localtime); +OutputIt format_to(OutputIt out, std::string_view format, timelib_time& t) noexcept { + return timelib::format_to(out, format, t, t.is_localtime); } template -OutputIt date_interval_format_to(OutputIt out, std::string_view format, timelib_rel_time& t) noexcept { +OutputIt format_to(OutputIt out, std::string_view format, timelib_rel_time& t) noexcept { if (format.empty()) { return out; } From d9da6756eac490eaad8b57037df4ec01980c405e Mon Sep 17 00:00:00 2001 From: Karim Shamazov Date: Thu, 11 Dec 2025 02:20:41 +0300 Subject: [PATCH 06/21] datetime methods --- .../kphp-light/stdlib/time-functions.txt | 20 ++++++------- runtime-light/stdlib/time/datetime.cpp | 29 +++++++++++++++++++ .../stdlib/time/timelib-functions.cpp | 8 +++++ runtime-light/stdlib/time/timelib-functions.h | 2 ++ 4 files changed, 49 insertions(+), 10 deletions(-) diff --git a/builtin-functions/kphp-light/stdlib/time-functions.txt b/builtin-functions/kphp-light/stdlib/time-functions.txt index 47401950f5..013a277286 100644 --- a/builtin-functions/kphp-light/stdlib/time-functions.txt +++ b/builtin-functions/kphp-light/stdlib/time-functions.txt @@ -111,38 +111,38 @@ class DateTimeImmutable implements DateTimeInterface { class DateTime implements DateTimeInterface { /** @kphp-extern-func-info can_throw */ public function __construct(string $datetime = "now", ?DateTimeZone $timezone = null); - /** @kphp-extern-func-info stub generation-required */ + /** @kphp-extern-func-info */ public function add(DateInterval $interval): DateTime; - /** @kphp-extern-func-info stub generation-required */ + /** @kphp-extern-func-info */ public static function createFromFormat(string $format, string $datetime, ?DateTimeZone $timezone = null): ?DateTime; - /** @kphp-extern-func-info stub generation-required */ + /** @kphp-extern-func-info */ public static function createFromImmutable(DateTimeImmutable $object): DateTime; /** @kphp-extern-func-info stub generation-required */ public static function getLastErrors(): array|false; - /** @kphp-extern-func-info stub generation-required */ + /** @kphp-extern-func-info */ public function modify(string $modifier): ?DateTime; - /** @kphp-extern-func-info stub generation-required */ + /** @kphp-extern-func-info */ public function setDate(int $year, int $month, int $day): DateTime; /** @kphp-extern-func-info stub generation-required */ public function setISODate(int $year, int $week, int $dayOfWeek = 1): DateTime; - /** @kphp-extern-func-info stub generation-required */ + /** @kphp-extern-func-info */ public function setTime( int $hour, int $minute, int $second = 0, int $microsecond = 0 ): DateTime; - /** @kphp-extern-func-info stub generation-required */ + /** @kphp-extern-func-info */ public function setTimestamp(int $timestamp): DateTime; /** @kphp-extern-func-info stub generation-required */ public function sub(DateInterval $interval): DateTime; - /** @kphp-extern-func-info stub generation-required */ + /** @kphp-extern-func-info */ public function diff(DateTimeInterface $targetObject, bool $absolute = false): DateInterval; - /** @kphp-extern-func-info stub generation-required */ + /** @kphp-extern-func-info */ public function format(string $format): string; /** @kphp-extern-func-info stub generation-required */ public function getOffset(): int; - /** @kphp-extern-func-info stub generation-required */ + /** @kphp-extern-func-info */ public function getTimestamp(): int; } diff --git a/runtime-light/stdlib/time/datetime.cpp b/runtime-light/stdlib/time/datetime.cpp index b1594952db..7e352f9837 100644 --- a/runtime-light/stdlib/time/datetime.cpp +++ b/runtime-light/stdlib/time/datetime.cpp @@ -81,3 +81,32 @@ class_instance f$DateTime$$setDate(const class_instance& kphp::timelib::set_date(*self->time, year, month, day); return self; } + +class_instance f$DateTime$$setTime(const class_instance& self, int64_t hour, int64_t minute, int64_t second, + int64_t microsecond) noexcept { + kphp::timelib::set_time(*self->time, hour, minute, second, microsecond); + return self; +} + +class_instance f$DateTime$$setTimestamp(const class_instance& self, int64_t timestamp) noexcept { + kphp::timelib::set_timestamp(*self->time, timestamp); + return self; +} + +class_instance f$DateTime$$diff(const class_instance& self, const class_instance& target_object, + bool absolute) noexcept { + class_instance interval; + interval.alloc(); + interval->rel_time = kphp::timelib::diff(*self->time, *target_object.get()->time, absolute); + return interval; +} + +string f$DateTime$$format(const class_instance& self, const string& format) noexcept { + string str; + kphp::timelib::format_to(std::back_inserter(str), {format.c_str(), format.size()}, *self->time); + return str; +} + +int64_t f$DateTime$$getTimestamp(const class_instance& self) noexcept { + return kphp::timelib::get_timestamp(*self->time); +} diff --git a/runtime-light/stdlib/time/timelib-functions.cpp b/runtime-light/stdlib/time/timelib-functions.cpp index 888cd7d309..f79fb12581 100644 --- a/runtime-light/stdlib/time/timelib-functions.cpp +++ b/runtime-light/stdlib/time/timelib-functions.cpp @@ -366,6 +366,14 @@ void set_date(timelib_time& t, int64_t y, int64_t m, int64_t d) noexcept { timelib_update_ts(std::addressof(t), nullptr); } +void set_time(timelib_time& t, int64_t h, int64_t i, int64_t s, int64_t ms) noexcept { + t.h = h; + t.i = i; + t.s = s; + t.us = ms; + timelib_update_ts(std::addressof(t), nullptr); +} + std::optional strtotime(std::string_view timezone, std::string_view datetime, int64_t timestamp) noexcept { if (datetime.empty()) [[unlikely]] { kphp::log::warning("datetime can't be empty"); diff --git a/runtime-light/stdlib/time/timelib-functions.h b/runtime-light/stdlib/time/timelib-functions.h index c1c8f24cc2..dfa3418309 100644 --- a/runtime-light/stdlib/time/timelib-functions.h +++ b/runtime-light/stdlib/time/timelib-functions.h @@ -99,6 +99,8 @@ time_t now(timelib_tzinfo* tzi) noexcept; void set_date(timelib_time& t, int64_t y, int64_t m, int64_t d) noexcept; +void set_time(timelib_time& t, int64_t h, int64_t i, int64_t s, int64_t ms) noexcept; + std::optional strtotime(std::string_view timezone, std::string_view datetime, int64_t timestamp) noexcept; bool valid_date(int64_t year, int64_t month, int64_t day) noexcept; From 9825b2be1fc8367ede4de0cd1786395f63644119 Mon Sep 17 00:00:00 2001 From: Karim Shamazov Date: Thu, 11 Dec 2025 12:59:49 +0300 Subject: [PATCH 07/21] kphp::memory::libc_alloc_guard --- .../stdlib/time/timelib-functions.cpp | 20 ++++++++++--------- runtime-light/stdlib/time/timelib-functions.h | 6 +++--- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/runtime-light/stdlib/time/timelib-functions.cpp b/runtime-light/stdlib/time/timelib-functions.cpp index f79fb12581..64652cb672 100644 --- a/runtime-light/stdlib/time/timelib-functions.cpp +++ b/runtime-light/stdlib/time/timelib-functions.cpp @@ -68,7 +68,7 @@ time_offset_t construct_time_offset(timelib_time& t) noexcept { std::format_to_n(offset->abbr, 9, "GMT{}{:02}{:02}", (offset->offset < 0) ? '-' : '+', hours_offset, std::abs((offset->offset % 3600) / 60)); return offset; } - return time_offset_t{timelib_get_time_zone_info(t.sse, t.tz_info)}; + return time_offset_t{(kphp::memory::libc_alloc_guard{}, timelib_get_time_zone_info(t.sse, t.tz_info))}; } std::expected construct_interval(std::string_view format) noexcept { @@ -116,8 +116,8 @@ time_t clone(timelib_time& t) noexcept { } rel_time_t diff(timelib_time& time1, timelib_time& time2, bool absolute) noexcept { - timelib_update_ts(std::addressof(time1), nullptr); - timelib_update_ts(std::addressof(time2), nullptr); + kphp::memory::libc_alloc_guard{}, timelib_update_ts(std::addressof(time1), nullptr); + kphp::memory::libc_alloc_guard{}, timelib_update_ts(std::addressof(time2), nullptr); rel_time_t diff{(kphp::memory::libc_alloc_guard{}, timelib_diff(std::addressof(time1), std::addressof(time2)))}; if (absolute) { @@ -143,7 +143,7 @@ std::string_view date_short_day_name(timelib_sll y, timelib_sll m, timelib_sll d } int64_t get_timestamp(timelib_time& t) noexcept { - timelib_update_ts(std::addressof(t), nullptr); + kphp::memory::libc_alloc_guard{}, timelib_update_ts(std::addressof(t), nullptr); int error{}; // it's intentionally declared as 'int' since timelib_date_to_int accepts 'int' timelib_long timestamp{timelib_date_to_int(std::addressof(t), std::addressof(error))}; @@ -155,7 +155,7 @@ int64_t get_timestamp(timelib_time& t) noexcept { void set_timestamp(timelib_time& t, int64_t timestamp) noexcept { kphp::memory::libc_alloc_guard{}, timelib_unixtime2local(std::addressof(t), static_cast(timestamp)); - timelib_update_ts(std::addressof(t), nullptr); + kphp::memory::libc_alloc_guard{}, timelib_update_ts(std::addressof(t), nullptr); t.us = 0; } @@ -335,7 +335,7 @@ std::expected modify(timelib_time& t, std::string_view t.us = tmp_time->us; } - timelib_update_ts(std::addressof(t), nullptr); + kphp::memory::libc_alloc_guard{}, timelib_update_ts(std::addressof(t), nullptr); timelib_update_from_sse(std::addressof(t)); t.have_relative = 0; std::memset(std::addressof(t.relative), 0, sizeof(t.relative)); @@ -353,7 +353,7 @@ time_t now(timelib_tzinfo* tzi) noexcept { const auto sec{chrono::duration_cast(time_since_epoch).count()}; const auto usec{chrono::duration_cast(time_since_epoch % chrono::seconds{1}).count()}; - timelib_unixtime2local(res.get(), static_cast(sec)); + kphp::memory::libc_alloc_guard{}, timelib_unixtime2local(res.get(), static_cast(sec)); res->us = usec; return res; @@ -363,7 +363,7 @@ void set_date(timelib_time& t, int64_t y, int64_t m, int64_t d) noexcept { t.y = y; t.m = m; t.d = d; - timelib_update_ts(std::addressof(t), nullptr); + kphp::memory::libc_alloc_guard{}, timelib_update_ts(std::addressof(t), nullptr); } void set_time(timelib_time& t, int64_t h, int64_t i, int64_t s, int64_t ms) noexcept { @@ -371,7 +371,7 @@ void set_time(timelib_time& t, int64_t h, int64_t i, int64_t s, int64_t ms) noex t.i = i; t.s = s; t.us = ms; - timelib_update_ts(std::addressof(t), nullptr); + kphp::memory::libc_alloc_guard{}, timelib_update_ts(std::addressof(t), nullptr); } std::optional strtotime(std::string_view timezone, std::string_view datetime, int64_t timestamp) noexcept { @@ -387,6 +387,8 @@ std::optional strtotime(std::string_view timezone, std::string_view dat return {}; } + kphp::memory::libc_alloc_guard _{}; + timelib_time* now{timelib_time_ctor()}; const vk::final_action now_deleter{[now] noexcept { timelib_time_dtor(now); }}; now->tz_info = tzinfo; diff --git a/runtime-light/stdlib/time/timelib-functions.h b/runtime-light/stdlib/time/timelib-functions.h index dfa3418309..cc9023a6c0 100644 --- a/runtime-light/stdlib/time/timelib-functions.h +++ b/runtime-light/stdlib/time/timelib-functions.h @@ -404,9 +404,9 @@ void fill_holes_with_now(timelib_time& time, timelib_tzinfo* tzi) noexcept { options |= TIMELIB_OVERRIDE_TIME; } - timelib_fill_holes(std::addressof(time), now_time.get(), options); - timelib_update_ts(std::addressof(time), now_time->tz_info); - timelib_update_from_sse(std::addressof(time)); + kphp::memory::libc_alloc_guard{}, timelib_fill_holes(std::addressof(time), now_time.get(), options); + kphp::memory::libc_alloc_guard{}, timelib_update_ts(std::addressof(time), now_time->tz_info); + kphp::memory::libc_alloc_guard{}, timelib_update_from_sse(std::addressof(time)); time.have_relative = 0; } From 7161d44ffb096863f74fe4478708e37fbe46b41c Mon Sep 17 00:00:00 2001 From: Karim Shamazov Date: Thu, 11 Dec 2025 13:01:45 +0300 Subject: [PATCH 08/21] some tests --- tests/phpt/datetime/01_format.php | 2 +- tests/phpt/datetime/03_set_timestamp.php | 2 +- tests/phpt/datetime/11_mutability_conversions.php | 2 +- tests/phpt/datetime/18_diff_date_interval.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/phpt/datetime/01_format.php b/tests/phpt/datetime/01_format.php index bed93ca5e4..d665b6e248 100644 --- a/tests/phpt/datetime/01_format.php +++ b/tests/phpt/datetime/01_format.php @@ -1,4 +1,4 @@ -@ok k2_skip +@ok Date: Thu, 11 Dec 2025 15:12:53 +0300 Subject: [PATCH 09/21] fixes --- .../kphp-light/stdlib/time-functions.txt | 12 ------------ runtime-light/stdlib/time/datetimeimmutable.cpp | 14 ++++++++++++++ runtime-light/stdlib/time/datetimeimmutable.h | 6 ++++++ runtime-light/stdlib/time/timelib-functions.cpp | 2 +- tests/phpt/datetime/04_modify.php | 2 +- tests/phpt/datetime/07_set_time.php | 2 +- 6 files changed, 23 insertions(+), 15 deletions(-) diff --git a/builtin-functions/kphp-light/stdlib/time-functions.txt b/builtin-functions/kphp-light/stdlib/time-functions.txt index 013a277286..520cfa9a01 100644 --- a/builtin-functions/kphp-light/stdlib/time-functions.txt +++ b/builtin-functions/kphp-light/stdlib/time-functions.txt @@ -87,11 +87,9 @@ class DateTimeImmutable implements DateTimeInterface { /** @kphp-extern-func-info stub generation-required */ public static function getLastErrors(): array|false; public function modify(string $modifier): ?DateTimeImmutable; - /** @kphp-extern-func-info stub generation-required */ public function setDate(int $year, int $month, int $day): DateTimeImmutable; /** @kphp-extern-func-info stub generation-required */ public function setISODate(int $year, int $week, int $dayOfWeek = 1): DateTimeImmutable; - /** @kphp-extern-func-info stub generation-required */ public function setTime( int $hour, int $minute, @@ -111,38 +109,28 @@ class DateTimeImmutable implements DateTimeInterface { class DateTime implements DateTimeInterface { /** @kphp-extern-func-info can_throw */ public function __construct(string $datetime = "now", ?DateTimeZone $timezone = null); - /** @kphp-extern-func-info */ public function add(DateInterval $interval): DateTime; - /** @kphp-extern-func-info */ public static function createFromFormat(string $format, string $datetime, ?DateTimeZone $timezone = null): ?DateTime; - /** @kphp-extern-func-info */ public static function createFromImmutable(DateTimeImmutable $object): DateTime; /** @kphp-extern-func-info stub generation-required */ public static function getLastErrors(): array|false; - /** @kphp-extern-func-info */ public function modify(string $modifier): ?DateTime; - /** @kphp-extern-func-info */ public function setDate(int $year, int $month, int $day): DateTime; /** @kphp-extern-func-info stub generation-required */ public function setISODate(int $year, int $week, int $dayOfWeek = 1): DateTime; - /** @kphp-extern-func-info */ public function setTime( int $hour, int $minute, int $second = 0, int $microsecond = 0 ): DateTime; - /** @kphp-extern-func-info */ public function setTimestamp(int $timestamp): DateTime; /** @kphp-extern-func-info stub generation-required */ public function sub(DateInterval $interval): DateTime; - /** @kphp-extern-func-info */ public function diff(DateTimeInterface $targetObject, bool $absolute = false): DateInterval; - /** @kphp-extern-func-info */ public function format(string $format): string; /** @kphp-extern-func-info stub generation-required */ public function getOffset(): int; - /** @kphp-extern-func-info */ public function getTimestamp(): int; } diff --git a/runtime-light/stdlib/time/datetimeimmutable.cpp b/runtime-light/stdlib/time/datetimeimmutable.cpp index b753e3f316..d901692689 100644 --- a/runtime-light/stdlib/time/datetimeimmutable.cpp +++ b/runtime-light/stdlib/time/datetimeimmutable.cpp @@ -107,6 +107,20 @@ class_instance f$DateTimeImmutable$$modify(const class_inst return new_date; } +class_instance f$DateTimeImmutable$$setDate(const class_instance& self, int64_t year, int64_t month, + int64_t day) noexcept { + auto new_date = clone_immutable(self); + kphp::timelib::set_date(*new_date->time, year, month, day); + return new_date; +} + +class_instance f$DateTimeImmutable$$setTime(const class_instance& self, int64_t hour, int64_t minute, int64_t second, + int64_t microsecond) noexcept { + auto new_date = clone_immutable(self); + kphp::timelib::set_time(*new_date->time, hour, minute, second, microsecond); + return new_date; +} + class_instance f$DateTimeImmutable$$setTimestamp(const class_instance& self, int64_t timestamp) noexcept { auto new_date{clone_immutable(self)}; kphp::timelib::set_timestamp(*new_date->time, timestamp); diff --git a/runtime-light/stdlib/time/datetimeimmutable.h b/runtime-light/stdlib/time/datetimeimmutable.h index 0ef736016d..acb534997e 100644 --- a/runtime-light/stdlib/time/datetimeimmutable.h +++ b/runtime-light/stdlib/time/datetimeimmutable.h @@ -44,6 +44,12 @@ class_instance f$DateTimeImmutable$$createFromMutable(const class_instance f$DateTimeImmutable$$modify(const class_instance& self, const string& modifier) noexcept; +class_instance f$DateTimeImmutable$$setDate(const class_instance& self, int64_t year, int64_t month, +int64_t day) noexcept; + +class_instance f$DateTimeImmutable$$setTime(const class_instance& self, int64_t hour, int64_t minute, + int64_t second = 0, int64_t microsecond = 0) noexcept; + class_instance f$DateTimeImmutable$$setTimestamp(const class_instance& self, int64_t timestamp) noexcept; class_instance f$DateTimeImmutable$$diff(const class_instance& self, diff --git a/runtime-light/stdlib/time/timelib-functions.cpp b/runtime-light/stdlib/time/timelib-functions.cpp index 64652cb672..c3cc13e019 100644 --- a/runtime-light/stdlib/time/timelib-functions.cpp +++ b/runtime-light/stdlib/time/timelib-functions.cpp @@ -336,7 +336,7 @@ std::expected modify(timelib_time& t, std::string_view } kphp::memory::libc_alloc_guard{}, timelib_update_ts(std::addressof(t), nullptr); - timelib_update_from_sse(std::addressof(t)); + kphp::memory::libc_alloc_guard{}, timelib_update_from_sse(std::addressof(t)); t.have_relative = 0; std::memset(std::addressof(t.relative), 0, sizeof(t.relative)); return {}; diff --git a/tests/phpt/datetime/04_modify.php b/tests/phpt/datetime/04_modify.php index 9b6d53348a..2672ea692b 100644 --- a/tests/phpt/datetime/04_modify.php +++ b/tests/phpt/datetime/04_modify.php @@ -1,4 +1,4 @@ -@ok k2_skip +@ok Date: Thu, 11 Dec 2025 15:15:44 +0300 Subject: [PATCH 10/21] format --- runtime-light/stdlib/time/datetimeimmutable.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime-light/stdlib/time/datetimeimmutable.h b/runtime-light/stdlib/time/datetimeimmutable.h index acb534997e..ad7ae75b55 100644 --- a/runtime-light/stdlib/time/datetimeimmutable.h +++ b/runtime-light/stdlib/time/datetimeimmutable.h @@ -45,7 +45,7 @@ class_instance f$DateTimeImmutable$$createFromMutable(const class_instance f$DateTimeImmutable$$modify(const class_instance& self, const string& modifier) noexcept; class_instance f$DateTimeImmutable$$setDate(const class_instance& self, int64_t year, int64_t month, -int64_t day) noexcept; + int64_t day) noexcept; class_instance f$DateTimeImmutable$$setTime(const class_instance& self, int64_t hour, int64_t minute, int64_t second = 0, int64_t microsecond = 0) noexcept; From 40b60bdc0c9b1eb6cdf74fb0257f85e59d161f72 Mon Sep 17 00:00:00 2001 From: Karim Shamazov Date: Thu, 11 Dec 2025 16:15:24 +0300 Subject: [PATCH 11/21] get_last_errors --- .../kphp-light/stdlib/time-functions.txt | 1 - runtime-light/stdlib/time/datetime.cpp | 11 ++++++ runtime-light/stdlib/time/datetime.h | 2 ++ .../stdlib/time/datetimeimmutable.cpp | 11 ++++++ runtime-light/stdlib/time/datetimeimmutable.h | 2 ++ runtime-light/stdlib/time/time-state.h | 34 +++++++++++++++++++ 6 files changed, 60 insertions(+), 1 deletion(-) diff --git a/builtin-functions/kphp-light/stdlib/time-functions.txt b/builtin-functions/kphp-light/stdlib/time-functions.txt index 520cfa9a01..d9f8a869b1 100644 --- a/builtin-functions/kphp-light/stdlib/time-functions.txt +++ b/builtin-functions/kphp-light/stdlib/time-functions.txt @@ -112,7 +112,6 @@ class DateTime implements DateTimeInterface { public function add(DateInterval $interval): DateTime; public static function createFromFormat(string $format, string $datetime, ?DateTimeZone $timezone = null): ?DateTime; public static function createFromImmutable(DateTimeImmutable $object): DateTime; - /** @kphp-extern-func-info stub generation-required */ public static function getLastErrors(): array|false; public function modify(string $modifier): ?DateTime; public function setDate(int $year, int $month, int $day): DateTime; diff --git a/runtime-light/stdlib/time/datetime.cpp b/runtime-light/stdlib/time/datetime.cpp index 7e352f9837..a9ed2c0b68 100644 --- a/runtime-light/stdlib/time/datetime.cpp +++ b/runtime-light/stdlib/time/datetime.cpp @@ -14,8 +14,11 @@ class_instance f$DateTime$$__construct(const class_instance(err_msg)); + return {}; } + TimeInstanceState::get().update_last_errors(nullptr); kphp::timelib::time_t time{std::move(*expected_time)}; timelib_tzinfo* tzi{!timezone.is_null() ? timezone->tzi : nullptr}; @@ -41,8 +44,10 @@ class_instance f$DateTime$$add(const class_instance& sel class_instance f$DateTime$$createFromFormat(const string& format, const string& datetime, const class_instance& timezone) noexcept { auto expected_time{kphp::timelib::construct_time({datetime.c_str(), datetime.size()}, format.c_str())}; if (!expected_time.has_value()) [[unlikely]] { + TimeInstanceState::get().update_last_errors(std::move(expected_time.error())); return {}; } + TimeInstanceState::get().update_last_errors(nullptr); auto time{std::move(*expected_time)}; timelib_tzinfo* tzi{!timezone.is_null() ? timezone->tzi : nullptr}; if (tzi == nullptr) { @@ -68,12 +73,18 @@ class_instance f$DateTime$$createFromImmutable(const class_instance< return clone; } +Optional> f$DateTime$$getLastErrors() noexcept { + return TimeInstanceState::get().get_last_errors(); +} + class_instance f$DateTime$$modify(const class_instance& self, const string& modifier) noexcept { auto expected_success{kphp::timelib::modify(*self->time, {modifier.c_str(), modifier.size()})}; if (!expected_success.has_value()) [[unlikely]] { kphp::log::warning("DateTime::modify(): failed to parse modifier ({}): {}", modifier.c_str(), expected_success.error()); + TimeInstanceState::get().update_last_errors(std::move(expected_success.error())); return {}; } + TimeInstanceState::get().update_last_errors(nullptr); return self; } diff --git a/runtime-light/stdlib/time/datetime.h b/runtime-light/stdlib/time/datetime.h index 55c4c10287..647898295a 100644 --- a/runtime-light/stdlib/time/datetime.h +++ b/runtime-light/stdlib/time/datetime.h @@ -39,6 +39,8 @@ class_instance f$DateTime$$createFromFormat(const string& format, co class_instance f$DateTime$$createFromImmutable(const class_instance& object) noexcept; +Optional> f$DateTime$$getLastErrors() noexcept; + class_instance f$DateTime$$modify(const class_instance& self, const string& modifier) noexcept; class_instance f$DateTime$$setDate(const class_instance& self, int64_t year, int64_t month, int64_t day) noexcept; diff --git a/runtime-light/stdlib/time/datetimeimmutable.cpp b/runtime-light/stdlib/time/datetimeimmutable.cpp index d901692689..5c4e968ae3 100644 --- a/runtime-light/stdlib/time/datetimeimmutable.cpp +++ b/runtime-light/stdlib/time/datetimeimmutable.cpp @@ -40,8 +40,11 @@ class_instance f$DateTimeImmutable$$__construct(const class if (!expected_time.has_value()) [[unlikely]] { string err_msg; format_to(std::back_inserter(err_msg), "DateTimeImmutable::__construct(): failed to parse datetime ({}): {}", datetime.c_str(), expected_time.error()); + TimeInstanceState::get().update_last_errors(std::move(expected_time.error())); THROW_EXCEPTION(kphp::exception::make_throwable(err_msg)); + return {}; } + TimeInstanceState::get().update_last_errors(nullptr); kphp::timelib::time_t time{std::move(*expected_time)}; timelib_tzinfo* tzi{!timezone.is_null() ? timezone->tzi : nullptr}; @@ -70,8 +73,10 @@ class_instance f$DateTimeImmutable$$createFromFormat(const const class_instance& timezone) noexcept { auto expected_time{kphp::timelib::construct_time({datetime.c_str(), datetime.size()}, format.c_str())}; if (!expected_time.has_value()) [[unlikely]] { + TimeInstanceState::get().update_last_errors(std::move(expected_time.error())); return {}; } + TimeInstanceState::get().update_last_errors(nullptr); auto time{std::move(*expected_time)}; timelib_tzinfo* tzi{!timezone.is_null() ? timezone->tzi : nullptr}; if (tzi == nullptr) { @@ -97,13 +102,19 @@ class_instance f$DateTimeImmutable$$createFromMutable(const return clone; } +Optional> f$DateTimeImmutable$$getLastErrors() noexcept { + return TimeInstanceState::get().get_last_errors(); +} + class_instance f$DateTimeImmutable$$modify(const class_instance& self, const string& modifier) noexcept { auto new_date{clone_immutable(self)}; auto expected_success{kphp::timelib::modify(*new_date->time, {modifier.c_str(), modifier.size()})}; if (!expected_success.has_value()) [[unlikely]] { kphp::log::warning("DateTimeImmutable::modify(): failed to parse modifier ({}): {}", modifier.c_str(), expected_success.error()); + TimeInstanceState::get().update_last_errors(std::move(expected_success.error())); return {}; } + TimeInstanceState::get().update_last_errors(nullptr); return new_date; } diff --git a/runtime-light/stdlib/time/datetimeimmutable.h b/runtime-light/stdlib/time/datetimeimmutable.h index ad7ae75b55..3901a053e8 100644 --- a/runtime-light/stdlib/time/datetimeimmutable.h +++ b/runtime-light/stdlib/time/datetimeimmutable.h @@ -42,6 +42,8 @@ class_instance f$DateTimeImmutable$$createFromFormat(const class_instance f$DateTimeImmutable$$createFromMutable(const class_instance& object) noexcept; +Optional> f$DateTimeImmutable$$getLastErrors() noexcept; + class_instance f$DateTimeImmutable$$modify(const class_instance& self, const string& modifier) noexcept; class_instance f$DateTimeImmutable$$setDate(const class_instance& self, int64_t year, int64_t month, diff --git a/runtime-light/stdlib/time/time-state.h b/runtime-light/stdlib/time/time-state.h index 0923c8b916..30c9daad96 100644 --- a/runtime-light/stdlib/time/time-state.h +++ b/runtime-light/stdlib/time/time-state.h @@ -9,6 +9,7 @@ #include "runtime-light/k2-platform/k2-api.h" #include "runtime-light/stdlib/diagnostics/logs.h" #include "runtime-light/stdlib/time/timelib-constants.h" +#include "runtime-light/stdlib/time/timelib-functions.h" #include "runtime-light/stdlib/time/timelib-timezone-cache.h" struct TimeInstanceState final : private vk::not_copyable { @@ -21,7 +22,40 @@ struct TimeInstanceState final : private vk::not_copyable { } } + Optional> get_last_errors() const noexcept { + if (last_errors == nullptr) { + return false; + } + + array result; + + array result_warnings; + result_warnings.reserve(last_errors->warning_count, false); + for (size_t i{}; i < last_errors->warning_count; i++) { + result_warnings.set_value(last_errors->warning_messages[i].position, string{last_errors->warning_messages[i].message}); + } + result.set_value(string{"warning_count"}, last_errors->warning_count); + result.set_value(string{"warnings"}, result_warnings); + + array result_errors; + result_errors.reserve(last_errors->error_count, false); + for (size_t i{}; i < last_errors->error_count; i++) { + result_errors.set_value(last_errors->error_messages[i].position, string{last_errors->error_messages[i].message}); + } + result.set_value(string{"error_count"}, last_errors->error_count); + result.set_value(string{"errors"}, result_errors); + + return result; + } + + void update_last_errors(kphp::timelib::error_container_t&& new_errors) noexcept { + last_errors.swap(new_errors); + } + static TimeInstanceState& get() noexcept; + +private: + kphp::timelib::error_container_t last_errors{nullptr}; }; struct TimeImageState final : private vk::not_copyable { From d5bb0032133bb1b06ef7f9d37eee386f47452e1a Mon Sep 17 00:00:00 2001 From: Karim Shamazov Date: Thu, 11 Dec 2025 18:38:10 +0300 Subject: [PATCH 12/21] error messages --- .../kphp-light/stdlib/time-functions.txt | 1 - runtime-light/stdlib/time/dateinterval.cpp | 11 +++--- runtime-light/stdlib/time/datetime.cpp | 30 +++++++------- .../stdlib/time/datetimeimmutable.cpp | 30 +++++++------- .../stdlib/time/timelib-functions.cpp | 39 ++++++++----------- runtime-light/stdlib/time/timelib-functions.h | 8 ++-- tests/python/lib/kphp_run_once.py | 7 +++- 7 files changed, 59 insertions(+), 67 deletions(-) diff --git a/builtin-functions/kphp-light/stdlib/time-functions.txt b/builtin-functions/kphp-light/stdlib/time-functions.txt index d9f8a869b1..e803664bbd 100644 --- a/builtin-functions/kphp-light/stdlib/time-functions.txt +++ b/builtin-functions/kphp-light/stdlib/time-functions.txt @@ -84,7 +84,6 @@ class DateTimeImmutable implements DateTimeInterface { public function add(DateInterval $interval): DateTimeImmutable; public static function createFromFormat(string $format, string $datetime, ?DateTimeZone $timezone = null): ?DateTimeImmutable; public static function createFromMutable(DateTime $object): DateTimeImmutable; - /** @kphp-extern-func-info stub generation-required */ public static function getLastErrors(): array|false; public function modify(string $modifier): ?DateTimeImmutable; public function setDate(int $year, int $month, int $day): DateTimeImmutable; diff --git a/runtime-light/stdlib/time/dateinterval.cpp b/runtime-light/stdlib/time/dateinterval.cpp index aba8b005ef..0aa2c192d6 100644 --- a/runtime-light/stdlib/time/dateinterval.cpp +++ b/runtime-light/stdlib/time/dateinterval.cpp @@ -16,8 +16,8 @@ class_instance f$DateInterval$$__construct(const class_instance& self, const string& duration) noexcept { auto expected_rel_time{kphp::timelib::construct_interval({duration.c_str(), duration.size()})}; if (!expected_rel_time.has_value()) [[unlikely]] { - string err_msg; - format_to(std::back_inserter(err_msg), "DateInterval::__construct(): failed to parse interval ({}): {}", duration.c_str(), expected_rel_time.error()); + string err_msg{"DateInterval::__construct(): "}; + format_to(std::back_inserter(err_msg), expected_rel_time.error(), duration.c_str()); THROW_EXCEPTION(kphp::exception::make_throwable(err_msg)); return {}; } @@ -26,12 +26,11 @@ class_instance f$DateInterval$$__construct(const class_instance< } class_instance f$DateInterval$$createFromDateString(const string& datetime) noexcept { - auto expected_time{kphp::timelib::construct_time({datetime.c_str(), datetime.size()})}; - if (!expected_time.has_value()) [[unlikely]] { - kphp::log::warning("DateInterval::createFromDateString(): failed to parse datetime ({}): {}", datetime.c_str(), expected_time.error()); + auto [time, errors]{kphp::timelib::construct_time({datetime.c_str(), datetime.size()})}; + if (time == nullptr) [[unlikely]] { + kphp::log::warning("DateInterval::createFromDateString(): failed to parse datetime ({}): {}", datetime.c_str(), errors); return {}; } - kphp::timelib::time_t time{std::move(*expected_time)}; class_instance date_interval; date_interval.alloc(); date_interval->rel_time = kphp::timelib::clone(time->relative); diff --git a/runtime-light/stdlib/time/datetime.cpp b/runtime-light/stdlib/time/datetime.cpp index a9ed2c0b68..70b810a443 100644 --- a/runtime-light/stdlib/time/datetime.cpp +++ b/runtime-light/stdlib/time/datetime.cpp @@ -10,17 +10,16 @@ class_instance f$DateTime$$__construct(const class_instance& self, const string& datetime, const class_instance& timezone) noexcept { const auto& str_to_parse{datetime.empty() ? StringLibConstants::get().NOW_STR : datetime}; - auto expected_time{kphp::timelib::construct_time(std::string_view{str_to_parse.c_str(), str_to_parse.size()})}; - if (!expected_time.has_value()) [[unlikely]] { + auto [time, errors]{kphp::timelib::construct_time(std::string_view{str_to_parse.c_str(), str_to_parse.size()})}; + if (time == nullptr) [[unlikely]] { string err_msg; - format_to(std::back_inserter(err_msg), "DateTime::__construct(): failed to parse datetime ({}): {}", datetime.c_str(), expected_time.error()); - TimeInstanceState::get().update_last_errors(std::move(expected_time.error())); + format_to(std::back_inserter(err_msg), "DateTime::__construct(): failed to parse datetime ({}): {}", datetime.c_str(), errors); + TimeInstanceState::get().update_last_errors(std::move(errors)); THROW_EXCEPTION(kphp::exception::make_throwable(err_msg)); return {}; } - TimeInstanceState::get().update_last_errors(nullptr); + TimeInstanceState::get().update_last_errors(std::move(errors)); - kphp::timelib::time_t time{std::move(*expected_time)}; timelib_tzinfo* tzi{!timezone.is_null() ? timezone->tzi : nullptr}; if (tzi == nullptr) { if (time->tz_info != nullptr) { @@ -42,13 +41,12 @@ class_instance f$DateTime$$add(const class_instance& sel } class_instance f$DateTime$$createFromFormat(const string& format, const string& datetime, const class_instance& timezone) noexcept { - auto expected_time{kphp::timelib::construct_time({datetime.c_str(), datetime.size()}, format.c_str())}; - if (!expected_time.has_value()) [[unlikely]] { - TimeInstanceState::get().update_last_errors(std::move(expected_time.error())); + auto [time, errors]{kphp::timelib::construct_time({datetime.c_str(), datetime.size()}, format.c_str())}; + if (time == nullptr) [[unlikely]] { + TimeInstanceState::get().update_last_errors(std::move(errors)); return {}; } - TimeInstanceState::get().update_last_errors(nullptr); - auto time{std::move(*expected_time)}; + TimeInstanceState::get().update_last_errors(std::move(errors)); timelib_tzinfo* tzi{!timezone.is_null() ? timezone->tzi : nullptr}; if (tzi == nullptr) { if (time->tz_info != nullptr) { @@ -78,13 +76,13 @@ Optional> f$DateTime$$getLastErrors() noexcept { } class_instance f$DateTime$$modify(const class_instance& self, const string& modifier) noexcept { - auto expected_success{kphp::timelib::modify(*self->time, {modifier.c_str(), modifier.size()})}; - if (!expected_success.has_value()) [[unlikely]] { - kphp::log::warning("DateTime::modify(): failed to parse modifier ({}): {}", modifier.c_str(), expected_success.error()); - TimeInstanceState::get().update_last_errors(std::move(expected_success.error())); + auto errors{kphp::timelib::modify(*self->time, {modifier.c_str(), modifier.size()})}; + if (errors != nullptr && errors->error_count > 0) [[unlikely]] { + kphp::log::warning("DateTime::modify(): failed to parse modifier ({}): {}", modifier.c_str(), errors); + TimeInstanceState::get().update_last_errors(std::move(errors)); return {}; } - TimeInstanceState::get().update_last_errors(nullptr); + TimeInstanceState::get().update_last_errors(std::move(errors)); return self; } diff --git a/runtime-light/stdlib/time/datetimeimmutable.cpp b/runtime-light/stdlib/time/datetimeimmutable.cpp index 5c4e968ae3..44c2dc0687 100644 --- a/runtime-light/stdlib/time/datetimeimmutable.cpp +++ b/runtime-light/stdlib/time/datetimeimmutable.cpp @@ -36,17 +36,16 @@ class_instance clone_immutable(const class_instance f$DateTimeImmutable$$__construct(const class_instance& self, const string& datetime, const class_instance& timezone) noexcept { const auto& str_to_parse{datetime.empty() ? StringLibConstants::get().NOW_STR : datetime}; - auto expected_time{kphp::timelib::construct_time(std::string_view{str_to_parse.c_str(), str_to_parse.size()})}; - if (!expected_time.has_value()) [[unlikely]] { + auto [time, errors]{kphp::timelib::construct_time(std::string_view{str_to_parse.c_str(), str_to_parse.size()})}; + if (time == nullptr) [[unlikely]] { string err_msg; - format_to(std::back_inserter(err_msg), "DateTimeImmutable::__construct(): failed to parse datetime ({}): {}", datetime.c_str(), expected_time.error()); - TimeInstanceState::get().update_last_errors(std::move(expected_time.error())); + format_to(std::back_inserter(err_msg), "DateTimeImmutable::__construct(): failed to parse datetime ({}): {}", datetime.c_str(), errors); + TimeInstanceState::get().update_last_errors(std::move(errors)); THROW_EXCEPTION(kphp::exception::make_throwable(err_msg)); return {}; } - TimeInstanceState::get().update_last_errors(nullptr); + TimeInstanceState::get().update_last_errors(std::move(errors)); - kphp::timelib::time_t time{std::move(*expected_time)}; timelib_tzinfo* tzi{!timezone.is_null() ? timezone->tzi : nullptr}; if (tzi == nullptr) { if (time->tz_info != nullptr) { @@ -71,13 +70,12 @@ class_instance f$DateTimeImmutable$$add(const class_instanc class_instance f$DateTimeImmutable$$createFromFormat(const string& format, const string& datetime, const class_instance& timezone) noexcept { - auto expected_time{kphp::timelib::construct_time({datetime.c_str(), datetime.size()}, format.c_str())}; - if (!expected_time.has_value()) [[unlikely]] { - TimeInstanceState::get().update_last_errors(std::move(expected_time.error())); + auto [time, errors]{kphp::timelib::construct_time({datetime.c_str(), datetime.size()}, format.c_str())}; + if (time == nullptr) [[unlikely]] { + TimeInstanceState::get().update_last_errors(std::move(errors)); return {}; } - TimeInstanceState::get().update_last_errors(nullptr); - auto time{std::move(*expected_time)}; + TimeInstanceState::get().update_last_errors(std::move(errors)); timelib_tzinfo* tzi{!timezone.is_null() ? timezone->tzi : nullptr}; if (tzi == nullptr) { if (time->tz_info != nullptr) { @@ -108,13 +106,13 @@ Optional> f$DateTimeImmutable$$getLastErrors() noexcept { class_instance f$DateTimeImmutable$$modify(const class_instance& self, const string& modifier) noexcept { auto new_date{clone_immutable(self)}; - auto expected_success{kphp::timelib::modify(*new_date->time, {modifier.c_str(), modifier.size()})}; - if (!expected_success.has_value()) [[unlikely]] { - kphp::log::warning("DateTimeImmutable::modify(): failed to parse modifier ({}): {}", modifier.c_str(), expected_success.error()); - TimeInstanceState::get().update_last_errors(std::move(expected_success.error())); + auto errors{kphp::timelib::modify(*new_date->time, {modifier.c_str(), modifier.size()})}; + if (errors != nullptr && errors->error_count > 0) [[unlikely]] { + kphp::log::warning("DateTimeImmutable::modify(): failed to parse modifier ({}): {}", modifier.c_str(), errors); + TimeInstanceState::get().update_last_errors(std::move(errors)); return {}; } - TimeInstanceState::get().update_last_errors(nullptr); + TimeInstanceState::get().update_last_errors(std::move(errors)); return new_date; } diff --git a/runtime-light/stdlib/time/timelib-functions.cpp b/runtime-light/stdlib/time/timelib-functions.cpp index c3cc13e019..456f22e37e 100644 --- a/runtime-light/stdlib/time/timelib-functions.cpp +++ b/runtime-light/stdlib/time/timelib-functions.cpp @@ -23,29 +23,27 @@ namespace kphp::timelib { -std::expected construct_time(std::string_view time_sv) noexcept { +std::pair construct_time(std::string_view time_sv) noexcept { timelib_error_container* errors{}; time_t time{(kphp::memory::libc_alloc_guard{}, timelib_strtotime(time_sv.data(), time_sv.size(), std::addressof(errors), timelib_builtin_db(), kphp::timelib::get_timezone_info))}; if (errors->error_count != 0) [[unlikely]] { - return std::unexpected{error_container_t{errors}}; + return {nullptr, error_container_t{errors}}; } - error_container_t{errors}; - return time; + return {std::move(time), error_container_t{errors}}; } -std::expected construct_time(std::string_view time_sv, const char* format) noexcept { +std::pair construct_time(std::string_view time_sv, const char* format) noexcept { timelib_error_container* err{nullptr}; time_t t{(kphp::memory::libc_alloc_guard{}, timelib_parse_from_format(format, time_sv.data(), time_sv.size(), std::addressof(err), timelib_builtin_db(), kphp::timelib::get_timezone_info))}; if (err && err->error_count) { - return std::unexpected{error_container_t{err}}; + return {nullptr, error_container_t{err}}; } - error_container_t{err}; - return t; + return {std::move(t), error_container_t{err}}; } time_offset_t construct_time_offset(timelib_time& t) noexcept { @@ -71,7 +69,7 @@ time_offset_t construct_time_offset(timelib_time& t) noexcept { return time_offset_t{(kphp::memory::libc_alloc_guard{}, timelib_get_time_zone_info(t.sse, t.tz_info))}; } -std::expected construct_interval(std::string_view format) noexcept { +std::expected> construct_interval(std::string_view format) noexcept { timelib_time* b{nullptr}; timelib_time* e{nullptr}; vk::final_action e_deleter{[e]() { kphp::memory::libc_alloc_guard{}, free(e); }}; @@ -85,7 +83,8 @@ std::expected construct_interval(std::string_view if (errors->error_count > 0) { rel_time_t{p}; - return std::unexpected{error_container_t{errors}}; + error_container_t{errors}; + return std::unexpected{std::format_string{"Unknown or bad format ({})"}}; } error_container_t{errors}; @@ -99,7 +98,7 @@ std::expected construct_interval(std::string_view return kphp::memory::libc_alloc_guard{}, rel_time_t{timelib_diff(b, e)}; } - return std::unexpected{error_container_t{nullptr}}; + return std::unexpected{std::format_string{"Failed to parse interval ({})"}}; } time_t add(timelib_time& t, timelib_rel_time& interval) noexcept { @@ -293,15 +292,13 @@ std::optional mktime(std::optional hou, std::optional return now->sse; } -std::expected modify(timelib_time& t, std::string_view modifier) noexcept { - auto expected_tmp_time{construct_time(modifier)}; +error_container_t modify(timelib_time& t, std::string_view modifier) noexcept { + auto [tmp_time, errors]{construct_time(modifier)}; - if (!expected_tmp_time.has_value()) [[unlikely]] { - return std::unexpected{std::move(expected_tmp_time.error())}; + if (tmp_time == nullptr) [[unlikely]] { + return std::move(errors); } - time_t tmp_time{std::move(*expected_tmp_time)}; - std::memcpy(std::addressof(t.relative), std::addressof(tmp_time->relative), sizeof(timelib_rel_time)); t.have_relative = tmp_time->have_relative; t.sse_uptodate = 0; @@ -339,7 +336,7 @@ std::expected modify(timelib_time& t, std::string_view kphp::memory::libc_alloc_guard{}, timelib_update_from_sse(std::addressof(t)); t.have_relative = 0; std::memset(std::addressof(t.relative), 0, sizeof(t.relative)); - return {}; + return std::move(errors);; } time_t now(timelib_tzinfo* tzi) noexcept { @@ -395,13 +392,11 @@ std::optional strtotime(std::string_view timezone, std::string_view dat now->zone_type = TIMELIB_ZONETYPE_ID; timelib_unixtime2local(now, timestamp); - auto expected_time{construct_time(datetime)}; - if (!expected_time.has_value()) [[unlikely]] { - auto* errors{expected_time.error().get()}; + auto [time, errors]{construct_time(datetime)}; + if (time == nullptr) [[unlikely]] { kphp::log::warning("got {} errors in timelib_strtotime", errors->error_count); // TODO should we logs all the errors? return {}; } - time_t time{std::move(*expected_time)}; errc = 0; timelib_fill_holes(time.get(), now, TIMELIB_NO_CLONE); diff --git a/runtime-light/stdlib/time/timelib-functions.h b/runtime-light/stdlib/time/timelib-functions.h index cc9023a6c0..f428a393c0 100644 --- a/runtime-light/stdlib/time/timelib-functions.h +++ b/runtime-light/stdlib/time/timelib-functions.h @@ -50,10 +50,10 @@ using rel_time_t = std::unique_ptr; using time_t = std::unique_ptr; using time_offset_t = std::unique_ptr; -std::expected construct_time(std::string_view time_sv) noexcept; -std::expected construct_time(std::string_view time_sv, const char* format) noexcept; +std::pair construct_time(std::string_view time_sv) noexcept; +std::pair construct_time(std::string_view time_sv, const char* format) noexcept; time_offset_t construct_time_offset(timelib_time& t) noexcept; -std::expected construct_interval(std::string_view format) noexcept; +std::expected> construct_interval(std::string_view format) noexcept; time_t add(timelib_time& t, timelib_rel_time& interval) noexcept; @@ -93,7 +93,7 @@ int64_t gmmktime(std::optional hou, std::optional min, std::op std::optional mktime(std::optional hou, std::optional min, std::optional sec, std::optional mon, std::optional day, std::optional yea) noexcept; -std::expected modify(timelib_time& t, std::string_view modifier) noexcept; +error_container_t modify(timelib_time& t, std::string_view modifier) noexcept; time_t now(timelib_tzinfo* tzi) noexcept; diff --git a/tests/python/lib/kphp_run_once.py b/tests/python/lib/kphp_run_once.py index 2f94af2ab5..3bb69faaaa 100644 --- a/tests/python/lib/kphp_run_once.py +++ b/tests/python/lib/kphp_run_once.py @@ -180,8 +180,11 @@ def compare_php_and_kphp_stdout(self): # just open and read the file - it's easier than messing with popen, etc. with open(diff_artifact.file, 'r') as f: print('diff: ' + f.read()) - with open(diff_artifact.file, 'r') as f: - print(f.read()) + try: + with open(diff_artifact.file, 'r') as f: + print(f.read()) + except UnicodeDecodeError as e: + print(f'Some binary data in diff: {e}') return False From 62739139d8f00533240f364e26e7fee05c7c7cc7 Mon Sep 17 00:00:00 2001 From: Karim Shamazov Date: Thu, 11 Dec 2025 18:39:38 +0300 Subject: [PATCH 13/21] test 12_create_interval.php --- tests/phpt/datetime/12_create_interval.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpt/datetime/12_create_interval.php b/tests/phpt/datetime/12_create_interval.php index c74f3f4d2a..19d04927c9 100644 --- a/tests/phpt/datetime/12_create_interval.php +++ b/tests/phpt/datetime/12_create_interval.php @@ -1,4 +1,4 @@ -@ok k2_skip +@ok Date: Thu, 11 Dec 2025 18:41:23 +0300 Subject: [PATCH 14/21] remove extra ';' --- runtime-light/stdlib/time/timelib-functions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime-light/stdlib/time/timelib-functions.cpp b/runtime-light/stdlib/time/timelib-functions.cpp index 456f22e37e..52d4b75c6d 100644 --- a/runtime-light/stdlib/time/timelib-functions.cpp +++ b/runtime-light/stdlib/time/timelib-functions.cpp @@ -336,7 +336,7 @@ error_container_t modify(timelib_time& t, std::string_view modifier) noexcept { kphp::memory::libc_alloc_guard{}, timelib_update_from_sse(std::addressof(t)); t.have_relative = 0; std::memset(std::addressof(t.relative), 0, sizeof(t.relative)); - return std::move(errors);; + return std::move(errors); } time_t now(timelib_tzinfo* tzi) noexcept { From 028e647cf0844f415498161d8d0b481079ec3083 Mon Sep 17 00:00:00 2001 From: Karim Shamazov Date: Fri, 12 Dec 2025 11:36:31 +0300 Subject: [PATCH 15/21] auto -> int32_t --- runtime-light/stdlib/time/time-functions.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/runtime-light/stdlib/time/time-functions.cpp b/runtime-light/stdlib/time/time-functions.cpp index a36c191bd2..1a923a574b 100644 --- a/runtime-light/stdlib/time/time-functions.cpp +++ b/runtime-light/stdlib/time/time-functions.cpp @@ -42,7 +42,7 @@ void iso_week_number(int32_t y, int32_t doy, int32_t weekday, int32_t& iw, int32 } /* Find if Y M D falls in YearNumber Y+1, WeekNumber 1 */ if (iy == y) { - auto i{y_leap ? 366 : 365}; + int32_t i{y_leap ? 366 : 365}; if ((i - (doy - y_leap + 1)) < (4 - weekday)) { iy = y + 1; iw = 1; @@ -87,11 +87,11 @@ string date(const string& format, const tm& t, int64_t timestamp, bool local) no auto day_of_week{t.tm_wday}; auto day_of_year{t.tm_yday}; int64_t internet_time{0}; - auto iso_week{0}; - auto iso_year{0}; + int32_t iso_week{0}; + int32_t iso_year{0}; SB.clean(); - for (auto i{0}; i < format.size(); i++) { + for (int32_t i{}; i < format.size(); i++) { switch (format[i]) { case 'd': SB << static_cast(day / 10 + '0') << static_cast(day % 10 + '0'); From dc430238febf1677701d0d10e5c28372f60060bf Mon Sep 17 00:00:00 2001 From: Karim Shamazov Date: Fri, 12 Dec 2025 14:18:26 +0300 Subject: [PATCH 16/21] fixes --- runtime-light/stdlib/time/dateinterval.cpp | 2 +- runtime-light/stdlib/time/datetime.cpp | 4 ++-- runtime-light/stdlib/time/datetimeimmutable.cpp | 4 ++-- runtime-light/stdlib/time/datetimezone.cpp | 3 +-- runtime-light/stdlib/time/timelib-functions.cpp | 2 +- runtime-light/stdlib/time/timelib-functions.h | 4 ++-- tests/kphp_tester.py | 2 +- tests/phpt/datetime/02_create.php | 2 +- tests/phpt/datetime/05_modify_wrong_modifier.php | 2 +- tests/phpt/datetime/08_date_time_zone.php | 2 +- tests/phpt/datetime/09_create_from_format.php | 2 +- tests/phpt/datetime/14_create_interval_wrong_date.php | 2 +- 12 files changed, 15 insertions(+), 16 deletions(-) diff --git a/runtime-light/stdlib/time/dateinterval.cpp b/runtime-light/stdlib/time/dateinterval.cpp index 0aa2c192d6..31cbf77204 100644 --- a/runtime-light/stdlib/time/dateinterval.cpp +++ b/runtime-light/stdlib/time/dateinterval.cpp @@ -28,7 +28,7 @@ class_instance f$DateInterval$$__construct(const class_instance< class_instance f$DateInterval$$createFromDateString(const string& datetime) noexcept { auto [time, errors]{kphp::timelib::construct_time({datetime.c_str(), datetime.size()})}; if (time == nullptr) [[unlikely]] { - kphp::log::warning("DateInterval::createFromDateString(): failed to parse datetime ({}): {}", datetime.c_str(), errors); + kphp::log::warning("DateInterval::createFromDateString(): Unknown or bad format ({}) {}", datetime.c_str(), errors); return {}; } class_instance date_interval; diff --git a/runtime-light/stdlib/time/datetime.cpp b/runtime-light/stdlib/time/datetime.cpp index 70b810a443..6e970af5af 100644 --- a/runtime-light/stdlib/time/datetime.cpp +++ b/runtime-light/stdlib/time/datetime.cpp @@ -13,7 +13,7 @@ class_instance f$DateTime$$__construct(const class_instance(err_msg)); return {}; @@ -78,7 +78,7 @@ Optional> f$DateTime$$getLastErrors() noexcept { class_instance f$DateTime$$modify(const class_instance& self, const string& modifier) noexcept { auto errors{kphp::timelib::modify(*self->time, {modifier.c_str(), modifier.size()})}; if (errors != nullptr && errors->error_count > 0) [[unlikely]] { - kphp::log::warning("DateTime::modify(): failed to parse modifier ({}): {}", modifier.c_str(), errors); + kphp::log::warning("DateTime::modify(): Failed to parse time string ({}) {}", modifier.c_str(), errors); TimeInstanceState::get().update_last_errors(std::move(errors)); return {}; } diff --git a/runtime-light/stdlib/time/datetimeimmutable.cpp b/runtime-light/stdlib/time/datetimeimmutable.cpp index 44c2dc0687..e09548cff4 100644 --- a/runtime-light/stdlib/time/datetimeimmutable.cpp +++ b/runtime-light/stdlib/time/datetimeimmutable.cpp @@ -39,7 +39,7 @@ class_instance f$DateTimeImmutable$$__construct(const class auto [time, errors]{kphp::timelib::construct_time(std::string_view{str_to_parse.c_str(), str_to_parse.size()})}; if (time == nullptr) [[unlikely]] { string err_msg; - format_to(std::back_inserter(err_msg), "DateTimeImmutable::__construct(): failed to parse datetime ({}): {}", datetime.c_str(), errors); + format_to(std::back_inserter(err_msg), "DateTimeImmutable::__construct(): Failed to parse time string ({}) {}", datetime.c_str(), errors); TimeInstanceState::get().update_last_errors(std::move(errors)); THROW_EXCEPTION(kphp::exception::make_throwable(err_msg)); return {}; @@ -108,7 +108,7 @@ class_instance f$DateTimeImmutable$$modify(const class_inst auto new_date{clone_immutable(self)}; auto errors{kphp::timelib::modify(*new_date->time, {modifier.c_str(), modifier.size()})}; if (errors != nullptr && errors->error_count > 0) [[unlikely]] { - kphp::log::warning("DateTimeImmutable::modify(): failed to parse modifier ({}): {}", modifier.c_str(), errors); + kphp::log::warning("DateTimeImmutable::modify(): Failed to parse time string ({}) {}", modifier.c_str(), errors); TimeInstanceState::get().update_last_errors(std::move(errors)); return {}; } diff --git a/runtime-light/stdlib/time/datetimezone.cpp b/runtime-light/stdlib/time/datetimezone.cpp index f79787d432..b370b180d5 100644 --- a/runtime-light/stdlib/time/datetimezone.cpp +++ b/runtime-light/stdlib/time/datetimezone.cpp @@ -12,8 +12,7 @@ class_instance f$DateTimeZone$$__construct(const class_instance< self->tzi = kphp::timelib::get_timezone_info(timezone.c_str(), timelib_builtin_db(), std::addressof(errc)); if (self->tzi == nullptr) [[unlikely]] { string err_msg; - format_to(std::back_inserter(err_msg), "DateTimeZone::__construct(): can't get timezone info: timezone -> {}, error -> {}", timezone.c_str(), - timelib_get_error_message(errc)); + format_to(std::back_inserter(err_msg), "DateTimeZone::__construct(): Unknown or bad timezone ({})", timezone.c_str()); THROW_EXCEPTION(kphp::exception::make_throwable(err_msg)); } } diff --git a/runtime-light/stdlib/time/timelib-functions.cpp b/runtime-light/stdlib/time/timelib-functions.cpp index 52d4b75c6d..aad42fb6af 100644 --- a/runtime-light/stdlib/time/timelib-functions.cpp +++ b/runtime-light/stdlib/time/timelib-functions.cpp @@ -63,7 +63,7 @@ time_offset_t construct_time_offset(timelib_time& t) noexcept { offset->abbr = (kphp::memory::libc_alloc_guard{}, static_cast(timelib_malloc(9))); // GMT±xxxx\0 // set upper bound to 99 just to ensure that 'hours_offset' fits in {:02} auto hours_offset{std::min(std::abs(offset->offset / 3600), 99)}; - std::format_to_n(offset->abbr, 9, "GMT{}{:02}{:02}", (offset->offset < 0) ? '-' : '+', hours_offset, std::abs((offset->offset % 3600) / 60)); + *std::format_to_n(offset->abbr, 8, "GMT{}{:02}{:02}", (offset->offset < 0) ? '-' : '+', hours_offset, std::abs((offset->offset % 3600) / 60)).out = '\0'; return offset; } return time_offset_t{(kphp::memory::libc_alloc_guard{}, timelib_get_time_zone_info(t.sse, t.tz_info))}; diff --git a/runtime-light/stdlib/time/timelib-functions.h b/runtime-light/stdlib/time/timelib-functions.h index f428a393c0..c409af488a 100644 --- a/runtime-light/stdlib/time/timelib-functions.h +++ b/runtime-light/stdlib/time/timelib-functions.h @@ -422,8 +422,8 @@ struct std::formatter { auto format(const kphp::timelib::error_container_t& error, auto& ctx) const noexcept { if (error != nullptr) { // spit out the first library error message, at least - return format_to(ctx.out(), "at position {} ({}): {}", error->error_messages[0].position, error->error_messages[0].character, - error->error_messages[0].message); + return format_to(ctx.out(), "at position {} ({}): {}", error->error_messages[0].position, + error->error_messages[0].character != '\0' ? error->error_messages[0].character : ' ', error->error_messages[0].message); } return format_to(ctx.out(), "unknown error"); } diff --git a/tests/kphp_tester.py b/tests/kphp_tester.py index a06934a910..810ce4d209 100755 --- a/tests/kphp_tester.py +++ b/tests/kphp_tester.py @@ -312,7 +312,7 @@ def run_runtime_warn_test(test: TestFile, runner): return TestResult.failed(test, runner.artifacts, "got kphp run error") if runner.k2_bin is not None: - warning_pattern = "WARN {2}component-log" + warning_pattern = '"level":"WARN","target":"component-log"' else: warning_pattern = "Warning: " diff --git a/tests/phpt/datetime/02_create.php b/tests/phpt/datetime/02_create.php index b5b7061f2d..ce25f6e9fe 100644 --- a/tests/phpt/datetime/02_create.php +++ b/tests/phpt/datetime/02_create.php @@ -1,4 +1,4 @@ -@ok k2_skip +@ok Date: Fri, 12 Dec 2025 15:06:40 +0300 Subject: [PATCH 17/21] implement setISODate method --- builtin-functions/kphp-light/stdlib/time-functions.txt | 2 -- runtime-light/stdlib/time/datetime.cpp | 5 +++++ runtime-light/stdlib/time/datetime.h | 2 ++ runtime-light/stdlib/time/datetimeimmutable.cpp | 7 +++++++ runtime-light/stdlib/time/datetimeimmutable.h | 3 +++ runtime-light/stdlib/time/timelib-functions.cpp | 10 ++++++++++ runtime-light/stdlib/time/timelib-functions.h | 2 ++ tests/phpt/datetime/06_set_date.php | 2 +- 8 files changed, 30 insertions(+), 3 deletions(-) diff --git a/builtin-functions/kphp-light/stdlib/time-functions.txt b/builtin-functions/kphp-light/stdlib/time-functions.txt index e803664bbd..42fd557363 100644 --- a/builtin-functions/kphp-light/stdlib/time-functions.txt +++ b/builtin-functions/kphp-light/stdlib/time-functions.txt @@ -87,7 +87,6 @@ class DateTimeImmutable implements DateTimeInterface { public static function getLastErrors(): array|false; public function modify(string $modifier): ?DateTimeImmutable; public function setDate(int $year, int $month, int $day): DateTimeImmutable; - /** @kphp-extern-func-info stub generation-required */ public function setISODate(int $year, int $week, int $dayOfWeek = 1): DateTimeImmutable; public function setTime( int $hour, @@ -114,7 +113,6 @@ class DateTime implements DateTimeInterface { public static function getLastErrors(): array|false; public function modify(string $modifier): ?DateTime; public function setDate(int $year, int $month, int $day): DateTime; - /** @kphp-extern-func-info stub generation-required */ public function setISODate(int $year, int $week, int $dayOfWeek = 1): DateTime; public function setTime( int $hour, diff --git a/runtime-light/stdlib/time/datetime.cpp b/runtime-light/stdlib/time/datetime.cpp index 6e970af5af..f0378b13b2 100644 --- a/runtime-light/stdlib/time/datetime.cpp +++ b/runtime-light/stdlib/time/datetime.cpp @@ -91,6 +91,11 @@ class_instance f$DateTime$$setDate(const class_instance& return self; } +class_instance f$DateTime$$setISODate(const class_instance& self, int64_t year, int64_t week, int64_t dayOfWeek) noexcept { + kphp::timelib::set_isodate(*self->time, year, week, dayOfWeek); + return self; +} + class_instance f$DateTime$$setTime(const class_instance& self, int64_t hour, int64_t minute, int64_t second, int64_t microsecond) noexcept { kphp::timelib::set_time(*self->time, hour, minute, second, microsecond); diff --git a/runtime-light/stdlib/time/datetime.h b/runtime-light/stdlib/time/datetime.h index 647898295a..c7ba3300c8 100644 --- a/runtime-light/stdlib/time/datetime.h +++ b/runtime-light/stdlib/time/datetime.h @@ -45,6 +45,8 @@ class_instance f$DateTime$$modify(const class_instance& class_instance f$DateTime$$setDate(const class_instance& self, int64_t year, int64_t month, int64_t day) noexcept; +class_instance f$DateTime$$setISODate(const class_instance& self, int64_t year, int64_t week, int64_t dayOfWeek = 1) noexcept; + class_instance f$DateTime$$setTime(const class_instance& self, int64_t hour, int64_t minute, int64_t second = 0, int64_t microsecond = 0) noexcept; diff --git a/runtime-light/stdlib/time/datetimeimmutable.cpp b/runtime-light/stdlib/time/datetimeimmutable.cpp index e09548cff4..b6cd153590 100644 --- a/runtime-light/stdlib/time/datetimeimmutable.cpp +++ b/runtime-light/stdlib/time/datetimeimmutable.cpp @@ -123,6 +123,13 @@ class_instance f$DateTimeImmutable$$setDate(const class_ins return new_date; } +class_instance f$DateTimeImmutable$$setISODate(const class_instance& self, int64_t year, int64_t week, + int64_t dayOfWeek) noexcept { + auto new_date = clone_immutable(self); + kphp::timelib::set_isodate(*new_date->time, year, week, dayOfWeek); + return new_date; +} + class_instance f$DateTimeImmutable$$setTime(const class_instance& self, int64_t hour, int64_t minute, int64_t second, int64_t microsecond) noexcept { auto new_date = clone_immutable(self); diff --git a/runtime-light/stdlib/time/datetimeimmutable.h b/runtime-light/stdlib/time/datetimeimmutable.h index 3901a053e8..85bcdd62e0 100644 --- a/runtime-light/stdlib/time/datetimeimmutable.h +++ b/runtime-light/stdlib/time/datetimeimmutable.h @@ -49,6 +49,9 @@ class_instance f$DateTimeImmutable$$modify(const class_inst class_instance f$DateTimeImmutable$$setDate(const class_instance& self, int64_t year, int64_t month, int64_t day) noexcept; +class_instance f$DateTimeImmutable$$setISODate(const class_instance& self, int64_t year, int64_t week, + int64_t dayOfWeek = 1) noexcept; + class_instance f$DateTimeImmutable$$setTime(const class_instance& self, int64_t hour, int64_t minute, int64_t second = 0, int64_t microsecond = 0) noexcept; diff --git a/runtime-light/stdlib/time/timelib-functions.cpp b/runtime-light/stdlib/time/timelib-functions.cpp index aad42fb6af..5bf15e48c5 100644 --- a/runtime-light/stdlib/time/timelib-functions.cpp +++ b/runtime-light/stdlib/time/timelib-functions.cpp @@ -363,6 +363,16 @@ void set_date(timelib_time& t, int64_t y, int64_t m, int64_t d) noexcept { kphp::memory::libc_alloc_guard{}, timelib_update_ts(std::addressof(t), nullptr); } +void set_isodate(timelib_time& t, int64_t y, int64_t w, int64_t d) noexcept { + t.y = y; + t.m = 1; + t.d = 1; + std::memset(std::addressof(t.relative), 0, sizeof(t.relative)); + t.relative.d = timelib_daynr_from_weeknr(y, w, d); + t.have_relative = 1; + kphp::memory::libc_alloc_guard{}, timelib_update_ts(std::addressof(t), nullptr); +} + void set_time(timelib_time& t, int64_t h, int64_t i, int64_t s, int64_t ms) noexcept { t.h = h; t.i = i; diff --git a/runtime-light/stdlib/time/timelib-functions.h b/runtime-light/stdlib/time/timelib-functions.h index c409af488a..3f33cbd10d 100644 --- a/runtime-light/stdlib/time/timelib-functions.h +++ b/runtime-light/stdlib/time/timelib-functions.h @@ -99,6 +99,8 @@ time_t now(timelib_tzinfo* tzi) noexcept; void set_date(timelib_time& t, int64_t y, int64_t m, int64_t d) noexcept; +void set_isodate(timelib_time& t, int64_t y, int64_t w, int64_t d) noexcept; + void set_time(timelib_time& t, int64_t h, int64_t i, int64_t s, int64_t ms) noexcept; std::optional strtotime(std::string_view timezone, std::string_view datetime, int64_t timestamp) noexcept; diff --git a/tests/phpt/datetime/06_set_date.php b/tests/phpt/datetime/06_set_date.php index 209290dc84..2ea8038281 100644 --- a/tests/phpt/datetime/06_set_date.php +++ b/tests/phpt/datetime/06_set_date.php @@ -1,4 +1,4 @@ -@ok k2_skip +@ok Date: Fri, 12 Dec 2025 19:34:32 +0300 Subject: [PATCH 18/21] implement sub methods --- .../kphp-light/stdlib/time-functions.txt | 2 -- runtime-light/stdlib/time/datetime.cpp | 10 ++++++++++ runtime-light/stdlib/time/datetime.h | 2 ++ .../stdlib/time/datetimeimmutable.cpp | 18 +++++++++++++++--- runtime-light/stdlib/time/datetimeimmutable.h | 3 +++ .../stdlib/time/timelib-functions.cpp | 9 +++++++++ runtime-light/stdlib/time/timelib-functions.h | 2 ++ .../phpt/datetime/16_add_sub_date_interval.php | 2 +- .../17_sub_relative_interval_warning.php | 2 +- 9 files changed, 43 insertions(+), 7 deletions(-) diff --git a/builtin-functions/kphp-light/stdlib/time-functions.txt b/builtin-functions/kphp-light/stdlib/time-functions.txt index 42fd557363..cca71c43ff 100644 --- a/builtin-functions/kphp-light/stdlib/time-functions.txt +++ b/builtin-functions/kphp-light/stdlib/time-functions.txt @@ -95,7 +95,6 @@ class DateTimeImmutable implements DateTimeInterface { int $microsecond = 0 ): DateTimeImmutable; public function setTimestamp(int $timestamp): DateTimeImmutable; - /** @kphp-extern-func-info stub generation-required */ public function sub(DateInterval $interval): DateTimeImmutable; public function diff(DateTimeInterface $targetObject, bool $absolute = false): DateInterval; public function format(string $format): string; @@ -121,7 +120,6 @@ class DateTime implements DateTimeInterface { int $microsecond = 0 ): DateTime; public function setTimestamp(int $timestamp): DateTime; - /** @kphp-extern-func-info stub generation-required */ public function sub(DateInterval $interval): DateTime; public function diff(DateTimeInterface $targetObject, bool $absolute = false): DateInterval; public function format(string $format): string; diff --git a/runtime-light/stdlib/time/datetime.cpp b/runtime-light/stdlib/time/datetime.cpp index f0378b13b2..366ce9d515 100644 --- a/runtime-light/stdlib/time/datetime.cpp +++ b/runtime-light/stdlib/time/datetime.cpp @@ -107,6 +107,16 @@ class_instance f$DateTime$$setTimestamp(const class_instance f$DateTime$$sub(const class_instance& self, const class_instance& interval) noexcept { + auto expected_new_time{kphp::timelib::sub(*self->time, *interval->rel_time)}; + if (!expected_new_time.has_value()) { + kphp::log::warning("DateTime::sub(): {}", expected_new_time.error()); + return self; + } + self->time = std::move(*expected_new_time); + return self; +} + class_instance f$DateTime$$diff(const class_instance& self, const class_instance& target_object, bool absolute) noexcept { class_instance interval; diff --git a/runtime-light/stdlib/time/datetime.h b/runtime-light/stdlib/time/datetime.h index c7ba3300c8..da1f671e83 100644 --- a/runtime-light/stdlib/time/datetime.h +++ b/runtime-light/stdlib/time/datetime.h @@ -52,6 +52,8 @@ class_instance f$DateTime$$setTime(const class_instance& class_instance f$DateTime$$setTimestamp(const class_instance& self, int64_t timestamp) noexcept; +class_instance f$DateTime$$sub(const class_instance& self, const class_instance& interval) noexcept; + class_instance f$DateTime$$diff(const class_instance& self, const class_instance& target_object, bool absolute = false) noexcept; diff --git a/runtime-light/stdlib/time/datetimeimmutable.cpp b/runtime-light/stdlib/time/datetimeimmutable.cpp index b6cd153590..a8467c61f7 100644 --- a/runtime-light/stdlib/time/datetimeimmutable.cpp +++ b/runtime-light/stdlib/time/datetimeimmutable.cpp @@ -118,21 +118,21 @@ class_instance f$DateTimeImmutable$$modify(const class_inst class_instance f$DateTimeImmutable$$setDate(const class_instance& self, int64_t year, int64_t month, int64_t day) noexcept { - auto new_date = clone_immutable(self); + auto new_date{clone_immutable(self)}; kphp::timelib::set_date(*new_date->time, year, month, day); return new_date; } class_instance f$DateTimeImmutable$$setISODate(const class_instance& self, int64_t year, int64_t week, int64_t dayOfWeek) noexcept { - auto new_date = clone_immutable(self); + auto new_date{clone_immutable(self)}; kphp::timelib::set_isodate(*new_date->time, year, week, dayOfWeek); return new_date; } class_instance f$DateTimeImmutable$$setTime(const class_instance& self, int64_t hour, int64_t minute, int64_t second, int64_t microsecond) noexcept { - auto new_date = clone_immutable(self); + auto new_date{clone_immutable(self)}; kphp::timelib::set_time(*new_date->time, hour, minute, second, microsecond); return new_date; } @@ -143,6 +143,18 @@ class_instance f$DateTimeImmutable$$setTimestamp(const clas return new_date; } +class_instance f$DateTimeImmutable$$sub(const class_instance& self, + const class_instance& interval) noexcept { + auto new_date{clone_immutable(self)}; + auto expected_new_time{kphp::timelib::sub(*new_date->time, *interval->rel_time)}; + if (!expected_new_time.has_value()) { + kphp::log::warning("DateTimeImmutable::sub(): {}", expected_new_time.error()); + return new_date; + } + new_date->time = std::move(*expected_new_time); + return new_date; +} + class_instance f$DateTimeImmutable$$diff(const class_instance& self, const class_instance& target_object, bool absolute) noexcept { class_instance interval; diff --git a/runtime-light/stdlib/time/datetimeimmutable.h b/runtime-light/stdlib/time/datetimeimmutable.h index 85bcdd62e0..82bcbdbec6 100644 --- a/runtime-light/stdlib/time/datetimeimmutable.h +++ b/runtime-light/stdlib/time/datetimeimmutable.h @@ -57,6 +57,9 @@ class_instance f$DateTimeImmutable$$setTime(const class_ins class_instance f$DateTimeImmutable$$setTimestamp(const class_instance& self, int64_t timestamp) noexcept; +class_instance f$DateTimeImmutable$$sub(const class_instance& self, + const class_instance& interval) noexcept; + class_instance f$DateTimeImmutable$$diff(const class_instance& self, const class_instance& target_object, bool absolute = false) noexcept; diff --git a/runtime-light/stdlib/time/timelib-functions.cpp b/runtime-light/stdlib/time/timelib-functions.cpp index 5bf15e48c5..d50b664178 100644 --- a/runtime-light/stdlib/time/timelib-functions.cpp +++ b/runtime-light/stdlib/time/timelib-functions.cpp @@ -381,6 +381,15 @@ void set_time(timelib_time& t, int64_t h, int64_t i, int64_t s, int64_t ms) noex kphp::memory::libc_alloc_guard{}, timelib_update_ts(std::addressof(t), nullptr); } +std::expected sub(timelib_time& t, timelib_rel_time& interval) noexcept { + if (interval.have_special_relative) { + return std::unexpected{"Only non-special relative time specifications are supported for subtraction"}; + } + + time_t new_time{(kphp::memory::libc_alloc_guard{}, timelib_sub(std::addressof(t), std::addressof(interval)))}; + return new_time; +} + std::optional strtotime(std::string_view timezone, std::string_view datetime, int64_t timestamp) noexcept { if (datetime.empty()) [[unlikely]] { kphp::log::warning("datetime can't be empty"); diff --git a/runtime-light/stdlib/time/timelib-functions.h b/runtime-light/stdlib/time/timelib-functions.h index 3f33cbd10d..46290004d1 100644 --- a/runtime-light/stdlib/time/timelib-functions.h +++ b/runtime-light/stdlib/time/timelib-functions.h @@ -103,6 +103,8 @@ void set_isodate(timelib_time& t, int64_t y, int64_t w, int64_t d) noexcept; void set_time(timelib_time& t, int64_t h, int64_t i, int64_t s, int64_t ms) noexcept; +std::expected sub(timelib_time& t, timelib_rel_time& interval) noexcept; + std::optional strtotime(std::string_view timezone, std::string_view datetime, int64_t timestamp) noexcept; bool valid_date(int64_t year, int64_t month, int64_t day) noexcept; diff --git a/tests/phpt/datetime/16_add_sub_date_interval.php b/tests/phpt/datetime/16_add_sub_date_interval.php index 27291d52df..c09a7544c8 100644 --- a/tests/phpt/datetime/16_add_sub_date_interval.php +++ b/tests/phpt/datetime/16_add_sub_date_interval.php @@ -1,4 +1,4 @@ -@ok k2_skip +@ok Date: Mon, 15 Dec 2025 12:05:39 +0300 Subject: [PATCH 19/21] implement getOffset methods --- .../kphp-light/stdlib/time-functions.txt | 2 -- runtime-light/stdlib/time/datetime.cpp | 4 ++++ runtime-light/stdlib/time/datetime.h | 2 ++ .../stdlib/time/datetimeimmutable.cpp | 4 ++++ runtime-light/stdlib/time/datetimeimmutable.h | 2 ++ .../stdlib/time/timelib-functions.cpp | 19 +++++++++++++++++++ runtime-light/stdlib/time/timelib-functions.h | 2 ++ tests/phpt/datetime/10_get_offset.php | 2 +- 8 files changed, 34 insertions(+), 3 deletions(-) diff --git a/builtin-functions/kphp-light/stdlib/time-functions.txt b/builtin-functions/kphp-light/stdlib/time-functions.txt index cca71c43ff..617dc6e0e1 100644 --- a/builtin-functions/kphp-light/stdlib/time-functions.txt +++ b/builtin-functions/kphp-light/stdlib/time-functions.txt @@ -98,7 +98,6 @@ class DateTimeImmutable implements DateTimeInterface { public function sub(DateInterval $interval): DateTimeImmutable; public function diff(DateTimeInterface $targetObject, bool $absolute = false): DateInterval; public function format(string $format): string; - /** @kphp-extern-func-info stub generation-required */ public function getOffset(): int; public function getTimestamp(): int; } @@ -123,7 +122,6 @@ class DateTime implements DateTimeInterface { public function sub(DateInterval $interval): DateTime; public function diff(DateTimeInterface $targetObject, bool $absolute = false): DateInterval; public function format(string $format): string; - /** @kphp-extern-func-info stub generation-required */ public function getOffset(): int; public function getTimestamp(): int; } diff --git a/runtime-light/stdlib/time/datetime.cpp b/runtime-light/stdlib/time/datetime.cpp index 366ce9d515..0396e4caaa 100644 --- a/runtime-light/stdlib/time/datetime.cpp +++ b/runtime-light/stdlib/time/datetime.cpp @@ -131,6 +131,10 @@ string f$DateTime$$format(const class_instance& self, const string& return str; } +int64_t f$DateTime$$getOffset(const class_instance& self) noexcept { + return kphp::timelib::get_offset(*self->time); +} + int64_t f$DateTime$$getTimestamp(const class_instance& self) noexcept { return kphp::timelib::get_timestamp(*self->time); } diff --git a/runtime-light/stdlib/time/datetime.h b/runtime-light/stdlib/time/datetime.h index da1f671e83..fc59ddde2d 100644 --- a/runtime-light/stdlib/time/datetime.h +++ b/runtime-light/stdlib/time/datetime.h @@ -59,4 +59,6 @@ class_instance f$DateTime$$diff(const class_instance string f$DateTime$$format(const class_instance& self, const string& format) noexcept; +int64_t f$DateTime$$getOffset(const class_instance& self) noexcept; + int64_t f$DateTime$$getTimestamp(const class_instance& self) noexcept; diff --git a/runtime-light/stdlib/time/datetimeimmutable.cpp b/runtime-light/stdlib/time/datetimeimmutable.cpp index a8467c61f7..615d43f051 100644 --- a/runtime-light/stdlib/time/datetimeimmutable.cpp +++ b/runtime-light/stdlib/time/datetimeimmutable.cpp @@ -169,6 +169,10 @@ string f$DateTimeImmutable$$format(const class_instance& se return str; } +int64_t f$DateTimeImmutable$$getOffset(const class_instance& self) noexcept { + return kphp::timelib::get_offset(*self->time); +} + int64_t f$DateTimeImmutable$$getTimestamp(const class_instance& self) noexcept { return kphp::timelib::get_timestamp(*(self->time)); } diff --git a/runtime-light/stdlib/time/datetimeimmutable.h b/runtime-light/stdlib/time/datetimeimmutable.h index 82bcbdbec6..c17562311a 100644 --- a/runtime-light/stdlib/time/datetimeimmutable.h +++ b/runtime-light/stdlib/time/datetimeimmutable.h @@ -65,4 +65,6 @@ class_instance f$DateTimeImmutable$$diff(const class_instance& self, const string& format) noexcept; +int64_t f$DateTimeImmutable$$getOffset(const class_instance& self) noexcept; + int64_t f$DateTimeImmutable$$getTimestamp(const class_instance& self) noexcept; diff --git a/runtime-light/stdlib/time/timelib-functions.cpp b/runtime-light/stdlib/time/timelib-functions.cpp index d50b664178..37692faf5f 100644 --- a/runtime-light/stdlib/time/timelib-functions.cpp +++ b/runtime-light/stdlib/time/timelib-functions.cpp @@ -174,6 +174,25 @@ std::string_view english_suffix(timelib_sll number) noexcept { return "th"; } +int64_t get_offset(timelib_time& t) noexcept { + if (t.is_localtime) { + switch (t.zone_type) { + case TIMELIB_ZONETYPE_ID: { + time_offset_t offset{(kphp::memory::libc_alloc_guard{}, timelib_get_time_zone_info(t.sse, t.tz_info))}; + int64_t offset_int{offset->offset}; + return offset_int; + } + case TIMELIB_ZONETYPE_OFFSET: { + return t.z; + } + case TIMELIB_ZONETYPE_ABBR: { + return t.z + (3600 * t.dst); + } + } + } + return 0; +} + timelib_tzinfo* get_timezone_info(const char* timezone) noexcept { int errc{}; // it's intentionally declared as 'int' since timelib_parse_tzfile accepts 'int' auto* tzinfo{kphp::timelib::get_timezone_info(timezone, timelib_builtin_db(), std::addressof(errc))}; diff --git a/runtime-light/stdlib/time/timelib-functions.h b/runtime-light/stdlib/time/timelib-functions.h index 46290004d1..f53d016b91 100644 --- a/runtime-light/stdlib/time/timelib-functions.h +++ b/runtime-light/stdlib/time/timelib-functions.h @@ -72,6 +72,8 @@ void set_timestamp(timelib_time& t, int64_t timestamp) noexcept; std::string_view english_suffix(timelib_sll number) noexcept; +int64_t get_offset(timelib_time& t) noexcept; + timelib_tzinfo* get_timezone_info(const char* timezone) noexcept; /** * @brief Retrieves a pointer to a `timelib_tzinfo` structure for a given time zone name. diff --git a/tests/phpt/datetime/10_get_offset.php b/tests/phpt/datetime/10_get_offset.php index b2d62172ee..e48b642b76 100644 --- a/tests/phpt/datetime/10_get_offset.php +++ b/tests/phpt/datetime/10_get_offset.php @@ -1,4 +1,4 @@ -@ok k2_skip +@ok Date: Thu, 25 Dec 2025 16:16:35 +0300 Subject: [PATCH 20/21] construct -> parse --- runtime-light/stdlib/time/dateinterval.cpp | 4 +- runtime-light/stdlib/time/datetime.cpp | 4 +- .../stdlib/time/datetimeimmutable.cpp | 4 +- .../stdlib/time/timelib-functions.cpp | 52 +++++++++---------- runtime-light/stdlib/time/timelib-functions.h | 7 +-- 5 files changed, 36 insertions(+), 35 deletions(-) diff --git a/runtime-light/stdlib/time/dateinterval.cpp b/runtime-light/stdlib/time/dateinterval.cpp index 31cbf77204..14f6f136b3 100644 --- a/runtime-light/stdlib/time/dateinterval.cpp +++ b/runtime-light/stdlib/time/dateinterval.cpp @@ -14,7 +14,7 @@ #include "runtime-light/stdlib/diagnostics/logs.h" class_instance f$DateInterval$$__construct(const class_instance& self, const string& duration) noexcept { - auto expected_rel_time{kphp::timelib::construct_interval({duration.c_str(), duration.size()})}; + auto expected_rel_time{kphp::timelib::parse_interval({duration.c_str(), duration.size()})}; if (!expected_rel_time.has_value()) [[unlikely]] { string err_msg{"DateInterval::__construct(): "}; format_to(std::back_inserter(err_msg), expected_rel_time.error(), duration.c_str()); @@ -26,7 +26,7 @@ class_instance f$DateInterval$$__construct(const class_instance< } class_instance f$DateInterval$$createFromDateString(const string& datetime) noexcept { - auto [time, errors]{kphp::timelib::construct_time({datetime.c_str(), datetime.size()})}; + auto [time, errors]{kphp::timelib::parse_time({datetime.c_str(), datetime.size()})}; if (time == nullptr) [[unlikely]] { kphp::log::warning("DateInterval::createFromDateString(): Unknown or bad format ({}) {}", datetime.c_str(), errors); return {}; diff --git a/runtime-light/stdlib/time/datetime.cpp b/runtime-light/stdlib/time/datetime.cpp index 0396e4caaa..aadd0bea8e 100644 --- a/runtime-light/stdlib/time/datetime.cpp +++ b/runtime-light/stdlib/time/datetime.cpp @@ -10,7 +10,7 @@ class_instance f$DateTime$$__construct(const class_instance& self, const string& datetime, const class_instance& timezone) noexcept { const auto& str_to_parse{datetime.empty() ? StringLibConstants::get().NOW_STR : datetime}; - auto [time, errors]{kphp::timelib::construct_time(std::string_view{str_to_parse.c_str(), str_to_parse.size()})}; + auto [time, errors]{kphp::timelib::parse_time(std::string_view{str_to_parse.c_str(), str_to_parse.size()})}; if (time == nullptr) [[unlikely]] { string err_msg; format_to(std::back_inserter(err_msg), "DateTime::__construct(): Failed to parse time string ({}) {}", datetime.c_str(), errors); @@ -41,7 +41,7 @@ class_instance f$DateTime$$add(const class_instance& sel } class_instance f$DateTime$$createFromFormat(const string& format, const string& datetime, const class_instance& timezone) noexcept { - auto [time, errors]{kphp::timelib::construct_time({datetime.c_str(), datetime.size()}, format.c_str())}; + auto [time, errors]{kphp::timelib::parse_time({datetime.c_str(), datetime.size()}, format.c_str())}; if (time == nullptr) [[unlikely]] { TimeInstanceState::get().update_last_errors(std::move(errors)); return {}; diff --git a/runtime-light/stdlib/time/datetimeimmutable.cpp b/runtime-light/stdlib/time/datetimeimmutable.cpp index 615d43f051..a9140f8769 100644 --- a/runtime-light/stdlib/time/datetimeimmutable.cpp +++ b/runtime-light/stdlib/time/datetimeimmutable.cpp @@ -36,7 +36,7 @@ class_instance clone_immutable(const class_instance f$DateTimeImmutable$$__construct(const class_instance& self, const string& datetime, const class_instance& timezone) noexcept { const auto& str_to_parse{datetime.empty() ? StringLibConstants::get().NOW_STR : datetime}; - auto [time, errors]{kphp::timelib::construct_time(std::string_view{str_to_parse.c_str(), str_to_parse.size()})}; + auto [time, errors]{kphp::timelib::parse_time(std::string_view{str_to_parse.c_str(), str_to_parse.size()})}; if (time == nullptr) [[unlikely]] { string err_msg; format_to(std::back_inserter(err_msg), "DateTimeImmutable::__construct(): Failed to parse time string ({}) {}", datetime.c_str(), errors); @@ -70,7 +70,7 @@ class_instance f$DateTimeImmutable$$add(const class_instanc class_instance f$DateTimeImmutable$$createFromFormat(const string& format, const string& datetime, const class_instance& timezone) noexcept { - auto [time, errors]{kphp::timelib::construct_time({datetime.c_str(), datetime.size()}, format.c_str())}; + auto [time, errors]{kphp::timelib::parse_time({datetime.c_str(), datetime.size()}, format.c_str())}; if (time == nullptr) [[unlikely]] { TimeInstanceState::get().update_last_errors(std::move(errors)); return {}; diff --git a/runtime-light/stdlib/time/timelib-functions.cpp b/runtime-light/stdlib/time/timelib-functions.cpp index 37692faf5f..62e68c341c 100644 --- a/runtime-light/stdlib/time/timelib-functions.cpp +++ b/runtime-light/stdlib/time/timelib-functions.cpp @@ -23,29 +23,6 @@ namespace kphp::timelib { -std::pair construct_time(std::string_view time_sv) noexcept { - timelib_error_container* errors{}; - time_t time{(kphp::memory::libc_alloc_guard{}, - timelib_strtotime(time_sv.data(), time_sv.size(), std::addressof(errors), timelib_builtin_db(), kphp::timelib::get_timezone_info))}; - if (errors->error_count != 0) [[unlikely]] { - return {nullptr, error_container_t{errors}}; - } - - return {std::move(time), error_container_t{errors}}; -} - -std::pair construct_time(std::string_view time_sv, const char* format) noexcept { - timelib_error_container* err{nullptr}; - time_t t{(kphp::memory::libc_alloc_guard{}, - timelib_parse_from_format(format, time_sv.data(), time_sv.size(), std::addressof(err), timelib_builtin_db(), kphp::timelib::get_timezone_info))}; - - if (err && err->error_count) { - return {nullptr, error_container_t{err}}; - } - - return {std::move(t), error_container_t{err}}; -} - time_offset_t construct_time_offset(timelib_time& t) noexcept { if (t.zone_type == TIMELIB_ZONETYPE_ABBR) { time_offset_t offset{(kphp::memory::libc_alloc_guard{}, timelib_time_offset_ctor())}; @@ -69,7 +46,30 @@ time_offset_t construct_time_offset(timelib_time& t) noexcept { return time_offset_t{(kphp::memory::libc_alloc_guard{}, timelib_get_time_zone_info(t.sse, t.tz_info))}; } -std::expected> construct_interval(std::string_view format) noexcept { +std::pair parse_time(std::string_view time_sv) noexcept { + timelib_error_container* errors{}; + time_t time{(kphp::memory::libc_alloc_guard{}, + timelib_strtotime(time_sv.data(), time_sv.size(), std::addressof(errors), timelib_builtin_db(), kphp::timelib::get_timezone_info))}; + if (errors->error_count != 0) [[unlikely]] { + return {nullptr, error_container_t{errors}}; + } + + return {std::move(time), error_container_t{errors}}; +} + +std::pair parse_time(std::string_view time_sv, const char* format) noexcept { + timelib_error_container* err{nullptr}; + time_t t{(kphp::memory::libc_alloc_guard{}, + timelib_parse_from_format(format, time_sv.data(), time_sv.size(), std::addressof(err), timelib_builtin_db(), kphp::timelib::get_timezone_info))}; + + if (err && err->error_count) { + return {nullptr, error_container_t{err}}; + } + + return {std::move(t), error_container_t{err}}; +} + +std::expected> parse_interval(std::string_view format) noexcept { timelib_time* b{nullptr}; timelib_time* e{nullptr}; vk::final_action e_deleter{[e]() { kphp::memory::libc_alloc_guard{}, free(e); }}; @@ -312,7 +312,7 @@ std::optional mktime(std::optional hou, std::optional } error_container_t modify(timelib_time& t, std::string_view modifier) noexcept { - auto [tmp_time, errors]{construct_time(modifier)}; + auto [tmp_time, errors]{parse_time(modifier)}; if (tmp_time == nullptr) [[unlikely]] { return std::move(errors); @@ -430,7 +430,7 @@ std::optional strtotime(std::string_view timezone, std::string_view dat now->zone_type = TIMELIB_ZONETYPE_ID; timelib_unixtime2local(now, timestamp); - auto [time, errors]{construct_time(datetime)}; + auto [time, errors]{parse_time(datetime)}; if (time == nullptr) [[unlikely]] { kphp::log::warning("got {} errors in timelib_strtotime", errors->error_count); // TODO should we logs all the errors? return {}; diff --git a/runtime-light/stdlib/time/timelib-functions.h b/runtime-light/stdlib/time/timelib-functions.h index f53d016b91..3c40d6ea09 100644 --- a/runtime-light/stdlib/time/timelib-functions.h +++ b/runtime-light/stdlib/time/timelib-functions.h @@ -50,10 +50,11 @@ using rel_time_t = std::unique_ptr; using time_t = std::unique_ptr; using time_offset_t = std::unique_ptr; -std::pair construct_time(std::string_view time_sv) noexcept; -std::pair construct_time(std::string_view time_sv, const char* format) noexcept; time_offset_t construct_time_offset(timelib_time& t) noexcept; -std::expected> construct_interval(std::string_view format) noexcept; + +std::pair parse_time(std::string_view time_sv) noexcept; +std::pair parse_time(std::string_view time_sv, const char* format) noexcept; +std::expected> parse_interval(std::string_view format) noexcept; time_t add(timelib_time& t, timelib_rel_time& interval) noexcept; From befbf8d134dd178b1adc05fbca35d2d5841374f4 Mon Sep 17 00:00:00 2001 From: Karim Shamazov Date: Thu, 25 Dec 2025 16:20:56 +0300 Subject: [PATCH 21/21] use destructor{} --- runtime-light/stdlib/time/timelib-functions.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/runtime-light/stdlib/time/timelib-functions.cpp b/runtime-light/stdlib/time/timelib-functions.cpp index 62e68c341c..19d3765560 100644 --- a/runtime-light/stdlib/time/timelib-functions.cpp +++ b/runtime-light/stdlib/time/timelib-functions.cpp @@ -82,11 +82,11 @@ std::expected> parse_interval(std::s timelib_strtointerval(format.data(), format.size(), std::addressof(b), std::addressof(e), std::addressof(p), std::addressof(r), std::addressof(errors)); if (errors->error_count > 0) { - rel_time_t{p}; - error_container_t{errors}; + destructor{}(p); + destructor{}(errors); return std::unexpected{std::format_string{"Unknown or bad format ({})"}}; } - error_container_t{errors}; + destructor{}(errors); if (p != nullptr) { return rel_time_t{p};