From 4a6d8341fed6ee19ccbb281bc2a091200e1245a4 Mon Sep 17 00:00:00 2001 From: Karim Shamazov Date: Mon, 14 Sep 2026 14:07:39 +0300 Subject: [PATCH 1/5] implement localtime --- runtime-light/stdlib/time/time-functions.h | 32 ++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/runtime-light/stdlib/time/time-functions.h b/runtime-light/stdlib/time/time-functions.h index 07a8de203c..168a4ce489 100644 --- a/runtime-light/stdlib/time/time-functions.h +++ b/runtime-light/stdlib/time/time-functions.h @@ -174,6 +174,38 @@ inline int64_t f$gmmktime(int64_t hour = std::numeric_limits::min(), in year != std::numeric_limits::min() ? std::make_optional(kphp::time::impl::fix_year(year)) : std::nullopt); } +inline array f$localtime(int64_t timestamp = std::numeric_limits::min(), bool is_associative = false) noexcept { + if (timestamp == std::numeric_limits::min()) { + namespace chrono = std::chrono; + timestamp = static_cast(chrono::time_point_cast(chrono::system_clock::now()).time_since_epoch().count()); + } + tm t{}; + time_t time{timestamp}; + tm* tp{k2::localtime_r(std::addressof(time), std::addressof(t))}; + if (tp == nullptr) { + kphp::log::warning("unknown error in localtime with timestamp {}", timestamp); + std::memset(std::addressof(t), 0, sizeof(tm)); + } + + if (!is_associative) { + return array::create(t.tm_sec, t.tm_min, t.tm_hour, t.tm_mday, t.tm_mon, t.tm_year, t.tm_wday, t.tm_yday, t.tm_isdst); + } + + array result{array_size{9, false}}; + + result.set_value(string{"tm_sec", 6}, t.tm_sec); + result.set_value(string{"tm_min", 6}, t.tm_min); + result.set_value(string{"tm_hour", 7}, t.tm_hour); + result.set_value(string{"tm_mday", 7}, t.tm_mday); + result.set_value(string{"tm_mon", 6}, t.tm_mon); + result.set_value(string{"tm_year", 7}, t.tm_year); + result.set_value(string{"tm_wday", 7}, t.tm_wday); + result.set_value(string{"tm_yday", 7}, t.tm_yday); + result.set_value(string{"tm_isdst", 8}, t.tm_isdst); + + return result; +} + inline string f$date(const string& format, int64_t timestamp = std::numeric_limits::min()) noexcept { if (timestamp == std::numeric_limits::min()) { namespace chrono = std::chrono; From 24f7b2a7407b28ea899a8becbc9fb2de364923be Mon Sep 17 00:00:00 2001 From: Karim Shamazov Date: Mon, 14 Sep 2026 14:09:46 +0300 Subject: [PATCH 2/5] update time-functions.txt --- builtin-functions/kphp-light/stdlib/time-functions.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin-functions/kphp-light/stdlib/time-functions.txt b/builtin-functions/kphp-light/stdlib/time-functions.txt index 617dc6e0e1..a81cfd3aa0 100644 --- a/builtin-functions/kphp-light/stdlib/time-functions.txt +++ b/builtin-functions/kphp-light/stdlib/time-functions.txt @@ -18,6 +18,8 @@ function gmdate ($format ::: string, $timestamp ::: int = PHP_INT_MIN) ::: strin function gmmktime ($h ::: int = PHP_INT_MIN, $m ::: int = PHP_INT_MIN, $s ::: int = PHP_INT_MIN, $month ::: int = PHP_INT_MIN, $day ::: int = PHP_INT_MIN, $year ::: int = PHP_INT_MIN) ::: int; +function localtime ($timestamp ::: int = PHP_INT_MIN, $is_associative ::: bool = false) ::: mixed[]; + function time() ::: int; function strtotime ($time ::: string, $timestamp ::: int = PHP_INT_MIN) ::: int | false; @@ -140,7 +142,5 @@ define('DATE_RFC3339', "Y-m-d\TH:i:sP"); define('DATE_RSS', "D, d M Y H:i:s O"); define('DATE_W3C', "Y-m-d\TH:i:sP"); -/** @kphp-extern-func-info stub generation-required */ -function localtime ($timestamp ::: int = PHP_INT_MIN, $is_associative ::: bool = false) ::: mixed[]; /** @kphp-extern-func-info stub generation-required */ function strftime ($format ::: string, $timestamp ::: int = PHP_INT_MIN) ::: string; From 8f21704b022edc3d9c4af72a8c82b716dd28d1ff Mon Sep 17 00:00:00 2001 From: Karim Shamazov Date: Mon, 14 Sep 2026 14:41:41 +0300 Subject: [PATCH 3/5] tests --- tests/phpt/dl/484_localtime.php | 17 +++++++++++++++++ tests/phpt/dl/484_strftime.php | 6 ------ 2 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 tests/phpt/dl/484_localtime.php diff --git a/tests/phpt/dl/484_localtime.php b/tests/phpt/dl/484_localtime.php new file mode 100644 index 0000000000..6e8901e241 --- /dev/null +++ b/tests/phpt/dl/484_localtime.php @@ -0,0 +1,17 @@ +@ok + Date: Tue, 15 Sep 2026 16:15:17 +0300 Subject: [PATCH 4/5] pre-initialize constant strings in TimeImageState --- runtime-light/stdlib/time/time-functions.h | 18 +++++----- runtime-light/stdlib/time/time-state.h | 38 ++++++++++++++++++++++ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/runtime-light/stdlib/time/time-functions.h b/runtime-light/stdlib/time/time-functions.h index 168a4ce489..ba76e51848 100644 --- a/runtime-light/stdlib/time/time-functions.h +++ b/runtime-light/stdlib/time/time-functions.h @@ -193,15 +193,15 @@ inline array f$localtime(int64_t timestamp = std::numeric_limits array result{array_size{9, false}}; - result.set_value(string{"tm_sec", 6}, t.tm_sec); - result.set_value(string{"tm_min", 6}, t.tm_min); - result.set_value(string{"tm_hour", 7}, t.tm_hour); - result.set_value(string{"tm_mday", 7}, t.tm_mday); - result.set_value(string{"tm_mon", 6}, t.tm_mon); - result.set_value(string{"tm_year", 7}, t.tm_year); - result.set_value(string{"tm_wday", 7}, t.tm_wday); - result.set_value(string{"tm_yday", 7}, t.tm_yday); - result.set_value(string{"tm_isdst", 8}, t.tm_isdst); + result.set_value(TimeImageState::get().TM_SEC_STR, t.tm_sec); + result.set_value(TimeImageState::get().TM_MIN_STR, t.tm_min); + result.set_value(TimeImageState::get().TM_HOUR_STR, t.tm_hour); + result.set_value(TimeImageState::get().TM_MDAY_STR, t.tm_mday); + result.set_value(TimeImageState::get().TM_MON_STR, t.tm_mon); + result.set_value(TimeImageState::get().TM_YEAR_STR, t.tm_year); + result.set_value(TimeImageState::get().TM_WDAY_STR, t.tm_wday); + result.set_value(TimeImageState::get().TM_YDAY_STR, t.tm_yday); + result.set_value(TimeImageState::get().TM_ISDST_STR, t.tm_isdst); return result; } diff --git a/runtime-light/stdlib/time/time-state.h b/runtime-light/stdlib/time/time-state.h index e76193f2b1..8727569b65 100644 --- a/runtime-light/stdlib/time/time-state.h +++ b/runtime-light/stdlib/time/time-state.h @@ -5,8 +5,10 @@ #pragma once #include +#include #include "common/mixin/not_copyable.h" +#include "common/php-functions.h" #include "runtime-common/core/runtime-core.h" #include "runtime-common/stdlib/time/timelib-constants.h" #include "runtime-light/core/reference-counter/reference-counter-functions.h" @@ -21,6 +23,15 @@ struct TimeImageState final : private vk::not_copyable { string WARNINGS_STR{WARNINGS_.data(), static_cast(WARNINGS_.size())}; string ERROR_COUNT_STR{ERROR_COUNT_.data(), static_cast(ERROR_COUNT_.size())}; string ERRORS_STR{ERRORS_.data(), static_cast(ERRORS_.size())}; + string TM_SEC_STR{TM_SEC_.data(), static_cast(TM_SEC_.size())}; + string TM_MIN_STR{TM_MIN_.data(), static_cast(TM_MIN_.size())}; + string TM_HOUR_STR{TM_HOUR_.data(), static_cast(TM_HOUR_.size())}; + string TM_MDAY_STR{TM_MDAY_.data(), static_cast(TM_MDAY_.size())}; + string TM_MON_STR{TM_MON_.data(), static_cast(TM_MON_.size())}; + string TM_YEAR_STR{TM_YEAR_.data(), static_cast(TM_YEAR_.size())}; + string TM_WDAY_STR{TM_WDAY_.data(), static_cast(TM_WDAY_.size())}; + string TM_YDAY_STR{TM_YDAY_.data(), static_cast(TM_YDAY_.size())}; + string TM_ISDST_STR{TM_ISDST_.data(), static_cast(TM_ISDST_.size())}; kphp::timelib::timezone_cache timelib_zone_cache{kphp::timelib::timezones::MOSCOW, kphp::timelib::timezones::GMT3}; @@ -35,6 +46,24 @@ struct TimeImageState final : private vk::not_copyable { kphp::core::is_reference_counter_recursive(ERROR_COUNT_STR, ExtraRefCnt::for_global_const))); kphp::log::assertion((kphp::core::set_reference_counter_recursive(ERRORS_STR, ExtraRefCnt::for_global_const), kphp::core::is_reference_counter_recursive(ERRORS_STR, ExtraRefCnt::for_global_const))); + kphp::log::assertion((kphp::core::set_reference_counter_recursive(TM_SEC_STR, ExtraRefCnt::for_global_const), + kphp::core::is_reference_counter_recursive(TM_SEC_STR, ExtraRefCnt::for_global_const))); + kphp::log::assertion((kphp::core::set_reference_counter_recursive(TM_MIN_STR, ExtraRefCnt::for_global_const), + kphp::core::is_reference_counter_recursive(TM_MIN_STR, ExtraRefCnt::for_global_const))); + kphp::log::assertion((kphp::core::set_reference_counter_recursive(TM_HOUR_STR, ExtraRefCnt::for_global_const), + kphp::core::is_reference_counter_recursive(TM_HOUR_STR, ExtraRefCnt::for_global_const))); + kphp::log::assertion((kphp::core::set_reference_counter_recursive(TM_MDAY_STR, ExtraRefCnt::for_global_const), + kphp::core::is_reference_counter_recursive(TM_MDAY_STR, ExtraRefCnt::for_global_const))); + kphp::log::assertion((kphp::core::set_reference_counter_recursive(TM_MON_STR, ExtraRefCnt::for_global_const), + kphp::core::is_reference_counter_recursive(TM_MON_STR, ExtraRefCnt::for_global_const))); + kphp::log::assertion((kphp::core::set_reference_counter_recursive(TM_YEAR_STR, ExtraRefCnt::for_global_const), + kphp::core::is_reference_counter_recursive(TM_YEAR_STR, ExtraRefCnt::for_global_const))); + kphp::log::assertion((kphp::core::set_reference_counter_recursive(TM_WDAY_STR, ExtraRefCnt::for_global_const), + kphp::core::is_reference_counter_recursive(TM_WDAY_STR, ExtraRefCnt::for_global_const))); + kphp::log::assertion((kphp::core::set_reference_counter_recursive(TM_YDAY_STR, ExtraRefCnt::for_global_const), + kphp::core::is_reference_counter_recursive(TM_YDAY_STR, ExtraRefCnt::for_global_const))); + kphp::log::assertion((kphp::core::set_reference_counter_recursive(TM_ISDST_STR, ExtraRefCnt::for_global_const), + kphp::core::is_reference_counter_recursive(TM_ISDST_STR, ExtraRefCnt::for_global_const))); } static const TimeImageState& get() noexcept; @@ -46,6 +75,15 @@ struct TimeImageState final : private vk::not_copyable { static constexpr std::string_view WARNINGS_ = "warnings"; static constexpr std::string_view ERROR_COUNT_ = "error_count"; static constexpr std::string_view ERRORS_ = "errors"; + static constexpr std::string_view TM_SEC_ = "tm_sec"; + static constexpr std::string_view TM_MIN_ = "tm_min"; + static constexpr std::string_view TM_HOUR_ = "tm_hour"; + static constexpr std::string_view TM_MDAY_ = "tm_mday"; + static constexpr std::string_view TM_MON_ = "tm_mon"; + static constexpr std::string_view TM_YEAR_ = "tm_year"; + static constexpr std::string_view TM_WDAY_ = "tm_wday"; + static constexpr std::string_view TM_YDAY_ = "tm_yday"; + static constexpr std::string_view TM_ISDST_ = "tm_isdst"; }; struct TimeInstanceState final : private vk::not_copyable { From b2aedfb6313dfd766c38fedce7ffc7c516801581 Mon Sep 17 00:00:00 2001 From: Karim Shamazov Date: Tue, 15 Sep 2026 17:43:40 +0300 Subject: [PATCH 5/5] fix --- runtime-light/stdlib/time/time-functions.h | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/runtime-light/stdlib/time/time-functions.h b/runtime-light/stdlib/time/time-functions.h index ba76e51848..38c88d49a4 100644 --- a/runtime-light/stdlib/time/time-functions.h +++ b/runtime-light/stdlib/time/time-functions.h @@ -191,17 +191,19 @@ inline array f$localtime(int64_t timestamp = std::numeric_limits return array::create(t.tm_sec, t.tm_min, t.tm_hour, t.tm_mday, t.tm_mon, t.tm_year, t.tm_wday, t.tm_yday, t.tm_isdst); } + const auto& time_image_state{TimeImageState::get()}; + array result{array_size{9, false}}; - result.set_value(TimeImageState::get().TM_SEC_STR, t.tm_sec); - result.set_value(TimeImageState::get().TM_MIN_STR, t.tm_min); - result.set_value(TimeImageState::get().TM_HOUR_STR, t.tm_hour); - result.set_value(TimeImageState::get().TM_MDAY_STR, t.tm_mday); - result.set_value(TimeImageState::get().TM_MON_STR, t.tm_mon); - result.set_value(TimeImageState::get().TM_YEAR_STR, t.tm_year); - result.set_value(TimeImageState::get().TM_WDAY_STR, t.tm_wday); - result.set_value(TimeImageState::get().TM_YDAY_STR, t.tm_yday); - result.set_value(TimeImageState::get().TM_ISDST_STR, t.tm_isdst); + result.set_value(time_image_state.TM_SEC_STR, t.tm_sec); + result.set_value(time_image_state.TM_MIN_STR, t.tm_min); + result.set_value(time_image_state.TM_HOUR_STR, t.tm_hour); + result.set_value(time_image_state.TM_MDAY_STR, t.tm_mday); + result.set_value(time_image_state.TM_MON_STR, t.tm_mon); + result.set_value(time_image_state.TM_YEAR_STR, t.tm_year); + result.set_value(time_image_state.TM_WDAY_STR, t.tm_wday); + result.set_value(time_image_state.TM_YDAY_STR, t.tm_yday); + result.set_value(time_image_state.TM_ISDST_STR, t.tm_isdst); return result; }