From 22546b176907ccbb6b0fb775b0648309ca95d6dd Mon Sep 17 00:00:00 2001 From: sirdigbot Date: Sat, 22 Jan 2022 21:01:18 +1100 Subject: [PATCH 1/8] Add GetTimeStamp (strptime) native --- core/logic/smn_core.cpp | 53 +++++++++++++++++++++++++++++++++++ plugins/include/sourcemod.inc | 17 +++++++++++ 2 files changed, 70 insertions(+) diff --git a/core/logic/smn_core.cpp b/core/logic/smn_core.cpp index adf0a0fb1c..d466f9ce20 100644 --- a/core/logic/smn_core.cpp +++ b/core/logic/smn_core.cpp @@ -32,6 +32,8 @@ #include #include #include +#include +#include #include "common_logic.h" #include "Logger.h" @@ -206,6 +208,56 @@ static cell_t FormatTime(IPluginContext *pContext, const cell_t *params) return 1; } +static int DaysFromEpoch(int year, unsigned month, unsigned day) +{ + /* https://howardhinnant.github.io/date_algorithms.html#days_from_civil */ + year -= (month <= 2); + const int era = (year >= 0 ? year : year-399) / 400; // C++11 trunc. division + const unsigned yearOfEra = static_cast(year - era * 400); /* [0, 399] */ + const unsigned dayOfYear = (153*(month > 2 ? month-3 : month+9) + 2)/5 + day-1; /* [0, 365] */ + const unsigned dayOfEra = yearOfEra * 365 + yearOfEra/4 - yearOfEra/100 + dayOfYear; /* [0, 146096] */ + return era * 146097 + static_cast(dayOfEra) - 719468; +} + +static cell_t GetTimeStamp(IPluginConext *pContext, const cell_t *params) +{ + char *format, *datetime; + pContext->LocalToStringNULL(params[1], datetime); + pContext->LocalToStringNULL(params[2], format); + + if (format == NULL) + { + format = const_cast(bridge->GetCvarString(g_datetime_format)); + } + + std::tm t; + std::istringstream input(datetime); + input.imbue(std::locale(setlocale(LC_ALL, nullptr))); + input >> std::get_time(&t, format); + if (input.fail()) + { + return pContext->ThrowNativeError("Invalid date/time string or time format."); + } + + /* https://stackoverflow.com/a/58037981 */ + int year = t.tm_year + 1900; + int month = t.tm_mon; // 0-11 + if (month > 11) + { + year += month / 12; + month %= 12; + } + else if (month < 0) + { + int yearsDiff = (11 - month) / 12; + year -= yearsDiff; + month += 12 * yearsDiff; + } + + int totalDays = DaysFromEpoch(year, month + 1, t.tm_mday); + return 60 * (60 * (24L * totalDays + t.tm_hour) + t.tm_min) + t.tm_sec; +} + static cell_t GetPluginIterator(IPluginContext *pContext, const cell_t *params) { IPluginIterator *iter = scripts->GetPluginIterator(); @@ -967,6 +1019,7 @@ REGISTER_NATIVES(coreNatives) {"ThrowError", ThrowError}, {"GetTime", GetTime}, {"FormatTime", FormatTime}, + {"GetTimeStamp", GetTimeStamp}, {"GetPluginIterator", GetPluginIterator}, {"MorePlugins", MorePlugins}, {"ReadPlugin", ReadPlugin}, diff --git a/plugins/include/sourcemod.inc b/plugins/include/sourcemod.inc index 37137a6785..59051ec808 100644 --- a/plugins/include/sourcemod.inc +++ b/plugins/include/sourcemod.inc @@ -388,6 +388,23 @@ native int GetTime(int bigStamp[2]={0,0}); */ native void FormatTime(char[] buffer, int maxlength, const char[] format, int stamp=-1); +/** + * Parses a string representing a time into a unix timestamp. + * + * See this URL for valid parameters: + * http://cplusplus.com/reference/clibrary/ctime/strftime.html + * + * Note that available parameters depends on support from your operating system. + * In particular, ones highlighted in yellow on that page are not currently + * available on Windows and should be avoided for portable plugins. + * + * @param dateTime Date and/or time string. + * @param format Formatting rules (passing NULL_STRING will use the rules defined in sm_datetime_format). + * @return 32bit timestamp (number of seconds since unix epoch). + * @error Invalid date/time string or time format. + */ +native int GetTimeStamp(const char[] dateTime, const char[] format); + /** * Loads a game config file. * From 7f589e4578ad0f548b102d538dff0985a397839d Mon Sep 17 00:00:00 2001 From: sirdigbot Date: Sat, 22 Jan 2022 21:15:51 +1100 Subject: [PATCH 2/8] Fix description --- plugins/include/sourcemod.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/include/sourcemod.inc b/plugins/include/sourcemod.inc index 59051ec808..c89381e0ed 100644 --- a/plugins/include/sourcemod.inc +++ b/plugins/include/sourcemod.inc @@ -389,7 +389,7 @@ native int GetTime(int bigStamp[2]={0,0}); native void FormatTime(char[] buffer, int maxlength, const char[] format, int stamp=-1); /** - * Parses a string representing a time into a unix timestamp. + * Parses a string representing a date and/or time into a unix timestamp. * * See this URL for valid parameters: * http://cplusplus.com/reference/clibrary/ctime/strftime.html From 622f6425da8cf8ffa8e23ab7ec3d1ad377a4f4b9 Mon Sep 17 00:00:00 2001 From: sirdigbot Date: Sat, 22 Jan 2022 21:55:41 +1100 Subject: [PATCH 3/8] Fix typos --- core/logic/smn_core.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/logic/smn_core.cpp b/core/logic/smn_core.cpp index d466f9ce20..e82c29df16 100644 --- a/core/logic/smn_core.cpp +++ b/core/logic/smn_core.cpp @@ -212,18 +212,18 @@ static int DaysFromEpoch(int year, unsigned month, unsigned day) { /* https://howardhinnant.github.io/date_algorithms.html#days_from_civil */ year -= (month <= 2); - const int era = (year >= 0 ? year : year-399) / 400; // C++11 trunc. division + const int era = (year >= 0 ? year : year-399) / 400; /* C++11 trunc. division */ const unsigned yearOfEra = static_cast(year - era * 400); /* [0, 399] */ const unsigned dayOfYear = (153*(month > 2 ? month-3 : month+9) + 2)/5 + day-1; /* [0, 365] */ const unsigned dayOfEra = yearOfEra * 365 + yearOfEra/4 - yearOfEra/100 + dayOfYear; /* [0, 146096] */ return era * 146097 + static_cast(dayOfEra) - 719468; } -static cell_t GetTimeStamp(IPluginConext *pContext, const cell_t *params) +static cell_t GetTimeStamp(IPluginContext *pContext, const cell_t *params) { char *format, *datetime; - pContext->LocalToStringNULL(params[1], datetime); - pContext->LocalToStringNULL(params[2], format); + pContext->LocalToStringNULL(params[1], &datetime); + pContext->LocalToStringNULL(params[2], &format); if (format == NULL) { From 6a9012696cd2a0d97ad475519d4a1e276ba5198e Mon Sep 17 00:00:00 2001 From: sirdigbot Date: Sat, 22 Jan 2022 22:19:10 +1100 Subject: [PATCH 4/8] Fix issues --- core/logic/smn_core.cpp | 8 ++++---- plugins/include/sourcemod.inc | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/logic/smn_core.cpp b/core/logic/smn_core.cpp index e82c29df16..bc4353b4bc 100644 --- a/core/logic/smn_core.cpp +++ b/core/logic/smn_core.cpp @@ -219,10 +219,10 @@ static int DaysFromEpoch(int year, unsigned month, unsigned day) return era * 146097 + static_cast(dayOfEra) - 719468; } -static cell_t GetTimeStamp(IPluginContext *pContext, const cell_t *params) +static cell_t ParseTime(IPluginContext *pContext, const cell_t *params) { char *format, *datetime; - pContext->LocalToStringNULL(params[1], &datetime); + pContext->LocalToString(params[1], &datetime); pContext->LocalToStringNULL(params[2], &format); if (format == NULL) @@ -232,7 +232,7 @@ static cell_t GetTimeStamp(IPluginContext *pContext, const cell_t *params) std::tm t; std::istringstream input(datetime); - input.imbue(std::locale(setlocale(LC_ALL, nullptr))); + input.imbue(std::locale(setlocale(LC_TIME, nullptr))); input >> std::get_time(&t, format); if (input.fail()) { @@ -1019,7 +1019,7 @@ REGISTER_NATIVES(coreNatives) {"ThrowError", ThrowError}, {"GetTime", GetTime}, {"FormatTime", FormatTime}, - {"GetTimeStamp", GetTimeStamp}, + {"ParseTime", ParseTime}, {"GetPluginIterator", GetPluginIterator}, {"MorePlugins", MorePlugins}, {"ReadPlugin", ReadPlugin}, diff --git a/plugins/include/sourcemod.inc b/plugins/include/sourcemod.inc index c89381e0ed..71268e351e 100644 --- a/plugins/include/sourcemod.inc +++ b/plugins/include/sourcemod.inc @@ -403,7 +403,7 @@ native void FormatTime(char[] buffer, int maxlength, const char[] format, int st * @return 32bit timestamp (number of seconds since unix epoch). * @error Invalid date/time string or time format. */ -native int GetTimeStamp(const char[] dateTime, const char[] format); +native int ParseTime(const char[] dateTime, const char[] format); /** * Loads a game config file. From d1bb6662f7e763849430c2720c48f3cc13cbdcb6 Mon Sep 17 00:00:00 2001 From: sirdigbot Date: Tue, 18 Apr 2023 14:16:58 +1000 Subject: [PATCH 5/8] Rewrite ParseTime --- core/logic/smn_core.cpp | 56 ++++++++++++++++------------------------- 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/core/logic/smn_core.cpp b/core/logic/smn_core.cpp index 0ccd39294f..25b14f3c5f 100644 --- a/core/logic/smn_core.cpp +++ b/core/logic/smn_core.cpp @@ -275,54 +275,42 @@ static cell_t FormatTime(IPluginContext *pContext, const cell_t *params) return 1; } -static int DaysFromEpoch(int year, unsigned month, unsigned day) +static int ParseTime(IPluginContext *pContext, const cell_t *params) { - /* https://howardhinnant.github.io/date_algorithms.html#days_from_civil */ - year -= (month <= 2); - const int era = (year >= 0 ? year : year-399) / 400; /* C++11 trunc. division */ - const unsigned yearOfEra = static_cast(year - era * 400); /* [0, 399] */ - const unsigned dayOfYear = (153*(month > 2 ? month-3 : month+9) + 2)/5 + day-1; /* [0, 365] */ - const unsigned dayOfEra = yearOfEra * 365 + yearOfEra/4 - yearOfEra/100 + dayOfYear; /* [0, 146096] */ - return era * 146097 + static_cast(dayOfEra) - 719468; -} - -static cell_t ParseTime(IPluginContext *pContext, const cell_t *params) -{ - char *format, *datetime; - pContext->LocalToString(params[1], &datetime); + char *datetime; + char *format; + pContext->LocalToStringNULL(params[1], &datetime); pContext->LocalToStringNULL(params[2], &format); if (format == NULL) { format = const_cast(bridge->GetCvarString(g_datetime_format)); } - - std::tm t; - std::istringstream input(datetime); - input.imbue(std::locale(setlocale(LC_TIME, nullptr))); - input >> std::get_time(&t, format); - if (input.fail()) + else if (!format[0]) { - return pContext->ThrowNativeError("Invalid date/time string or time format."); + return pContext->ThrowNativeError("Time format string cannot be empty."); } - - /* https://stackoverflow.com/a/58037981 */ - int year = t.tm_year + 1900; - int month = t.tm_mon; // 0-11 - if (month > 11) + if (!datetime || !datetime[0]) { - year += month / 12; - month %= 12; + return pContext->ThrowNativeError("Date/time string cannot be empty."); } - else if (month < 0) + + // https://stackoverflow.com/a/33542189 + std::tm t{}; + std::istringstream input(datetime); + + auto previousLocale = input.imbue(std::locale::classic()); + input >> std::get_time(&t, format); + bool failed = input.fail(); + input.imbue(previousLocale); + + if (failed) { - int yearsDiff = (11 - month) / 12; - year -= yearsDiff; - month += 12 * yearsDiff; + return pContext->ThrowNativeError("Invalid date/time string or time format."); } - int totalDays = DaysFromEpoch(year, month + 1, t.tm_mday); - return 60 * (60 * (24L * totalDays + t.tm_hour) + t.tm_min) + t.tm_sec; + // Assuming "C" locale is localised not global, we don't need timegm() or _mkgmtime() + return mktime(&t); } static cell_t GetPluginIterator(IPluginContext *pContext, const cell_t *params) From 1d2d3ae544cd2e67f609ae44208041b77e9bea57 Mon Sep 17 00:00:00 2001 From: sirdigbot Date: Tue, 18 Apr 2023 18:47:17 +1000 Subject: [PATCH 6/8] Fix docstrings --- plugins/include/sourcemod.inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/include/sourcemod.inc b/plugins/include/sourcemod.inc index a95255a98d..200ff6f66e 100644 --- a/plugins/include/sourcemod.inc +++ b/plugins/include/sourcemod.inc @@ -397,7 +397,7 @@ native int GetTime(int bigStamp[2]={0,0}); * Produces a date and/or time string value for a timestamp. * * See this URL for valid parameters: - * http://cplusplus.com/reference/clibrary/ctime/strftime.html + * https://cplusplus.com/reference/ctime/strftime/ * * Note that available parameters depends on support from your operating system. * In particular, ones highlighted in yellow on that page are not currently @@ -415,7 +415,7 @@ native void FormatTime(char[] buffer, int maxlength, const char[] format, int st * Parses a string representing a date and/or time into a unix timestamp. * * See this URL for valid parameters: - * http://cplusplus.com/reference/clibrary/ctime/strftime.html + * https://en.cppreference.com/w/cpp/io/manip/get_time * * Note that available parameters depends on support from your operating system. * In particular, ones highlighted in yellow on that page are not currently From 12733edd8f10bd0714e173b6cd469a21fb84fd03 Mon Sep 17 00:00:00 2001 From: sirdigbot Date: Tue, 18 Apr 2023 18:47:52 +1000 Subject: [PATCH 7/8] Fix ParseTime --- core/logic/smn_core.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/logic/smn_core.cpp b/core/logic/smn_core.cpp index 25b14f3c5f..8e90d40310 100644 --- a/core/logic/smn_core.cpp +++ b/core/logic/smn_core.cpp @@ -309,8 +309,13 @@ static int ParseTime(IPluginContext *pContext, const cell_t *params) return pContext->ThrowNativeError("Invalid date/time string or time format."); } - // Assuming "C" locale is localised not global, we don't need timegm() or _mkgmtime() - return mktime(&t); +#if defined PLATFORM_WINDOWS + return _mkgmtime(&t); +#elif defined PLATFORM_LINUX || defined PLATFORM_APPLE + return timegm(&t); +#else + return pContext->ThrowNativeError("Platform has no implemented UTC conversion for std::tm to std::time_t"); +#endif } static cell_t GetPluginIterator(IPluginContext *pContext, const cell_t *params) From f65ded360dee63790b6211f2f0c426936675b21a Mon Sep 17 00:00:00 2001 From: Corey D Date: Tue, 4 Jul 2023 21:22:56 +1000 Subject: [PATCH 8/8] Clarify docs --- plugins/include/sourcemod.inc | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/include/sourcemod.inc b/plugins/include/sourcemod.inc index 200ff6f66e..432f8e51d0 100644 --- a/plugins/include/sourcemod.inc +++ b/plugins/include/sourcemod.inc @@ -413,6 +413,7 @@ native void FormatTime(char[] buffer, int maxlength, const char[] format, int st /** * Parses a string representing a date and/or time into a unix timestamp. + * The timezone is always interpreted as UTC/GMT. * * See this URL for valid parameters: * https://en.cppreference.com/w/cpp/io/manip/get_time