From 4e371fbd0a18284a53708e0ee48b19da692bd048 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Thu, 7 Nov 2024 18:44:11 +0800 Subject: [PATCH 01/45] feat:[TD-32642] add cmake configure for tz database --- cmake/cmake.define | 2 ++ cmake/tz_CMakeLists.txt.in | 15 +++++++++++++++ contrib/CMakeLists.txt | 7 +++++++ packaging/tools/make_install.bat | 4 ++++ packaging/tools/make_install.sh | 9 +++++++++ 5 files changed, 37 insertions(+) create mode 100644 cmake/tz_CMakeLists.txt.in diff --git a/cmake/cmake.define b/cmake/cmake.define index ff582261b3..1b277a1f3a 100644 --- a/cmake/cmake.define +++ b/cmake/cmake.define @@ -6,12 +6,14 @@ set(TD_BUILD_KEEPER_INTERNAL FALSE) # set output directory SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/build/lib) SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/build/bin) +SET(SHARE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/build/share) SET(TD_TESTS_OUTPUT_DIR ${PROJECT_BINARY_DIR}/test) MESSAGE(STATUS "Project source directory: " ${PROJECT_SOURCE_DIR}) MESSAGE(STATUS "Project binary files output path: " ${PROJECT_BINARY_DIR}) MESSAGE(STATUS "Project executable files output path: " ${EXECUTABLE_OUTPUT_PATH}) MESSAGE(STATUS "Project library files output path: " ${LIBRARY_OUTPUT_PATH}) +MESSAGE(STATUS "Project share files output path: " ${SHARE_OUTPUT_PATH}) IF(NOT DEFINED TD_GRANT) SET(TD_GRANT FALSE) diff --git a/cmake/tz_CMakeLists.txt.in b/cmake/tz_CMakeLists.txt.in new file mode 100644 index 0000000000..a2e3636d9a --- /dev/null +++ b/cmake/tz_CMakeLists.txt.in @@ -0,0 +1,15 @@ +# timezone +ExternalProject_Add(tz + GIT_REPOSITORY https://github.com/eggert/tz.git + GIT_TAG main + SOURCE_DIR "${TD_CONTRIB_DIR}/tz" + BINARY_DIR "" + CONFIGURE_COMMAND "" + #BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" + GIT_SHALLOW true + GIT_PROGRESS true + BUILD_COMMAND "" +) + diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index eae697560b..e52a5b4120 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -106,6 +106,8 @@ cat("${TD_SUPPORT_DIR}/zlib_CMakeLists.txt.in" ${CONTRIB_TMP_FILE}) # cJson cat("${TD_SUPPORT_DIR}/cjson_CMakeLists.txt.in" ${CONTRIB_TMP_FILE}) +cat("${TD_SUPPORT_DIR}/tz_CMakeLists.txt.in" ${CONTRIB_TMP_FILE}) + # xz # cat("${TD_SUPPORT_DIR}/xz_CMakeLists.txt.in" ${CONTRIB_TMP_FILE}) @@ -632,6 +634,11 @@ if(${TD_LINUX} AND ${BUILD_WITH_S3}) add_subdirectory(azure-cmake EXCLUDE_FROM_ALL) endif() +execute_process( + COMMAND make TZDIR=${SHARE_OUTPUT_PATH}/timezone all posix_only + WORKING_DIRECTORY "${TD_CONTRIB_DIR}/tz" +) + # ================================================================================================ # Build test # ================================================================================================ diff --git a/packaging/tools/make_install.bat b/packaging/tools/make_install.bat index 04d342ea06..ed4fc982b4 100644 --- a/packaging/tools/make_install.bat +++ b/packaging/tools/make_install.bat @@ -43,6 +43,9 @@ if not exist %target_dir%\\cfg ( if not exist %target_dir%\\include ( mkdir %target_dir%\\include ) +if not exist %target_dir%\\share ( + mkdir %target_dir%\\share +) if not exist %target_dir%\\driver ( mkdir %target_dir%\\driver ) @@ -67,6 +70,7 @@ copy %binary_dir%\\build\\lib\\taos.lib %target_dir%\\driver > nul copy %binary_dir%\\build\\lib\\taos_static.lib %target_dir%\\driver > nul copy %binary_dir%\\build\\lib\\taos.dll %target_dir%\\driver > nul copy %binary_dir%\\build\\bin\\taos.exe %target_dir% > nul +xcopy %binary_dir%\\build\\share\\* %target_dir%\\share > nul if exist %binary_dir%\\build\\bin\\taosBenchmark.exe ( copy %binary_dir%\\build\\bin\\taosBenchmark.exe %target_dir% > nul ) diff --git a/packaging/tools/make_install.sh b/packaging/tools/make_install.sh index 1b8fa2fb70..ee13f97f3e 100755 --- a/packaging/tools/make_install.sh +++ b/packaging/tools/make_install.sh @@ -52,6 +52,8 @@ else installDir="/usr/local/taos" fi fi + +timezone_dir="/usr/local/share/timezone" install_main_dir=${installDir} bin_dir="${installDir}/bin" cfg_dir="${installDir}/cfg" @@ -378,6 +380,11 @@ function install_header() { ${csudo}chmod 644 ${install_main_dir}/include/* } +function install_timezone(){ + ${csudo}mkdir -p ${timezone_dir} + ${csudo}cp -rf ${binary_dir}/build/share/timezone/* ${timezone_dir} && ${csudo}chmod 644 ${timezone_dir}/* +} + function install_config() { if [ ! -f ${cfg_install_dir}/${configFile} ]; then ${csudo}mkdir -p ${cfg_install_dir} @@ -634,6 +641,7 @@ function update_TDengine() { install_log install_header install_lib + install_timezone # install_connector install_examples install_bin @@ -699,6 +707,7 @@ function install_TDengine() { install_log install_header install_lib + install_timezone # install_connector install_examples install_bin From 3156eed774b6463032964f131bc2e98741b33282 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Fri, 8 Nov 2024 18:53:40 +0800 Subject: [PATCH 02/45] feat:[TD-32642] add tz source code to TD --- include/client/taos.h | 9 + include/common/tmsg.h | 1 + include/common/ttime.h | 2 +- include/os/osTimezone.h | 1143 +++++++++++++++++ source/client/inc/clientInt.h | 7 + source/client/src/clientEnv.c | 1 - source/common/src/ttime.c | 68 +- source/os/src/osTimezone.c | 2266 +++++++++++++++++++++++++++++++++ 8 files changed, 3430 insertions(+), 67 deletions(-) diff --git a/include/client/taos.h b/include/client/taos.h index 80dbe27c47..f820536983 100644 --- a/include/client/taos.h +++ b/include/client/taos.h @@ -64,6 +64,14 @@ typedef enum { TSDB_MAX_OPTIONS } TSDB_OPTION; +typedef enum { + TSDB_OPTION_CONNECT_CHARSET, + TSDB_OPTION_CONNECT_TIMEZONE, + TSDB_OPTION_CONNECT_IP, + TSDB_OPTION_CONNECT_APP_NAME, + TSDB_MAX_CONNECT_OPTIONS +} TSDB_OPTION_CONNECT; + typedef enum { TSDB_SML_UNKNOWN_PROTOCOL = 0, TSDB_SML_LINE_PROTOCOL = 1, @@ -158,6 +166,7 @@ typedef struct TAOS_STMT_OPTIONS { DLL_EXPORT void taos_cleanup(void); DLL_EXPORT int taos_options(TSDB_OPTION option, const void *arg, ...); +DLL_EXPORT int taos_options_connect(TAOS *taos, TSDB_OPTION_CONNECT option, const void *arg, ...); DLL_EXPORT setConfRet taos_set_config(const char *config); DLL_EXPORT int taos_init(void); DLL_EXPORT TAOS *taos_connect(const char *ip, const char *user, const char *pass, const char *db, uint16_t port); diff --git a/include/common/tmsg.h b/include/common/tmsg.h index cdcbb84462..b34acbadbe 100644 --- a/include/common/tmsg.h +++ b/include/common/tmsg.h @@ -3393,6 +3393,7 @@ typedef struct { SAppHbReq app; SQueryHbReqBasic* query; SHashObj* info; // hash + char name[TSDB_APP_NAME_LEN]; } SClientHbReq; typedef struct { diff --git a/include/common/ttime.h b/include/common/ttime.h index 65bb763b1f..8df4ebb5df 100644 --- a/include/common/ttime.h +++ b/include/common/ttime.h @@ -64,7 +64,7 @@ static FORCE_INLINE int64_t taosGetTimestampToday(int32_t precision) { : 1000000000; time_t t = taosTime(NULL); struct tm tm; - (void) taosLocalTime(&t, &tm, NULL, 0); + (void) taosLocalTime(&t, &tm, NULL, 0); //tztodo tm.tm_hour = 0; tm.tm_min = 0; tm.tm_sec = 0; diff --git a/include/os/osTimezone.h b/include/os/osTimezone.h index 2a8d5a442d..cf3ca6a96a 100644 --- a/include/os/osTimezone.h +++ b/include/os/osTimezone.h @@ -20,6 +20,1149 @@ extern "C" { #endif +/* PORT_TO_C89 means the code should work even if the underlying + compiler and library support only C89 plus C99's 'long long' + and perhaps a few other extensions to C89. + + This macro is obsolescent, and the plan is to remove it along with + associated code. A good time to do that might be in the year 2029 + because RHEL 7 (whose GCC defaults to C89) extended life cycle + support (ELS) is scheduled to end on 2028-06-30. */ +#ifndef PORT_TO_C89 +# define PORT_TO_C89 0 +#endif + +/* SUPPORT_C89 means the tzcode library should support C89 callers + in addition to the usual support for C99-and-later callers. + This defaults to 1 as POSIX requires, even though that can trigger + latent bugs in callers. */ +#ifndef SUPPORT_C89 +# define SUPPORT_C89 1 +#endif + +#ifndef __STDC_VERSION__ +# define __STDC_VERSION__ 0 +#endif + +/* Define true, false and bool if they don't work out of the box. */ +#if PORT_TO_C89 && __STDC_VERSION__ < 199901 +# define true 1 +# define false 0 +# define bool int +#elif __STDC_VERSION__ < 202311 +# include +#endif + +#if __STDC_VERSION__ < 202311 +# undef static_assert_tz +# define static_assert_tz(cond) extern int static_assert_check[(cond) ? 1 : -1] +#endif + +/* +** zdump has been made independent of the rest of the time +** conversion package to increase confidence in the verification it provides. +** You can use zdump to help in verifying other implementations. +** To do this, compile with -DUSE_LTZ=0 and link without the tz library. +*/ +#ifndef USE_LTZ +# define USE_LTZ 1 +#endif + +/* This string was in the Factory zone through version 2016f. */ +#define GRANDPARENTED "Local time zone must be set--see zic manual page" + +/* +** Defaults for preprocessor symbols. +** You can override these in your C compiler options, e.g. '-DHAVE_GETTEXT=1'. +*/ + +#if !defined HAVE__GENERIC && defined __has_extension +# if !__has_extension(c_generic_selections) +# define HAVE__GENERIC 0 +# endif +#endif +/* _Generic is buggy in pre-4.9 GCC. */ +#if !defined HAVE__GENERIC && defined __GNUC__ && !defined __STRICT_ANSI__ +# define HAVE__GENERIC (4 < __GNUC__ + (9 <= __GNUC_MINOR__)) +#endif +#ifndef HAVE__GENERIC +# define HAVE__GENERIC (201112 <= __STDC_VERSION__) +#endif + +#if !defined HAVE_GETTEXT && defined __has_include +# if __has_include() +# define HAVE_GETTEXT true +# endif +#endif +#ifndef HAVE_GETTEXT +# define HAVE_GETTEXT false +#endif + +#ifndef HAVE_INCOMPATIBLE_CTIME_R +# define HAVE_INCOMPATIBLE_CTIME_R 0 +#endif + +#ifndef HAVE_LINK +# define HAVE_LINK 1 +#endif /* !defined HAVE_LINK */ + +#ifndef HAVE_MALLOC_ERRNO +# define HAVE_MALLOC_ERRNO 1 +#endif + +#ifndef HAVE_POSIX_DECLS +# define HAVE_POSIX_DECLS 1 +#endif + +#ifndef HAVE_SETENV +# define HAVE_SETENV 1 +#endif + +#ifndef HAVE_STRDUP +# define HAVE_STRDUP 1 +#endif + +#ifndef HAVE_SYMLINK +# define HAVE_SYMLINK 1 +#endif /* !defined HAVE_SYMLINK */ + +#if !defined HAVE_SYS_STAT_H && defined __has_include +# if !__has_include() +# define HAVE_SYS_STAT_H false +# endif +#endif +#ifndef HAVE_SYS_STAT_H +# define HAVE_SYS_STAT_H true +#endif + +#if !defined HAVE_UNISTD_H && defined __has_include +# if !__has_include() +# define HAVE_UNISTD_H false +# endif +#endif +#ifndef HAVE_UNISTD_H +# define HAVE_UNISTD_H true +#endif + +#ifndef NETBSD_INSPIRED +# define NETBSD_INSPIRED 1 +#endif + +#if HAVE_INCOMPATIBLE_CTIME_R +# define asctime_r _incompatible_asctime_r +# define ctime_r _incompatible_ctime_r +#endif /* HAVE_INCOMPATIBLE_CTIME_R */ + +/* Enable tm_gmtoff, tm_zone, and environ on GNUish systems. */ +//#define _GNU_SOURCE 1 +/* Fix asctime_r on Solaris 11. */ +#define _POSIX_PTHREAD_SEMANTICS 1 +/* Enable strtoimax on pre-C99 Solaris 11. */ +#define __EXTENSIONS__ 1 + +/* On GNUish systems where time_t might be 32 or 64 bits, use 64. + On these platforms _FILE_OFFSET_BITS must also be 64; otherwise + setting _TIME_BITS to 64 does not work. The code does not + otherwise rely on _FILE_OFFSET_BITS being 64, since it does not + use off_t or functions like 'stat' that depend on off_t. */ +#ifndef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 +#endif +#if !defined _TIME_BITS && _FILE_OFFSET_BITS == 64 +# define _TIME_BITS 64 +#endif + +/* +** Nested includes +*/ + +/* Avoid clashes with NetBSD by renaming NetBSD's declarations. + If defining the 'timezone' variable, avoid a clash with FreeBSD's + 'timezone' function by renaming its declaration. */ +#define localtime_rz sys_localtime_rz +#define mktime_z sys_mktime_z +#define posix2time_z sys_posix2time_z +#define time2posix_z sys_time2posix_z +#if defined USG_COMPAT && USG_COMPAT == 2 +# define timezone sys_timezone +#endif +#define timezone_t sys_timezone_t +#define tzalloc sys_tzalloc +#define tzfree sys_tzfree +#include +#undef localtime_rz +#undef mktime_z +#undef posix2time_z +#undef time2posix_z +#if defined USG_COMPAT && USG_COMPAT == 2 +# undef timezone +#endif +#undef timezone_t +#undef tzalloc +#undef tzfree + +#include +#include +#if !PORT_TO_C89 +# include +#endif +#include /* for CHAR_BIT et al. */ +#include + +#include + +#ifndef EINVAL +# define EINVAL ERANGE +#endif + +#ifndef ELOOP +# define ELOOP EINVAL +#endif +#ifndef ENAMETOOLONG +# define ENAMETOOLONG EINVAL +#endif +#ifndef ENOMEM +# define ENOMEM EINVAL +#endif +#ifndef ENOTSUP +# define ENOTSUP EINVAL +#endif +#ifndef EOVERFLOW +# define EOVERFLOW EINVAL +#endif + +#if HAVE_GETTEXT +# include +#endif /* HAVE_GETTEXT */ + +#if HAVE_UNISTD_H +# include /* for R_OK, and other POSIX goodness */ +#endif /* HAVE_UNISTD_H */ + +/* SUPPORT_POSIX2008 means the tzcode library should support + POSIX.1-2017-and-earlier callers in addition to the usual support for + POSIX.1-2024-and-later callers; however, this can be + incompatible with POSIX.1-2024-and-later callers. + This macro is obsolescent, and the plan is to remove it + along with any code needed only when it is nonzero. + A good time to do that might be in the year 2034. + This macro's name is SUPPORT_POSIX2008 because _POSIX_VERSION == 200809 + in POSIX.1-2017, a minor revision of POSIX.1-2008. */ +#ifndef SUPPORT_POSIX2008 +# if defined _POSIX_VERSION && _POSIX_VERSION <= 200809 +# define SUPPORT_POSIX2008 1 +# else +# define SUPPORT_POSIX2008 0 +# endif +#endif + +#ifndef HAVE_DECL_ASCTIME_R +# if SUPPORT_POSIX2008 +# define HAVE_DECL_ASCTIME_R 1 +# else +# define HAVE_DECL_ASCTIME_R 0 +# endif +#endif + +#ifndef HAVE_STRFTIME_L +# if _POSIX_VERSION < 200809 +# define HAVE_STRFTIME_L 0 +# else +# define HAVE_STRFTIME_L 1 +# endif +#endif + +#ifndef USG_COMPAT +# ifndef _XOPEN_VERSION +# define USG_COMPAT 0 +# else +# define USG_COMPAT 1 +# endif +#endif + +#ifndef HAVE_TZNAME +# if _POSIX_VERSION < 198808 && !USG_COMPAT +# define HAVE_TZNAME 0 +# else +# define HAVE_TZNAME 1 +# endif +#endif + +#ifndef ALTZONE +# if defined __sun || defined _M_XENIX +# define ALTZONE 1 +# else +# define ALTZONE 0 +# endif +#endif + +#ifndef R_OK +# define R_OK 4 +#endif /* !defined R_OK */ + +#if PORT_TO_C89 + +/* +** Define HAVE_STDINT_H's default value here, rather than at the +** start, since __GLIBC__ and INTMAX_MAX's values depend on +** previously included files. glibc 2.1 and Solaris 10 and later have +** stdint.h, even with pre-C99 compilers. +*/ +#if !defined HAVE_STDINT_H && defined __has_include +# define HAVE_STDINT_H true /* C23 __has_include implies C99 stdint.h. */ +#endif +#ifndef HAVE_STDINT_H +# define HAVE_STDINT_H \ + (199901 <= __STDC_VERSION__ \ + || 2 < __GLIBC__ + (1 <= __GLIBC_MINOR__) \ + || __CYGWIN__ || INTMAX_MAX) +#endif /* !defined HAVE_STDINT_H */ + +#if HAVE_STDINT_H +# include +#endif /* !HAVE_STDINT_H */ + +#ifndef HAVE_INTTYPES_H +# define HAVE_INTTYPES_H HAVE_STDINT_H +#endif +#if HAVE_INTTYPES_H +# include +#endif + +/* Pre-C99 GCC compilers define __LONG_LONG_MAX__ instead of LLONG_MAX. */ +#if defined __LONG_LONG_MAX__ && !defined __STRICT_ANSI__ +# ifndef LLONG_MAX +# define LLONG_MAX __LONG_LONG_MAX__ +# endif +# ifndef LLONG_MIN +# define LLONG_MIN (-1 - LLONG_MAX) +# endif +# ifndef ULLONG_MAX +# define ULLONG_MAX (LLONG_MAX * 2ull + 1) +# endif +#endif + +#ifndef INT_FAST64_MAX +# if 1 <= LONG_MAX >> 31 >> 31 +typedef long int_fast64_t; +# define INT_FAST64_MIN LONG_MIN +# define INT_FAST64_MAX LONG_MAX +# else +/* If this fails, compile with -DHAVE_STDINT_H or with a better compiler. */ +typedef long long int_fast64_t; +# define INT_FAST64_MIN LLONG_MIN +# define INT_FAST64_MAX LLONG_MAX +# endif +#endif + +#ifndef PRIdFAST64 +# if INT_FAST64_MAX == LONG_MAX +# define PRIdFAST64 "ld" +# else +# define PRIdFAST64 "lld" +# endif +#endif + +#ifndef SCNdFAST64 +# define SCNdFAST64 PRIdFAST64 +#endif + +#ifndef INT_FAST32_MAX +# if INT_MAX >> 31 == 0 +typedef long int_fast32_t; +# define INT_FAST32_MAX LONG_MAX +# define INT_FAST32_MIN LONG_MIN +# else +typedef int int_fast32_t; +# define INT_FAST32_MAX INT_MAX +# define INT_FAST32_MIN INT_MIN +# endif +#endif + +#ifndef INTMAX_MAX +# ifdef LLONG_MAX +typedef long long intmax_t; +# ifndef HAVE_STRTOLL +# define HAVE_STRTOLL true +# endif +# if HAVE_STRTOLL +# define strtoimax strtoll +# endif +# define INTMAX_MAX LLONG_MAX +# define INTMAX_MIN LLONG_MIN +# else +typedef long intmax_t; +# define INTMAX_MAX LONG_MAX +# define INTMAX_MIN LONG_MIN +# endif +# ifndef strtoimax +# define strtoimax strtol +# endif +#endif + +#ifndef PRIdMAX +# if INTMAX_MAX == LLONG_MAX +# define PRIdMAX "lld" +# else +# define PRIdMAX "ld" +# endif +#endif + +#ifndef PTRDIFF_MAX +# define PTRDIFF_MAX MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t)) +#endif + +#ifndef UINT_FAST32_MAX +typedef unsigned long uint_fast32_t; +#endif + +#ifndef UINT_FAST64_MAX +# if 3 <= ULONG_MAX >> 31 >> 31 +typedef unsigned long uint_fast64_t; +# define UINT_FAST64_MAX ULONG_MAX +# else +/* If this fails, compile with -DHAVE_STDINT_H or with a better compiler. */ +typedef unsigned long long uint_fast64_t; +# define UINT_FAST64_MAX ULLONG_MAX +# endif +#endif + +#ifndef UINTMAX_MAX +# ifdef ULLONG_MAX +typedef unsigned long long uintmax_t; +# define UINTMAX_MAX ULLONG_MAX +# else +typedef unsigned long uintmax_t; +# define UINTMAX_MAX ULONG_MAX +# endif +#endif + +#ifndef PRIuMAX +# ifdef ULLONG_MAX +# define PRIuMAX "llu" +# else +# define PRIuMAX "lu" +# endif +#endif + +#ifndef SIZE_MAX +# define SIZE_MAX ((size_t) -1) +#endif + +#endif /* PORT_TO_C89 */ + +/* The maximum size of any created object, as a signed integer. + Although the C standard does not outright prohibit larger objects, + behavior is undefined if the result of pointer subtraction does not + fit into ptrdiff_t, and the code assumes in several places that + pointer subtraction works. As a practical matter it's OK to not + support objects larger than this. */ +#define INDEX_MAX ((ptrdiff_t) min_tz(PTRDIFF_MAX, SIZE_MAX)) + +/* Support ckd_add, ckd_sub, ckd_mul on C23 or recent-enough GCC-like + hosts, unless compiled with -DHAVE_STDCKDINT_H=0 or with pre-C23 EDG. */ +#if !defined HAVE_STDCKDINT_H && defined __has_include +# if __has_include() +# define HAVE_STDCKDINT_H true +# endif +#endif +#ifdef HAVE_STDCKDINT_H +# if HAVE_STDCKDINT_H +# include +# endif +#elif defined __EDG__ +/* Do nothing, to work around EDG bug . */ +#elif defined __has_builtin +# if __has_builtin(__builtin_add_overflow) +# define ckd_add(r, a, b) __builtin_add_overflow(a, b, r) +# endif +# if __has_builtin(__builtin_sub_overflow) +# define ckd_sub(r, a, b) __builtin_sub_overflow(a, b, r) +# endif +# if __has_builtin(__builtin_mul_overflow) +# define ckd_mul(r, a, b) __builtin_mul_overflow(a, b, r) +# endif +#elif 7 <= __GNUC__ +# define ckd_add(r, a, b) __builtin_add_overflow(a, b, r) +# define ckd_sub(r, a, b) __builtin_sub_overflow(a, b, r) +# define ckd_mul(r, a, b) __builtin_mul_overflow(a, b, r) +#endif + +#if (defined __has_c_attribute \ + && (202311 <= __STDC_VERSION__ || !defined __STRICT_ANSI__)) +# define HAVE___HAS_C_ATTRIBUTE true +#else +# define HAVE___HAS_C_ATTRIBUTE false +#endif + +#if HAVE___HAS_C_ATTRIBUTE +# if __has_c_attribute(deprecated) +# define ATTRIBUTE_DEPRECATED [[deprecated]] +# endif +#endif +#ifndef ATTRIBUTE_DEPRECATED +# if 3 < __GNUC__ + (2 <= __GNUC_MINOR__) +# define ATTRIBUTE_DEPRECATED __attribute__((deprecated)) +# else +# define ATTRIBUTE_DEPRECATED /* empty */ +# endif +#endif + +#if HAVE___HAS_C_ATTRIBUTE +# if __has_c_attribute(fallthrough) +# define ATTRIBUTE_FALLTHROUGH [[fallthrough]] +# endif +#endif +#ifndef ATTRIBUTE_FALLTHROUGH +# if 7 <= __GNUC__ +# define ATTRIBUTE_FALLTHROUGH __attribute__((fallthrough)) +# else +# define ATTRIBUTE_FALLTHROUGH ((void) 0) +# endif +#endif + +#if HAVE___HAS_C_ATTRIBUTE +# if __has_c_attribute(maybe_unused) +# define ATTRIBUTE_MAYBE_UNUSED [[maybe_unused]] +# endif +#endif +#ifndef ATTRIBUTE_MAYBE_UNUSED +# if 2 < __GNUC__ + (7 <= __GNUC_MINOR__) +# define ATTRIBUTE_MAYBE_UNUSED __attribute__((unused)) +# else +# define ATTRIBUTE_MAYBE_UNUSED /* empty */ +# endif +#endif + +#if HAVE___HAS_C_ATTRIBUTE +# if __has_c_attribute(noreturn) +# define ATTRIBUTE_NORETURN [[noreturn]] +# endif +#endif +#ifndef ATTRIBUTE_NORETURN +# if 201112 <= __STDC_VERSION__ +# define ATTRIBUTE_NORETURN _Noreturn +# elif 2 < __GNUC__ + (8 <= __GNUC_MINOR__) +# define ATTRIBUTE_NORETURN __attribute__((noreturn)) +# else +# define ATTRIBUTE_NORETURN /* empty */ +# endif +#endif + +#if HAVE___HAS_C_ATTRIBUTE +# if __has_c_attribute(reproducible) +# define ATTRIBUTE_REPRODUCIBLE [[reproducible]] +# endif +#endif +#ifndef ATTRIBUTE_REPRODUCIBLE +# define ATTRIBUTE_REPRODUCIBLE /* empty */ +#endif + +/* GCC attributes that are useful in tzcode. + __attribute__((pure)) is stricter than [[reproducible]], + so the latter is an adequate substitute in non-GCC C23 platforms. */ +#if __GNUC__ < 3 +# define ATTRIBUTE_FORMAT(spec) /* empty */ +# define ATTRIBUTE_PURE ATTRIBUTE_REPRODUCIBLE +#else +# define ATTRIBUTE_FORMAT(spec) __attribute__((format spec)) +# define ATTRIBUTE_PURE __attribute__((pure)) +#endif + +/* Avoid GCC bug 114833 . + Remove this macro and its uses when the bug is fixed in a GCC release, + because only the latest GCC matters for $(GCC_DEBUG_FLAGS). */ +#ifdef GCC_LINT +# define ATTRIBUTE_PURE_114833 ATTRIBUTE_PURE +#else +# define ATTRIBUTE_PURE_114833 /* empty */ +#endif + +#if (__STDC_VERSION__ < 199901 && !defined restrict \ + && (PORT_TO_C89 || defined _MSC_VER)) +# define restrict /* empty */ +#endif + +/* +** Workarounds for compilers/systems. +*/ + +#ifndef EPOCH_LOCAL +# define EPOCH_LOCAL 0 +#endif +#ifndef EPOCH_OFFSET +# define EPOCH_OFFSET 0 +#endif +#ifndef RESERVE_STD_EXT_IDS +# define RESERVE_STD_EXT_IDS 0 +#endif + +/* If standard C identifiers with external linkage (e.g., localtime) + are reserved and are not already being renamed anyway, rename them + as if compiling with '-Dtime_tz=time_t'. */ +#if !defined time_tz && RESERVE_STD_EXT_IDS && USE_LTZ +# define time_tz time_t +#endif + +/* +** Compile with -Dtime_tz=T to build the tz package with a private +** time_t type equivalent to T rather than the system-supplied time_t. +** This debugging feature can test unusual design decisions +** (e.g., time_t wider than 'long', or unsigned time_t) even on +** typical platforms. +*/ +#if defined time_tz || EPOCH_LOCAL || EPOCH_OFFSET != 0 +# define TZ_TIME_T 1 +#else +# define TZ_TIME_T 0 +#endif + +#if defined LOCALTIME_IMPLEMENTATION && TZ_TIME_T +static time_t sys_time(time_t *x) { return time(x); } +#endif + +#if TZ_TIME_T + +typedef time_tz tz_time_t; + +# undef asctime +# define asctime tz_asctime +# undef ctime +# define ctime tz_ctime +# undef difftime +# define difftime tz_difftime +# undef gmtime +# define gmtime tz_gmtime +# undef gmtime_r +# define gmtime_r tz_gmtime_r +# undef localtime +# define localtime tz_localtime +# undef localtime_r +# define localtime_r tz_localtime_r +# undef localtime_rz +# define localtime_rz tz_localtime_rz +# undef mktime +# define mktime tz_mktime +# undef mktime_z +# define mktime_z tz_mktime_z +# undef offtime +# define offtime tz_offtime +# undef posix2time +# define posix2time tz_posix2time +# undef posix2time_z +# define posix2time_z tz_posix2time_z +# undef strftime +# define strftime tz_strftime +# undef time +# define time tz_time +# undef time2posix +# define time2posix tz_time2posix +# undef time2posix_z +# define time2posix_z tz_time2posix_z +# undef time_t +# define time_t tz_time_t +# undef timegm +# define timegm tz_timegm +# undef timelocal +# define timelocal tz_timelocal +# undef timeoff +# define timeoff tz_timeoff +# undef tzalloc +# define tzalloc tz_tzalloc +# undef tzfree +# define tzfree tz_tzfree +# undef tzset +# define tzset tz_tzset +# if SUPPORT_POSIX2008 +# undef asctime_r +# define asctime_r tz_asctime_r +# undef ctime_r +# define ctime_r tz_ctime_r +# endif +# if HAVE_STRFTIME_L +# undef strftime_l +# define strftime_l tz_strftime_l +# endif +# if HAVE_TZNAME +# undef tzname +# define tzname tz_tzname +# endif +# if USG_COMPAT +# undef daylight +# define daylight tz_daylight +# undef timezone +# define timezone tz_timezone +# endif +# if ALTZONE +# undef altzone +# define altzone tz_altzone +# endif + +# if __STDC_VERSION__ < 202311 +# define DEPRECATED_IN_C23 /* empty */ +# else +# define DEPRECATED_IN_C23 ATTRIBUTE_DEPRECATED +# endif +DEPRECATED_IN_C23 char *asctime(struct tm const *); +DEPRECATED_IN_C23 char *ctime(time_t const *); +#if SUPPORT_POSIX2008 +char *asctime_r(struct tm const *restrict, char *restrict); +char *ctime_r(time_t const *, char *); +#endif +double difftime(time_t, time_t); +size_t strftime(char *restrict, size_t, char const *restrict, + struct tm const *restrict); +# if HAVE_STRFTIME_L +size_t strftime_l(char *restrict, size_t, char const *restrict, + struct tm const *restrict, locale_t); +# endif +struct tm *gmtime(time_t const *); +struct tm *gmtime_r(time_t const *restrict, struct tm *restrict); +struct tm *localtime(time_t const *); +struct tm *localtime_r(time_t const *restrict, struct tm *restrict); +time_t mktime(struct tm *); +time_t time(time_t *); +time_t timegm(struct tm *); +void tzset(void); +#endif + +#ifndef HAVE_DECL_TIMEGM +# if (202311 <= __STDC_VERSION__ \ + || defined __GLIBC__ || defined __tm_zone /* musl */ \ + || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \ + || (defined __APPLE__ && defined __MACH__)) +# define HAVE_DECL_TIMEGM true +# else +# define HAVE_DECL_TIMEGM false +# endif +#endif +#if !HAVE_DECL_TIMEGM && !defined timegm +time_t timegm(struct tm *); +#endif + +#if !HAVE_DECL_ASCTIME_R && !defined asctime_r && SUPPORT_POSIX2008 +extern char *asctime_r(struct tm const *restrict, char *restrict); +#endif + +#ifndef HAVE_DECL_ENVIRON +# if defined environ || defined __USE_GNU +# define HAVE_DECL_ENVIRON 1 +# else +# define HAVE_DECL_ENVIRON 0 +# endif +#endif + +#if !HAVE_DECL_ENVIRON +extern char **environ; +#endif + +#if 2 <= HAVE_TZNAME + (TZ_TIME_T || !HAVE_POSIX_DECLS) +extern char *tzname[]; +#endif +#if 2 <= USG_COMPAT + (TZ_TIME_T || !HAVE_POSIX_DECLS) +extern long timezone; +extern int daylight; +#endif +#if 2 <= ALTZONE + (TZ_TIME_T || !HAVE_POSIX_DECLS) +extern long altzone; +#endif + +/* +** The STD_INSPIRED functions are similar, but most also need +** declarations if time_tz is defined. +*/ + +#ifndef STD_INSPIRED +# define STD_INSPIRED 0 +#endif +#if STD_INSPIRED +# if TZ_TIME_T || !defined offtime +struct tm *offtime(time_t const *, long); +# endif +# if TZ_TIME_T || !defined timelocal +time_t timelocal(struct tm *); +# endif +# if TZ_TIME_T || !defined timeoff +# define EXTERN_TIMEOFF +# endif +# if TZ_TIME_T || !defined time2posix +time_t time2posix(time_t); +# endif +# if TZ_TIME_T || !defined posix2time +time_t posix2time(time_t); +# endif +#endif + +/* Infer TM_ZONE on systems where this information is known, but suppress + guessing if NO_TM_ZONE is defined. Similarly for TM_GMTOFF. */ +#if (200809 < _POSIX_VERSION \ + || defined __GLIBC__ \ + || defined __tm_zone /* musl */ \ + || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \ + || (defined __APPLE__ && defined __MACH__)) +# if !defined TM_GMTOFF && !defined NO_TM_GMTOFF +# define TM_GMTOFF tm_gmtoff +# endif +# if !defined TM_ZONE && !defined NO_TM_ZONE +# define TM_ZONE tm_zone +# endif +#endif + +/* +** Define functions that are ABI compatible with NetBSD but have +** better prototypes. NetBSD 6.1.4 defines a pointer type timezone_t +** and labors under the misconception that 'const timezone_t' is a +** pointer to a constant. This use of 'const' is ineffective, so it +** is not done here. What we call 'struct state' NetBSD calls +** 'struct __state', but this is a private name so it doesn't matter. +*/ +#if NETBSD_INSPIRED +typedef struct state *timezone_t; +struct tm *localtime_rz(timezone_t, time_t const *, + struct tm *); +time_t mktime_z(timezone_t, struct tm *); +timezone_t tzalloc(char const *); +void tzfree(timezone_t); +# if STD_INSPIRED +# if TZ_TIME_T || !defined posix2time_z +ATTRIBUTE_PURE time_t posix2time_z(timezone_t, time_t); +# endif +# if TZ_TIME_T || !defined time2posix_z +ATTRIBUTE_PURE time_t time2posix_z(timezone_t, time_t); +# endif +# endif +#endif + +/* +** Finally, some convenience items. +*/ + +#define TYPE_BIT(type) (CHAR_BIT * (ptrdiff_t) sizeof(type)) +#define TYPE_SIGNED(type) (((type) -1) < 0) +#define TWOS_COMPLEMENT(t) ((t) ~ (t) 0 < 0) + +/* Minimum and maximum of two values. Use lower case to avoid + naming clashes with standard include files. */ +#define max_tz(a, b) ((a) > (b) ? (a) : (b)) +#define min_tz(a, b) ((a) < (b) ? (a) : (b)) + +/* Max and min values of the integer type T, of which only the bottom + B bits are used, and where the highest-order used bit is considered + to be a sign bit if T is signed. */ +#define MAXVAL(t, b) \ + ((t) (((t) 1 << ((b) - 1 - TYPE_SIGNED(t))) \ + - 1 + ((t) 1 << ((b) - 1 - TYPE_SIGNED(t))))) +#define MINVAL(t, b) \ + ((t) (TYPE_SIGNED(t) ? - TWOS_COMPLEMENT(t) - MAXVAL(t, b) : 0)) + +/* The extreme time values, assuming no padding. */ +#define TIME_T_MIN_NO_PADDING MINVAL(time_t, TYPE_BIT(time_t)) +#define TIME_T_MAX_NO_PADDING MAXVAL(time_t, TYPE_BIT(time_t)) + +/* The extreme time values. These are macros, not constants, so that + any portability problems occur only when compiling .c files that use + the macros, which is safer for applications that need only zdump and zic. + This implementation assumes no padding if time_t is signed and + either the compiler lacks support for _Generic or time_t is not one + of the standard signed integer types. */ +#if HAVE__GENERIC +# define TIME_T_MIN \ + _Generic((time_t) 0, \ + signed char: SCHAR_MIN, short: SHRT_MIN, \ + int: INT_MIN, long: LONG_MIN, long long: LLONG_MIN, \ + default: TIME_T_MIN_NO_PADDING) +# define TIME_T_MAX \ + (TYPE_SIGNED(time_t) \ + ? _Generic((time_t) 0, \ + signed char: SCHAR_MAX, short: SHRT_MAX, \ + int: INT_MAX, long: LONG_MAX, long long: LLONG_MAX, \ + default: TIME_T_MAX_NO_PADDING) \ + : (time_t) -1) +enum { SIGNED_PADDING_CHECK_NEEDED + = _Generic((time_t) 0, + signed char: false, short: false, + int: false, long: false, long long: false, + default: true) }; +#else +# define TIME_T_MIN TIME_T_MIN_NO_PADDING +# define TIME_T_MAX TIME_T_MAX_NO_PADDING +enum { SIGNED_PADDING_CHECK_NEEDED = true }; +#endif +/* Try to check the padding assumptions. Although TIME_T_MAX and the + following check can both have undefined behavior on oddball + platforms due to shifts exceeding widths of signed integers, these + platforms' compilers are likely to diagnose these issues in integer + constant expressions, so it shouldn't hurt to check statically. */ +static_assert_tz(! TYPE_SIGNED(time_t) || ! SIGNED_PADDING_CHECK_NEEDED + || TIME_T_MAX >> (TYPE_BIT(time_t) - 2) == 1); + +/* +** 302 / 1000 is log10(2.0) rounded up. +** Subtract one for the sign bit if the type is signed; +** add one for integer division truncation; +** add one more for a minus sign if the type is signed. +*/ +#define INT_STRLEN_MAXIMUM(type) \ + ((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + \ + 1 + TYPE_SIGNED(type)) + +/* +** INITIALIZE(x) +*/ + +#ifdef GCC_LINT +# define INITIALIZE(x) ((x) = 0) +#else +# define INITIALIZE(x) +#endif + +/* Whether memory access must strictly follow the C standard. + If 0, it's OK to read uninitialized storage so long as the value is + not relied upon. Defining it to 0 lets mktime access parts of + struct tm that might be uninitialized, as a heuristic when the + standard doesn't say what to return and when tm_gmtoff can help + mktime likely infer a better value. */ +#ifndef UNINIT_TRAP +# define UNINIT_TRAP 0 +#endif + +/* localtime.c sometimes needs access to timeoff if it is not already public. + tz_private_timeoff should be used only by localtime.c. */ +#if (!defined EXTERN_TIMEOFF \ + && defined TM_GMTOFF && (200809 < _POSIX_VERSION || ! UNINIT_TRAP)) +# ifndef timeoff +# define timeoff tz_private_timeoff +# endif +# define EXTERN_TIMEOFF +#endif +#ifdef EXTERN_TIMEOFF +time_t timeoff(struct tm *, long); +#endif + +#ifdef DEBUG +# undef unreachable +# define unreachable() abort() +#elif !defined unreachable +# ifdef __has_builtin +# if __has_builtin(__builtin_unreachable) +# define unreachable() __builtin_unreachable() +# endif +# elif 4 < __GNUC__ + (5 <= __GNUC_MINOR__) +# define unreachable() __builtin_unreachable() +# endif +# ifndef unreachable +# define unreachable() ((void) 0) +# endif +#endif + +/* +** For the benefit of GNU folk... +** '_(MSGID)' uses the current locale's message library string for MSGID. +** The default is to use gettext if available, and use MSGID otherwise. +*/ + +#if HAVE_GETTEXT +#define _(msgid) gettext(msgid) +#else /* !HAVE_GETTEXT */ +#define _(msgid) msgid +#endif /* !HAVE_GETTEXT */ + +#if !defined TZ_DOMAIN && defined HAVE_GETTEXT +# define TZ_DOMAIN "tz" +#endif + +#if HAVE_INCOMPATIBLE_CTIME_R +#undef asctime_r +#undef ctime_r +char *asctime_r(struct tm const *restrict, char *restrict); +char *ctime_r(time_t const *, char *); +#endif /* HAVE_INCOMPATIBLE_CTIME_R */ + +/* Handy macros that are independent of tzfile implementation. */ + +enum { + SECSPERMIN = 60, + MINSPERHOUR = 60, + SECSPERHOUR = SECSPERMIN * MINSPERHOUR, + HOURSPERDAY = 24, + DAYSPERWEEK = 7, + DAYSPERNYEAR = 365, + DAYSPERLYEAR = DAYSPERNYEAR + 1, + MONSPERYEAR = 12, + YEARSPERREPEAT = 400 /* years before a Gregorian repeat */ +}; + +#define SECSPERDAY ((int_fast32_t) SECSPERHOUR * HOURSPERDAY) + +#define DAYSPERREPEAT ((int_fast32_t) 400 * 365 + 100 - 4 + 1) +#define SECSPERREPEAT ((int_fast64_t) DAYSPERREPEAT * SECSPERDAY) +#define AVGSECSPERYEAR (SECSPERREPEAT / YEARSPERREPEAT) + +/* How many years to generate (in zic.c) or search through (in localtime.c). + This is two years larger than the obvious 400, to avoid edge cases. + E.g., suppose a rule applies from 2012 on with transitions + in March and September, plus one-off transitions in November 2013, + and suppose the rule cannot be expressed as a proleptic TZ string. + If zic looked only at the last 400 years, it would set max_year=2413, + with the intent that the 400 years 2014 through 2413 will be repeated. + The last transition listed in the tzfile would be in 2413-09, + less than 400 years after the last one-off transition in 2013-11. + Two years is not overkill for localtime.c, as a one-year bump + would mishandle 2023d's America/Ciudad_Juarez for November 2422. */ +enum { years_of_observations = YEARSPERREPEAT + 2 }; + +enum { + TM_SUNDAY, + TM_MONDAY, + TM_TUESDAY, + TM_WEDNESDAY, + TM_THURSDAY, + TM_FRIDAY, + TM_SATURDAY +}; + +enum { + TM_JANUARY, + TM_FEBRUARY, + TM_MARCH, + TM_APRIL, + TM_MAY, + TM_JUNE, + TM_JULY, + TM_AUGUST, + TM_SEPTEMBER, + TM_OCTOBER, + TM_NOVEMBER, + TM_DECEMBER +}; + +enum { + TM_YEAR_BASE = 1900, + TM_WDAY_BASE = TM_MONDAY, + EPOCH_YEAR = 1970, + EPOCH_WDAY = TM_THURSDAY +}; + +#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) + +/* +** Since everything in isleap is modulo 400 (or a factor of 400), we know that +** isleap(y) == isleap(y % 400) +** and so +** isleap(a + b) == isleap((a + b) % 400) +** or +** isleap(a + b) == isleap(a % 400 + b % 400) +** This is true even if % means modulo rather than Fortran remainder +** (which is allowed by C89 but not by C99 or later). +** We use this to avoid addition overflow problems. +*/ + +#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400) + +#ifndef TZDEFRULES +# define TZDEFRULES "posixrules" +#endif /* !defined TZDEFRULES */ + + +/* See Internet RFC 8536 for more details about the following format. */ + +/* +** Each file begins with. . . +*/ + +#define TZ_MAGIC "TZif" + +struct tzhead { + char tzh_magic[4]; /* TZ_MAGIC */ + char tzh_version[1]; /* '\0' or '2'-'4' as of 2021 */ + char tzh_reserved[15]; /* reserved; must be zero */ + char tzh_ttisutcnt[4]; /* coded number of trans. time flags */ + char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */ + char tzh_leapcnt[4]; /* coded number of leap seconds */ + char tzh_timecnt[4]; /* coded number of transition times */ + char tzh_typecnt[4]; /* coded number of local time types */ + char tzh_charcnt[4]; /* coded number of abbr. chars */ +}; + +/* +** . . .followed by. . . +** +** tzh_timecnt (char [4])s coded transition times a la time(2) +** tzh_timecnt (unsigned char)s types of local time starting at above +** tzh_typecnt repetitions of +** one (char [4]) coded UT offset in seconds +** one (unsigned char) used to set tm_isdst +** one (unsigned char) that's an abbreviation list index +** tzh_charcnt (char)s '\0'-terminated zone abbreviations +** tzh_leapcnt repetitions of +** one (char [4]) coded leap second transition times +** one (char [4]) total correction after above +** tzh_ttisstdcnt (char)s indexed by type; if 1, transition +** time is standard time, if 0, +** transition time is local (wall clock) +** time; if absent, transition times are +** assumed to be local time +** tzh_ttisutcnt (char)s indexed by type; if 1, transition +** time is UT, if 0, transition time is +** local time; if absent, transition +** times are assumed to be local time. +** When this is 1, the corresponding +** std/wall indicator must also be 1. +*/ + +/* +** If tzh_version is '2' or greater, the above is followed by a second instance +** of tzhead and a second instance of the data in which each coded transition +** time uses 8 rather than 4 chars, +** then a POSIX.1-2017 proleptic TZ string for use in handling +** instants after the last transition time stored in the file +** (with nothing between the newlines if there is no POSIX.1-2017 +** representation for such instants). +** +** If tz_version is '3' or greater, the TZ string can be any POSIX.1-2024 +** proleptic TZ string, which means the above is extended as follows. +** First, the TZ string's hour offset may range from -167 +** through 167 as compared to the range 0 through 24 required +** by POSIX.1-2017 and earlier. +** Second, its DST start time may be January 1 at 00:00 and its stop +** time December 31 at 24:00 plus the difference between DST and +** standard time, indicating DST all year. +*/ + +/* +** In the current implementation, "tzset()" refuses to deal with files that +** exceed any of the limits below. +*/ + +#ifndef TZ_MAX_TIMES +/* This must be at least 242 for Europe/London with 'zic -b fat'. */ +# define TZ_MAX_TIMES 2000 +#endif /* !defined TZ_MAX_TIMES */ + +#ifndef TZ_MAX_TYPES +/* This must be at least 18 for Europe/Vilnius with 'zic -b fat'. */ +# define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */ +#endif /* !defined TZ_MAX_TYPES */ + +#ifndef TZ_MAX_CHARS +/* This must be at least 40 for America/Anchorage. */ +# define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */ +/* (limited by what unsigned chars can hold) */ +#endif /* !defined TZ_MAX_CHARS */ + +#ifndef TZ_MAX_LEAPS +/* This must be at least 27 for leap seconds from 1972 through mid-2023. + There's a plan to discontinue leap seconds by 2035. */ +# define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */ +#endif /* !defined TZ_MAX_LEAPS */ + +#ifndef TZDEFAULT +# define TZDEFAULT "/etc/localtime" /* default zone */ +#endif +#ifndef TZDIR +# define TZDIR "/usr/local/share/timezone" /* TZif directory */ +#endif + // If the error is in a third-party library, place this header file under the third-party library header file. // When you want to use this feature, you should find or add the same function in the following section. #ifndef ALLOW_FORBID_FUNC diff --git a/source/client/inc/clientInt.h b/source/client/inc/clientInt.h index 114bc00125..e651d5b37e 100644 --- a/source/client/inc/clientInt.h +++ b/source/client/inc/clientInt.h @@ -148,6 +148,13 @@ typedef struct { __taos_notify_fn_t fp; } SWhiteListInfo; +typedef struct { +// TIMEZONE *tz; + char charset[TD_LOCALE_LEN]; + char app[TSDB_APP_NAME_LEN]; + uint32_t ip; +}optionInfo; + typedef struct STscObj { char user[TSDB_USER_LEN]; char pass[TSDB_PASSWORD_LEN]; diff --git a/source/client/src/clientEnv.c b/source/client/src/clientEnv.c index f892575f0a..43b7382645 100644 --- a/source/client/src/clientEnv.c +++ b/source/client/src/clientEnv.c @@ -953,7 +953,6 @@ void taos_init_imp(void) { return; } taosHashSetFreeFp(appInfo.pInstMap, destroyAppInst); - deltaToUtcInitOnce(); char logDirName[64] = {0}; #ifdef CUS_PROMPT diff --git a/source/common/src/ttime.c b/source/common/src/ttime.c index 75624593d9..638f0411b7 100644 --- a/source/common/src/ttime.c +++ b/source/common/src/ttime.c @@ -25,18 +25,6 @@ #include "tlog.h" -// ==== mktime() kernel code =================// -static int64_t m_deltaUtc = 0; - -void deltaToUtcInitOnce() { - struct tm tm = {0}; - if (taosStrpTime("1970-01-01 00:00:00", (const char*)("%Y-%m-%d %H:%M:%S"), &tm) != 0) { - uError("failed to parse time string"); - } - m_deltaUtc = (int64_t)taosMktime(&tm); - // printf("====delta:%lld\n\n", seconds); -} - static int32_t parseFraction(char* str, char** end, int32_t timePrec, int64_t* pFraction); static int32_t parseTimeWithTz(const char* timestr, int64_t* time, int32_t timePrec, char delim); static int32_t parseLocaltime(char* timestr, int32_t len, int64_t* utime, int32_t timePrec, char delim); @@ -374,7 +362,7 @@ int32_t parseLocaltimeDst(char* timestr, int32_t len, int64_t* utime, int32_t ti } /* mktime will be affected by TZ, set by using taos_options */ - int64_t seconds = taosMktime(&tm); + int64_t seconds = taosMktime(&tm); //tztodo int64_t fraction = 0; if (*str == '.') { @@ -903,56 +891,6 @@ int64_t taosTimeGetIntervalEnd(int64_t intervalStart, const SInterval* pInterval return result; } } -// internal function, when program is paused in debugger, -// one can call this function from debugger to print a -// timestamp as human readable string, for example (gdb): -// p fmtts(1593769722) -// outputs: -// 2020-07-03 17:48:42 -// and the parameter can also be a variable. -const char* fmtts(int64_t ts) { - static char buf[TD_TIME_STR_LEN] = {0}; - size_t pos = 0; - struct tm tm; - - if (ts > -62135625943 && ts < 32503651200) { - time_t t = (time_t)ts; - if (taosLocalTime(&t, &tm, buf, sizeof(buf)) == NULL) { - return buf; - } - pos += strftime(buf + pos, sizeof(buf), "s=%Y-%m-%d %H:%M:%S", &tm); - } - - if (ts > -62135625943000 && ts < 32503651200000) { - time_t t = (time_t)(ts / 1000); - if (taosLocalTime(&t, &tm, buf, sizeof(buf)) == NULL) { - return buf; - } - if (pos > 0) { - buf[pos++] = ' '; - buf[pos++] = '|'; - buf[pos++] = ' '; - } - pos += strftime(buf + pos, sizeof(buf), "ms=%Y-%m-%d %H:%M:%S", &tm); - pos += sprintf(buf + pos, ".%03d", (int32_t)(ts % 1000)); - } - - { - time_t t = (time_t)(ts / 1000000); - if (taosLocalTime(&t, &tm, buf, sizeof(buf)) == NULL) { - return buf; - } - if (pos > 0) { - buf[pos++] = ' '; - buf[pos++] = '|'; - buf[pos++] = ' '; - } - pos += strftime(buf + pos, sizeof(buf), "us=%Y-%m-%d %H:%M:%S", &tm); - pos += sprintf(buf + pos, ".%06d", (int32_t)(ts % 1000000)); - } - - return buf; -} int32_t taosFormatUtcTime(char* buf, int32_t bufLen, int64_t t, int32_t precision) { char ts[40] = {0}; @@ -1007,14 +945,14 @@ int32_t taosFormatUtcTime(char* buf, int32_t bufLen, int64_t t, int32_t precisio int32_t taosTs2Tm(int64_t ts, int32_t precision, struct STm* tm) { tm->fsec = ts % TICK_PER_SECOND[precision] * (TICK_PER_SECOND[TSDB_TIME_PRECISION_NANO] / TICK_PER_SECOND[precision]); time_t t = ts / TICK_PER_SECOND[precision]; - if (NULL == taosLocalTime(&t, &tm->tm, NULL, 0)) { + if (NULL == taosLocalTime(&t, &tm->tm, NULL, 0)) { //tztodo TAOS_RETURN(TAOS_SYSTEM_ERROR(errno)); } return TSDB_CODE_SUCCESS; } int32_t taosTm2Ts(struct STm* tm, int64_t* ts, int32_t precision) { - *ts = taosMktime(&tm->tm); + *ts = taosMktime(&tm->tm); //tztodo *ts *= TICK_PER_SECOND[precision]; *ts += tm->fsec / (TICK_PER_SECOND[TSDB_TIME_PRECISION_NANO] / TICK_PER_SECOND[precision]); return TSDB_CODE_SUCCESS; diff --git a/source/os/src/osTimezone.c b/source/os/src/osTimezone.c index 89c7ce9e31..f9a9dac85e 100644 --- a/source/os/src/osTimezone.c +++ b/source/os/src/osTimezone.c @@ -1053,3 +1053,2269 @@ int32_t taosGetSystemTimezone(char *outTimezoneStr, enum TdTimezone *tsTimezone) return 0; #endif } + +#if defined THREAD_SAFE && THREAD_SAFE +# include +static pthread_mutex_t locallock = PTHREAD_MUTEX_INITIALIZER; +static int lock(void) { return pthread_mutex_lock(&locallock); } +static void unlock(void) { pthread_mutex_unlock(&locallock); } +#else +static int lock(void) { return 0; } +static void unlock(void) { } +#endif + +#ifndef TZ_ABBR_CHAR_SET +# define TZ_ABBR_CHAR_SET \ + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 :+-._" +#endif /* !defined TZ_ABBR_CHAR_SET */ + +#ifndef TZ_ABBR_ERR_CHAR +# define TZ_ABBR_ERR_CHAR '_' +#endif /* !defined TZ_ABBR_ERR_CHAR */ + +/* +** Support non-POSIX platforms that distinguish between text and binary files. +*/ + +#ifndef O_BINARY +# define O_BINARY 0 +#endif + +#ifndef WILDABBR +/* +** Someone might make incorrect use of a time zone abbreviation: +** 1. They might reference tzname[0] before calling tzset (explicitly +** or implicitly). +** 2. They might reference tzname[1] before calling tzset (explicitly +** or implicitly). +** 3. They might reference tzname[1] after setting to a time zone +** in which Daylight Saving Time is never observed. +** 4. They might reference tzname[0] after setting to a time zone +** in which Standard Time is never observed. +** 5. They might reference tm.TM_ZONE after calling offtime. +** What's best to do in the above cases is open to debate; +** for now, we just set things up so that in any of the five cases +** WILDABBR is used. Another possibility: initialize tzname[0] to the +** string "tzname[0] used before set", and similarly for the other cases. +** And another: initialize tzname[0] to "ERA", with an explanation in the +** manual page of what this "time zone abbreviation" means (doing this so +** that tzname[0] has the "normal" length of three characters). +*/ +# define WILDABBR " " +#endif /* !defined WILDABBR */ + +static const char wildabbr[] = WILDABBR; + +static char const etc_utc[] = "Etc/UTC"; +static char const *utc = etc_utc + sizeof "Etc/" - 1; + +/* +** The DST rules to use if TZ has no rules and we can't load TZDEFRULES. +** Default to US rules as of 2017-05-07. +** POSIX does not specify the default DST rules; +** for historical reasons, US rules are a common default. +*/ +#ifndef TZDEFRULESTRING +# define TZDEFRULESTRING ",M3.2.0,M11.1.0" +#endif + +struct ttinfo { /* time type information */ + int_fast32_t tt_utoff; /* UT offset in seconds */ + bool tt_isdst; /* used to set tm_isdst */ + int tt_desigidx; /* abbreviation list index */ + bool tt_ttisstd; /* transition is std time */ + bool tt_ttisut; /* transition is UT */ +}; + +struct lsinfo { /* leap second information */ + time_t ls_trans; /* transition time */ + int_fast32_t ls_corr; /* correction to apply */ +}; + +/* This abbreviation means local time is unspecified. */ +static char const UNSPEC[] = "-00"; + +/* How many extra bytes are needed at the end of struct state's chars array. + This needs to be at least 1 for null termination in case the input + data isn't properly terminated, and it also needs to be big enough + for ttunspecified to work without crashing. */ +enum { CHARS_EXTRA = max_tz(sizeof UNSPEC, 2) - 1 }; + +/* Limit to time zone abbreviation length in proleptic TZ strings. + This is distinct from TZ_MAX_CHARS, which limits TZif file contents. */ +#ifndef TZNAME_MAXIMUM +# define TZNAME_MAXIMUM 255 +#endif + +/* A representation of the contents of a TZif file. Ideally this + would have no size limits; the following sizes should suffice for + practical use. This struct should not be too large, as instances + are put on the stack and stacks are relatively small on some platforms. + See tzfile.h for more about the sizes. */ +struct state { + int leapcnt; + int timecnt; + int typecnt; + int charcnt; + bool goback; + bool goahead; + time_t ats[TZ_MAX_TIMES]; + unsigned char types[TZ_MAX_TIMES]; + struct ttinfo ttis[TZ_MAX_TYPES]; + char chars[max_tz(max_tz(TZ_MAX_CHARS + CHARS_EXTRA, sizeof "UTC"), + 2 * (TZNAME_MAXIMUM + 1))]; + struct lsinfo lsis[TZ_MAX_LEAPS]; +}; + +enum r_type { + JULIAN_DAY, /* Jn = Julian day */ + DAY_OF_YEAR, /* n = day of year */ + MONTH_NTH_DAY_OF_WEEK /* Mm.n.d = month, week, day of week */ +}; + +struct rule { + enum r_type r_type; /* type of rule */ + int r_day; /* day number of rule */ + int r_week; /* week number of rule */ + int r_mon; /* month number of rule */ + int_fast32_t r_time; /* transition time of rule */ +}; + +static struct tm *gmtsub(struct state const *, time_t const *, int_fast32_t, + struct tm *); +static bool increment_overflow(int *, int); +static bool increment_overflow_time(time_t *, int_fast32_t); +static int_fast32_t leapcorr(struct state const *, time_t); +static bool normalize_overflow32(int_fast32_t *, int *, int); +static struct tm *timesub(time_t const *, int_fast32_t, struct state const *, + struct tm *); +static bool tzparse(char const *, struct state *, struct state const *); + +#ifdef ALL_STATE +static struct state * lclptr; +static struct state * gmtptr; +#endif /* defined ALL_STATE */ + +#ifndef ALL_STATE +static struct state lclmem; +static struct state gmtmem; +static struct state *const lclptr = &lclmem; +static struct state *const gmtptr = &gmtmem; +#endif /* State Farm */ + +#ifndef TZ_STRLEN_MAX +# define TZ_STRLEN_MAX 255 +#endif /* !defined TZ_STRLEN_MAX */ + +static char lcl_TZname[TZ_STRLEN_MAX + 1]; +static int lcl_is_set; + +/* +** Section 4.12.3 of X3.159-1989 requires that +** Except for the strftime function, these functions [asctime, +** ctime, gmtime, localtime] return values in one of two static +** objects: a broken-down time structure and an array of char. +** Thanks to Paul Eggert for noting this. +** +** Although this requirement was removed in C99 it is still present in POSIX. +** Follow the requirement if SUPPORT_C89, even though this is more likely to +** trigger latent bugs in programs. +*/ + +#if SUPPORT_C89 +static struct tm tm; +#endif + +#if 2 <= HAVE_TZNAME + TZ_TIME_T +char * tzname[2] = { + (char *) wildabbr, + (char *) wildabbr +}; +#endif +#if 2 <= USG_COMPAT + TZ_TIME_T +long timezone; +int daylight; +#endif +#if 2 <= ALTZONE + TZ_TIME_T +long altzone; +#endif + +/* Initialize *S to a value based on UTOFF, ISDST, and DESIGIDX. */ +static void +init_ttinfo(struct ttinfo *s, int_fast32_t utoff, bool isdst, int desigidx) +{ + s->tt_utoff = utoff; + s->tt_isdst = isdst; + s->tt_desigidx = desigidx; + s->tt_ttisstd = false; + s->tt_ttisut = false; +} + +/* Return true if SP's time type I does not specify local time. */ +static bool +ttunspecified(struct state const *sp, int i) +{ + char const *abbr = &sp->chars[sp->ttis[i].tt_desigidx]; + /* memcmp is likely faster than strcmp, and is safe due to CHARS_EXTRA. */ + return memcmp(abbr, UNSPEC, sizeof UNSPEC) == 0; +} + +static int_fast32_t +detzcode(const char *const codep) +{ + register int_fast32_t result; + register int i; + int_fast32_t one = 1; + int_fast32_t halfmaxval = one << (32 - 2); + int_fast32_t maxval = halfmaxval - 1 + halfmaxval; + int_fast32_t minval = -1 - maxval; + + result = codep[0] & 0x7f; + for (i = 1; i < 4; ++i) + result = (result << 8) | (codep[i] & 0xff); + + if (codep[0] & 0x80) { + /* Do two's-complement negation even on non-two's-complement machines. + If the result would be minval - 1, return minval. */ + result -= !TWOS_COMPLEMENT(int_fast32_t) && result != 0; + result += minval; + } + return result; +} + +static int_fast64_t +detzcode64(const char *const codep) +{ + register int_fast64_t result; + register int i; + int_fast64_t one = 1; + int_fast64_t halfmaxval = one << (64 - 2); + int_fast64_t maxval = halfmaxval - 1 + halfmaxval; + int_fast64_t minval = -TWOS_COMPLEMENT(int_fast64_t) - maxval; + + result = codep[0] & 0x7f; + for (i = 1; i < 8; ++i) + result = (result << 8) | (codep[i] & 0xff); + + if (codep[0] & 0x80) { + /* Do two's-complement negation even on non-two's-complement machines. + If the result would be minval - 1, return minval. */ + result -= !TWOS_COMPLEMENT(int_fast64_t) && result != 0; + result += minval; + } + return result; +} + +static void +update_tzname_etc(struct state const *sp, struct ttinfo const *ttisp) +{ +#if HAVE_TZNAME + tzname[ttisp->tt_isdst] = (char *) &sp->chars[ttisp->tt_desigidx]; +#endif +#if USG_COMPAT + if (!ttisp->tt_isdst) + timezone = - ttisp->tt_utoff; +#endif +#if ALTZONE + if (ttisp->tt_isdst) + altzone = - ttisp->tt_utoff; +#endif +} + +/* If STDDST_MASK indicates that SP's TYPE provides useful info, + update tzname, timezone, and/or altzone and return STDDST_MASK, + diminished by the provided info if it is a specified local time. + Otherwise, return STDDST_MASK. See settzname for STDDST_MASK. */ +static int +may_update_tzname_etc(int stddst_mask, struct state *sp, int type) +{ + struct ttinfo *ttisp = &sp->ttis[type]; + int this_bit = 1 << ttisp->tt_isdst; + if (stddst_mask & this_bit) { + update_tzname_etc(sp, ttisp); + if (!ttunspecified(sp, type)) + return stddst_mask & ~this_bit; + } + return stddst_mask; +} + +static void +settzname(void) +{ + register struct state * const sp = lclptr; + register int i; + + /* If STDDST_MASK & 1 we need info about a standard time. + If STDDST_MASK & 2 we need info about a daylight saving time. + When STDDST_MASK becomes zero we can stop looking. */ + int stddst_mask = 0; + +#if HAVE_TZNAME + tzname[0] = tzname[1] = (char *) (sp ? wildabbr : utc); + stddst_mask = 3; +#endif +#if USG_COMPAT + timezone = 0; + stddst_mask = 3; +#endif +#if ALTZONE + altzone = 0; + stddst_mask |= 2; +#endif + /* + ** And to get the latest time zone abbreviations into tzname. . . + */ + if (sp) { + for (i = sp->timecnt - 1; stddst_mask && 0 <= i; i--) + stddst_mask = may_update_tzname_etc(stddst_mask, sp, sp->types[i]); + for (i = sp->typecnt - 1; stddst_mask && 0 <= i; i--) + stddst_mask = may_update_tzname_etc(stddst_mask, sp, i); + } +#if USG_COMPAT + daylight = stddst_mask >> 1 ^ 1; +#endif +} + +/* Replace bogus characters in time zone abbreviations. + Return 0 on success, an errno value if a time zone abbreviation is + too long. */ +static int +scrub_abbrs(struct state *sp) +{ + int i; + + /* Reject overlong abbreviations. */ + for (i = 0; i < sp->charcnt - (TZNAME_MAXIMUM + 1); ) { + int len = strlen(&sp->chars[i]); + if (TZNAME_MAXIMUM < len) + return EOVERFLOW; + i += len + 1; + } + + /* Replace bogus characters. */ + for (i = 0; i < sp->charcnt; ++i) + if (strchr(TZ_ABBR_CHAR_SET, sp->chars[i]) == NULL) + sp->chars[i] = TZ_ABBR_ERR_CHAR; + + return 0; +} + +/* Input buffer for data read from a compiled tz file. */ +union input_buffer { + /* The first part of the buffer, interpreted as a header. */ + struct tzhead tzhead; + + /* The entire buffer. Ideally this would have no size limits; + the following should suffice for practical use. */ + char buf[2 * sizeof(struct tzhead) + 2 * sizeof(struct state) + + 4 * TZ_MAX_TIMES]; +}; + +/* TZDIR with a trailing '/' rather than a trailing '\0'. */ +static char const tzdirslash[sizeof TZDIR] = TZDIR "/"; + +/* Local storage needed for 'tzloadbody'. */ +union local_storage { + /* The results of analyzing the file's contents after it is opened. */ + struct file_analysis { + /* The input buffer. */ + union input_buffer u; + + /* A temporary state used for parsing a TZ string in the file. */ + struct state st; + } u; + + /* The name of the file to be opened. Ideally this would have no + size limits, to support arbitrarily long Zone names. + Limiting Zone names to 1024 bytes should suffice for practical use. + However, there is no need for this to be smaller than struct + file_analysis as that struct is allocated anyway, as the other + union member. */ + char fullname[max_tz(sizeof(struct file_analysis), sizeof tzdirslash + 1024)]; +}; + +/* Load tz data from the file named NAME into *SP. Read extended + format if DOEXTEND. Use *LSP for temporary storage. Return 0 on + success, an errno value on failure. */ +static int +tzloadbody(char const *name, struct state *sp, bool doextend, + union local_storage *lsp) +{ + register int i; + register int fid; + register int stored; + register ssize_t nread; + register bool doaccess; + register union input_buffer *up = &lsp->u.u; + register int tzheadsize = sizeof(struct tzhead); + + sp->goback = sp->goahead = false; + + if (! name) { + name = TZDEFAULT; + if (! name) + return EINVAL; + } + + if (name[0] == ':') + ++name; +#ifdef SUPPRESS_TZDIR + /* Do not prepend TZDIR. This is intended for specialized + applications only, due to its security implications. */ + doaccess = true; +#else + doaccess = name[0] == '/'; +#endif + if (!doaccess) { + char const *dot; + if (sizeof lsp->fullname - sizeof tzdirslash <= strlen(name)) + return ENAMETOOLONG; + + /* Create a string "TZDIR/NAME". Using sprintf here + would pull in stdio (and would fail if the + resulting string length exceeded INT_MAX!). */ + memcpy(lsp->fullname, tzdirslash, sizeof tzdirslash); + strcpy(lsp->fullname + sizeof tzdirslash, name); + + /* Set doaccess if NAME contains a ".." file name + component, as such a name could read a file outside + the TZDIR virtual subtree. */ + for (dot = name; (dot = strchr(dot, '.')); dot++) + if ((dot == name || dot[-1] == '/') && dot[1] == '.' + && (dot[2] == '/' || !dot[2])) { + doaccess = true; + break; + } + + name = lsp->fullname; + } + if (doaccess && access(name, R_OK) != 0) + return errno; + fid = open(name, O_RDONLY | O_BINARY); + if (fid < 0) + return errno; + + nread = read(fid, up->buf, sizeof up->buf); + if (nread < tzheadsize) { + int err = nread < 0 ? errno : EINVAL; + close(fid); + return err; + } + if (close(fid) < 0) + return errno; + for (stored = 4; stored <= 8; stored *= 2) { + char version = up->tzhead.tzh_version[0]; + bool skip_datablock = stored == 4 && version; + int_fast32_t datablock_size; + int_fast32_t ttisstdcnt = detzcode(up->tzhead.tzh_ttisstdcnt); + int_fast32_t ttisutcnt = detzcode(up->tzhead.tzh_ttisutcnt); + int_fast64_t prevtr = -1; + int_fast32_t prevcorr; + int_fast32_t leapcnt = detzcode(up->tzhead.tzh_leapcnt); + int_fast32_t timecnt = detzcode(up->tzhead.tzh_timecnt); + int_fast32_t typecnt = detzcode(up->tzhead.tzh_typecnt); + int_fast32_t charcnt = detzcode(up->tzhead.tzh_charcnt); + char const *p = up->buf + tzheadsize; + /* Although tzfile(5) currently requires typecnt to be nonzero, + support future formats that may allow zero typecnt + in files that have a TZ string and no transitions. */ + if (! (0 <= leapcnt && leapcnt < TZ_MAX_LEAPS + && 0 <= typecnt && typecnt < TZ_MAX_TYPES + && 0 <= timecnt && timecnt < TZ_MAX_TIMES + && 0 <= charcnt && charcnt < TZ_MAX_CHARS + && 0 <= ttisstdcnt && ttisstdcnt < TZ_MAX_TYPES + && 0 <= ttisutcnt && ttisutcnt < TZ_MAX_TYPES)) + return EINVAL; + datablock_size + = (timecnt * stored /* ats */ + + timecnt /* types */ + + typecnt * 6 /* ttinfos */ + + charcnt /* chars */ + + leapcnt * (stored + 4) /* lsinfos */ + + ttisstdcnt /* ttisstds */ + + ttisutcnt); /* ttisuts */ + if (nread < tzheadsize + datablock_size) + return EINVAL; + if (skip_datablock) + p += datablock_size; + else { + if (! ((ttisstdcnt == typecnt || ttisstdcnt == 0) + && (ttisutcnt == typecnt || ttisutcnt == 0))) + return EINVAL; + + sp->leapcnt = leapcnt; + sp->timecnt = timecnt; + sp->typecnt = typecnt; + sp->charcnt = charcnt; + + /* Read transitions, discarding those out of time_t range. + But pretend the last transition before TIME_T_MIN + occurred at TIME_T_MIN. */ + timecnt = 0; + for (i = 0; i < sp->timecnt; ++i) { + int_fast64_t at + = stored == 4 ? detzcode(p) : detzcode64(p); + sp->types[i] = at <= TIME_T_MAX; + if (sp->types[i]) { + time_t attime + = ((TYPE_SIGNED(time_t) ? at < TIME_T_MIN : at < 0) + ? TIME_T_MIN : at); + if (timecnt && attime <= sp->ats[timecnt - 1]) { + if (attime < sp->ats[timecnt - 1]) + return EINVAL; + sp->types[i - 1] = 0; + timecnt--; + } + sp->ats[timecnt++] = attime; + } + p += stored; + } + + timecnt = 0; + for (i = 0; i < sp->timecnt; ++i) { + unsigned char typ = *p++; + if (sp->typecnt <= typ) + return EINVAL; + if (sp->types[i]) + sp->types[timecnt++] = typ; + } + sp->timecnt = timecnt; + for (i = 0; i < sp->typecnt; ++i) { + register struct ttinfo * ttisp; + unsigned char isdst, desigidx; + + ttisp = &sp->ttis[i]; + ttisp->tt_utoff = detzcode(p); + p += 4; + isdst = *p++; + if (! (isdst < 2)) + return EINVAL; + ttisp->tt_isdst = isdst; + desigidx = *p++; + if (! (desigidx < sp->charcnt)) + return EINVAL; + ttisp->tt_desigidx = desigidx; + } + for (i = 0; i < sp->charcnt; ++i) + sp->chars[i] = *p++; + /* Ensure '\0'-terminated, and make it safe to call + ttunspecified later. */ + memset(&sp->chars[i], 0, CHARS_EXTRA); + + /* Read leap seconds, discarding those out of time_t range. */ + leapcnt = 0; + for (i = 0; i < sp->leapcnt; ++i) { + int_fast64_t tr = stored == 4 ? detzcode(p) : detzcode64(p); + int_fast32_t corr = detzcode(p + stored); + p += stored + 4; + + /* Leap seconds cannot occur before the Epoch, + or out of order. */ + if (tr <= prevtr) + return EINVAL; + + /* To avoid other botches in this code, each leap second's + correction must differ from the previous one's by 1 + second or less, except that the first correction can be + any value; these requirements are more generous than + RFC 8536, to allow future RFC extensions. */ + if (! (i == 0 + || (prevcorr < corr + ? corr == prevcorr + 1 + : (corr == prevcorr + || corr == prevcorr - 1)))) + return EINVAL; + prevtr = tr; + prevcorr = corr; + + if (tr <= TIME_T_MAX) { + sp->lsis[leapcnt].ls_trans = tr; + sp->lsis[leapcnt].ls_corr = corr; + leapcnt++; + } + } + sp->leapcnt = leapcnt; + + for (i = 0; i < sp->typecnt; ++i) { + register struct ttinfo * ttisp; + + ttisp = &sp->ttis[i]; + if (ttisstdcnt == 0) + ttisp->tt_ttisstd = false; + else { + if (*p != true && *p != false) + return EINVAL; + ttisp->tt_ttisstd = *p++; + } + } + for (i = 0; i < sp->typecnt; ++i) { + register struct ttinfo * ttisp; + + ttisp = &sp->ttis[i]; + if (ttisutcnt == 0) + ttisp->tt_ttisut = false; + else { + if (*p != true && *p != false) + return EINVAL; + ttisp->tt_ttisut = *p++; + } + } + } + + nread -= p - up->buf; + memmove(up->buf, p, nread); + + /* If this is an old file, we're done. */ + if (!version) + break; + } + if (doextend && nread > 2 && + up->buf[0] == '\n' && up->buf[nread - 1] == '\n' && + sp->typecnt + 2 <= TZ_MAX_TYPES) { + struct state *ts = &lsp->u.st; + + up->buf[nread - 1] = '\0'; + if (tzparse(&up->buf[1], ts, sp)) { + + /* Attempt to reuse existing abbreviations. + Without this, America/Anchorage would be right on + the edge after 2037 when TZ_MAX_CHARS is 50, as + sp->charcnt equals 40 (for LMT AST AWT APT AHST + AHDT YST AKDT AKST) and ts->charcnt equals 10 + (for AKST AKDT). Reusing means sp->charcnt can + stay 40 in this example. */ + int gotabbr = 0; + int charcnt = sp->charcnt; + for (i = 0; i < ts->typecnt; i++) { + char *tsabbr = ts->chars + ts->ttis[i].tt_desigidx; + int j; + for (j = 0; j < charcnt; j++) + if (strcmp(sp->chars + j, tsabbr) == 0) { + ts->ttis[i].tt_desigidx = j; + gotabbr++; + break; + } + if (! (j < charcnt)) { + int tsabbrlen = strlen(tsabbr); + if (j + tsabbrlen < TZ_MAX_CHARS) { + strcpy(sp->chars + j, tsabbr); + charcnt = j + tsabbrlen + 1; + ts->ttis[i].tt_desigidx = j; + gotabbr++; + } + } + } + if (gotabbr == ts->typecnt) { + sp->charcnt = charcnt; + + /* Ignore any trailing, no-op transitions generated + by zic as they don't help here and can run afoul + of bugs in zic 2016j or earlier. */ + while (1 < sp->timecnt + && (sp->types[sp->timecnt - 1] + == sp->types[sp->timecnt - 2])) + sp->timecnt--; + + sp->goahead = ts->goahead; + + for (i = 0; i < ts->timecnt; i++) { + time_t t = ts->ats[i]; + if (increment_overflow_time(&t, leapcorr(sp, t)) + || (0 < sp->timecnt + && t <= sp->ats[sp->timecnt - 1])) + continue; + if (TZ_MAX_TIMES <= sp->timecnt) { + sp->goahead = false; + break; + } + sp->ats[sp->timecnt] = t; + sp->types[sp->timecnt] = (sp->typecnt + + ts->types[i]); + sp->timecnt++; + } + for (i = 0; i < ts->typecnt; i++) + sp->ttis[sp->typecnt++] = ts->ttis[i]; + } + } + } + if (sp->typecnt == 0) + return EINVAL; + + return 0; +} + +/* Load tz data from the file named NAME into *SP. Read extended + format if DOEXTEND. Return 0 on success, an errno value on failure. */ +static int +tzload(char const *name, struct state *sp, bool doextend) +{ +#ifdef ALL_STATE + union local_storage *lsp = malloc(sizeof *lsp); + if (!lsp) { + return HAVE_MALLOC_ERRNO ? errno : ENOMEM; + } else { + int err = tzloadbody(name, sp, doextend, lsp); + free(lsp); + return err; + } +#else + union local_storage ls; + return tzloadbody(name, sp, doextend, &ls); +#endif +} + +static const int mon_lengths[2][MONSPERYEAR] = { + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } +}; + +static const int year_lengths[2] = { + DAYSPERNYEAR, DAYSPERLYEAR +}; + +/* Is C an ASCII digit? */ +static bool +is_digit(char c) +{ + return '0' <= c && c <= '9'; +} + +/* +** Given a pointer into a timezone string, scan until a character that is not +** a valid character in a time zone abbreviation is found. +** Return a pointer to that character. +*/ + +ATTRIBUTE_PURE_114833 static const char * +getzname(register const char *strp) +{ + register char c; + + while ((c = *strp) != '\0' && !is_digit(c) && c != ',' && c != '-' && + c != '+') + ++strp; + return strp; +} + +/* +** Given a pointer into an extended timezone string, scan until the ending +** delimiter of the time zone abbreviation is located. +** Return a pointer to the delimiter. +** +** As with getzname above, the legal character set is actually quite +** restricted, with other characters producing undefined results. +** We don't do any checking here; checking is done later in common-case code. +*/ + +ATTRIBUTE_PURE_114833 static const char * +getqzname(register const char *strp, const int delim) +{ + register int c; + + while ((c = *strp) != '\0' && c != delim) + ++strp; + return strp; +} + +/* +** Given a pointer into a timezone string, extract a number from that string. +** Check that the number is within a specified range; if it is not, return +** NULL. +** Otherwise, return a pointer to the first character not part of the number. +*/ + +static const char * +getnum(register const char *strp, int *const nump, const int min, const int max) +{ + register char c; + register int num; + + if (strp == NULL || !is_digit(c = *strp)) + return NULL; + num = 0; + do { + num = num * 10 + (c - '0'); + if (num > max) + return NULL; /* illegal value */ + c = *++strp; + } while (is_digit(c)); + if (num < min) + return NULL; /* illegal value */ + *nump = num; + return strp; +} + +/* +** Given a pointer into a timezone string, extract a number of seconds, +** in hh[:mm[:ss]] form, from the string. +** If any error occurs, return NULL. +** Otherwise, return a pointer to the first character not part of the number +** of seconds. +*/ + +static const char * +getsecs(register const char *strp, int_fast32_t *const secsp) +{ + int num; + int_fast32_t secsperhour = SECSPERHOUR; + + /* + ** 'HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-POSIX rules like + ** "M10.4.6/26", which does not conform to POSIX, + ** but which specifies the equivalent of + ** "02:00 on the first Sunday on or after 23 Oct". + */ + strp = getnum(strp, &num, 0, HOURSPERDAY * DAYSPERWEEK - 1); + if (strp == NULL) + return NULL; + *secsp = num * secsperhour; + if (*strp == ':') { + ++strp; + strp = getnum(strp, &num, 0, MINSPERHOUR - 1); + if (strp == NULL) + return NULL; + *secsp += num * SECSPERMIN; + if (*strp == ':') { + ++strp; + /* 'SECSPERMIN' allows for leap seconds. */ + strp = getnum(strp, &num, 0, SECSPERMIN); + if (strp == NULL) + return NULL; + *secsp += num; + } + } + return strp; +} + +/* +** Given a pointer into a timezone string, extract an offset, in +** [+-]hh[:mm[:ss]] form, from the string. +** If any error occurs, return NULL. +** Otherwise, return a pointer to the first character not part of the time. +*/ + +static const char * +getoffset(register const char *strp, int_fast32_t *const offsetp) +{ + register bool neg = false; + + if (*strp == '-') { + neg = true; + ++strp; + } else if (*strp == '+') + ++strp; + strp = getsecs(strp, offsetp); + if (strp == NULL) + return NULL; /* illegal time */ + if (neg) + *offsetp = -*offsetp; + return strp; +} + +/* +** Given a pointer into a timezone string, extract a rule in the form +** date[/time]. See POSIX Base Definitions section 8.3 variable TZ +** for the format of "date" and "time". +** If a valid rule is not found, return NULL. +** Otherwise, return a pointer to the first character not part of the rule. +*/ + +static const char * +getrule(const char *strp, register struct rule *const rulep) +{ + if (*strp == 'J') { + /* + ** Julian day. + */ + rulep->r_type = JULIAN_DAY; + ++strp; + strp = getnum(strp, &rulep->r_day, 1, DAYSPERNYEAR); + } else if (*strp == 'M') { + /* + ** Month, week, day. + */ + rulep->r_type = MONTH_NTH_DAY_OF_WEEK; + ++strp; + strp = getnum(strp, &rulep->r_mon, 1, MONSPERYEAR); + if (strp == NULL) + return NULL; + if (*strp++ != '.') + return NULL; + strp = getnum(strp, &rulep->r_week, 1, 5); + if (strp == NULL) + return NULL; + if (*strp++ != '.') + return NULL; + strp = getnum(strp, &rulep->r_day, 0, DAYSPERWEEK - 1); + } else if (is_digit(*strp)) { + /* + ** Day of year. + */ + rulep->r_type = DAY_OF_YEAR; + strp = getnum(strp, &rulep->r_day, 0, DAYSPERLYEAR - 1); + } else return NULL; /* invalid format */ + if (strp == NULL) + return NULL; + if (*strp == '/') { + /* + ** Time specified. + */ + ++strp; + strp = getoffset(strp, &rulep->r_time); + } else rulep->r_time = 2 * SECSPERHOUR; /* default = 2:00:00 */ + return strp; +} + +/* +** Given a year, a rule, and the offset from UT at the time that rule takes +** effect, calculate the year-relative time that rule takes effect. +*/ + +static int_fast32_t +transtime(const int year, register const struct rule *const rulep, + const int_fast32_t offset) +{ + register bool leapyear; + register int_fast32_t value; + register int i; + int d, m1, yy0, yy1, yy2, dow; + + leapyear = isleap(year); + switch (rulep->r_type) { + + case JULIAN_DAY: + /* + ** Jn - Julian day, 1 == January 1, 60 == March 1 even in leap + ** years. + ** In non-leap years, or if the day number is 59 or less, just + ** add SECSPERDAY times the day number-1 to the time of + ** January 1, midnight, to get the day. + */ + value = (rulep->r_day - 1) * SECSPERDAY; + if (leapyear && rulep->r_day >= 60) + value += SECSPERDAY; + break; + + case DAY_OF_YEAR: + /* + ** n - day of year. + ** Just add SECSPERDAY times the day number to the time of + ** January 1, midnight, to get the day. + */ + value = rulep->r_day * SECSPERDAY; + break; + + case MONTH_NTH_DAY_OF_WEEK: + /* + ** Mm.n.d - nth "dth day" of month m. + */ + + /* + ** Use Zeller's Congruence to get day-of-week of first day of + ** month. + */ + m1 = (rulep->r_mon + 9) % 12 + 1; + yy0 = (rulep->r_mon <= 2) ? (year - 1) : year; + yy1 = yy0 / 100; + yy2 = yy0 % 100; + dow = ((26 * m1 - 2) / 10 + + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7; + if (dow < 0) + dow += DAYSPERWEEK; + + /* + ** "dow" is the day-of-week of the first day of the month. Get + ** the day-of-month (zero-origin) of the first "dow" day of the + ** month. + */ + d = rulep->r_day - dow; + if (d < 0) + d += DAYSPERWEEK; + for (i = 1; i < rulep->r_week; ++i) { + if (d + DAYSPERWEEK >= + mon_lengths[leapyear][rulep->r_mon - 1]) + break; + d += DAYSPERWEEK; + } + + /* + ** "d" is the day-of-month (zero-origin) of the day we want. + */ + value = d * SECSPERDAY; + for (i = 0; i < rulep->r_mon - 1; ++i) + value += mon_lengths[leapyear][i] * SECSPERDAY; + break; + + default: unreachable(); + } + + /* + ** "value" is the year-relative time of 00:00:00 UT on the day in + ** question. To get the year-relative time of the specified local + ** time on that day, add the transition time and the current offset + ** from UT. + */ + return value + rulep->r_time + offset; +} + +/* +** Given a POSIX.1 proleptic TZ string, fill in the rule tables as +** appropriate. +*/ + +static bool +tzparse(const char *name, struct state *sp, struct state const *basep) +{ + const char * stdname; + const char * dstname; + int_fast32_t stdoffset; + int_fast32_t dstoffset; + register char * cp; + register bool load_ok; + ptrdiff_t stdlen, dstlen, charcnt; + time_t atlo = TIME_T_MIN, leaplo = TIME_T_MIN; + + stdname = name; + if (*name == '<') { + name++; + stdname = name; + name = getqzname(name, '>'); + if (*name != '>') + return false; + stdlen = name - stdname; + name++; + } else { + name = getzname(name); + stdlen = name - stdname; + } + if (! (0 < stdlen && stdlen <= TZNAME_MAXIMUM)) + return false; + name = getoffset(name, &stdoffset); + if (name == NULL) + return false; + charcnt = stdlen + 1; + if (basep) { + if (0 < basep->timecnt) + atlo = basep->ats[basep->timecnt - 1]; + load_ok = false; + sp->leapcnt = basep->leapcnt; + memcpy(sp->lsis, basep->lsis, sp->leapcnt * sizeof *sp->lsis); + } else { + load_ok = tzload(TZDEFRULES, sp, false) == 0; + if (!load_ok) + sp->leapcnt = 0; /* So, we're off a little. */ + } + if (0 < sp->leapcnt) + leaplo = sp->lsis[sp->leapcnt - 1].ls_trans; + sp->goback = sp->goahead = false; + if (*name != '\0') { + if (*name == '<') { + dstname = ++name; + name = getqzname(name, '>'); + if (*name != '>') + return false; + dstlen = name - dstname; + name++; + } else { + dstname = name; + name = getzname(name); + dstlen = name - dstname; /* length of DST abbr. */ + } + if (! (0 < dstlen && dstlen <= TZNAME_MAXIMUM)) + return false; + charcnt += dstlen + 1; + if (*name != '\0' && *name != ',' && *name != ';') { + name = getoffset(name, &dstoffset); + if (name == NULL) + return false; + } else dstoffset = stdoffset - SECSPERHOUR; + if (*name == '\0' && !load_ok) + name = TZDEFRULESTRING; + if (*name == ',' || *name == ';') { + struct rule start; + struct rule end; + register int year; + register int timecnt; + time_t janfirst; + int_fast32_t janoffset = 0; + int yearbeg, yearlim; + + ++name; + if ((name = getrule(name, &start)) == NULL) + return false; + if (*name++ != ',') + return false; + if ((name = getrule(name, &end)) == NULL) + return false; + if (*name != '\0') + return false; + sp->typecnt = 2; /* standard time and DST */ + /* + ** Two transitions per year, from EPOCH_YEAR forward. + */ + init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); + init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1); + timecnt = 0; + janfirst = 0; + yearbeg = EPOCH_YEAR; + + do { + int_fast32_t yearsecs + = year_lengths[isleap(yearbeg - 1)] * SECSPERDAY; + time_t janfirst1 = janfirst; + yearbeg--; + if (increment_overflow_time(&janfirst1, -yearsecs)) { + janoffset = -yearsecs; + break; + } + janfirst = janfirst1; + } while (atlo < janfirst + && EPOCH_YEAR - YEARSPERREPEAT / 2 < yearbeg); + + while (true) { + int_fast32_t yearsecs + = year_lengths[isleap(yearbeg)] * SECSPERDAY; + int yearbeg1 = yearbeg; + time_t janfirst1 = janfirst; + if (increment_overflow_time(&janfirst1, yearsecs) + || increment_overflow(&yearbeg1, 1) + || atlo <= janfirst1) + break; + yearbeg = yearbeg1; + janfirst = janfirst1; + } + + yearlim = yearbeg; + if (increment_overflow(&yearlim, years_of_observations)) + yearlim = INT_MAX; + for (year = yearbeg; year < yearlim; year++) { + int_fast32_t + starttime = transtime(year, &start, stdoffset), + endtime = transtime(year, &end, dstoffset); + int_fast32_t + yearsecs = (year_lengths[isleap(year)] + * SECSPERDAY); + bool reversed = endtime < starttime; + if (reversed) { + int_fast32_t swap = starttime; + starttime = endtime; + endtime = swap; + } + if (reversed + || (starttime < endtime + && endtime - starttime < yearsecs)) { + if (TZ_MAX_TIMES - 2 < timecnt) + break; + sp->ats[timecnt] = janfirst; + if (! increment_overflow_time + (&sp->ats[timecnt], + janoffset + starttime) + && atlo <= sp->ats[timecnt]) + sp->types[timecnt++] = !reversed; + sp->ats[timecnt] = janfirst; + if (! increment_overflow_time + (&sp->ats[timecnt], + janoffset + endtime) + && atlo <= sp->ats[timecnt]) { + sp->types[timecnt++] = reversed; + } + } + if (endtime < leaplo) { + yearlim = year; + if (increment_overflow(&yearlim, + years_of_observations)) + yearlim = INT_MAX; + } + if (increment_overflow_time + (&janfirst, janoffset + yearsecs)) + break; + janoffset = 0; + } + sp->timecnt = timecnt; + if (! timecnt) { + sp->ttis[0] = sp->ttis[1]; + sp->typecnt = 1; /* Perpetual DST. */ + } else if (years_of_observations <= year - yearbeg) + sp->goback = sp->goahead = true; + } else { + register int_fast32_t theirstdoffset; + register int_fast32_t theirdstoffset; + register int_fast32_t theiroffset; + register bool isdst; + register int i; + register int j; + + if (*name != '\0') + return false; + /* + ** Initial values of theirstdoffset and theirdstoffset. + */ + theirstdoffset = 0; + for (i = 0; i < sp->timecnt; ++i) { + j = sp->types[i]; + if (!sp->ttis[j].tt_isdst) { + theirstdoffset = + - sp->ttis[j].tt_utoff; + break; + } + } + theirdstoffset = 0; + for (i = 0; i < sp->timecnt; ++i) { + j = sp->types[i]; + if (sp->ttis[j].tt_isdst) { + theirdstoffset = + - sp->ttis[j].tt_utoff; + break; + } + } + /* + ** Initially we're assumed to be in standard time. + */ + isdst = false; + /* + ** Now juggle transition times and types + ** tracking offsets as you do. + */ + for (i = 0; i < sp->timecnt; ++i) { + j = sp->types[i]; + sp->types[i] = sp->ttis[j].tt_isdst; + if (sp->ttis[j].tt_ttisut) { + /* No adjustment to transition time */ + } else { + /* + ** If daylight saving time is in + ** effect, and the transition time was + ** not specified as standard time, add + ** the daylight saving time offset to + ** the transition time; otherwise, add + ** the standard time offset to the + ** transition time. + */ + /* + ** Transitions from DST to DDST + ** will effectively disappear since + ** proleptic TZ strings have only one + ** DST offset. + */ + if (isdst && !sp->ttis[j].tt_ttisstd) { + sp->ats[i] += dstoffset - + theirdstoffset; + } else { + sp->ats[i] += stdoffset - + theirstdoffset; + } + } + theiroffset = -sp->ttis[j].tt_utoff; + if (sp->ttis[j].tt_isdst) + theirdstoffset = theiroffset; + else theirstdoffset = theiroffset; + } + /* + ** Finally, fill in ttis. + */ + init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); + init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1); + sp->typecnt = 2; + } + } else { + dstlen = 0; + sp->typecnt = 1; /* only standard time */ + sp->timecnt = 0; + init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); + } + sp->charcnt = charcnt; + cp = sp->chars; + memcpy(cp, stdname, stdlen); + cp += stdlen; + *cp++ = '\0'; + if (dstlen != 0) { + memcpy(cp, dstname, dstlen); + *(cp + dstlen) = '\0'; + } + return true; +} + +static void +gmtload(struct state *const sp) +{ + if (tzload(etc_utc, sp, true) != 0) + tzparse("UTC0", sp, NULL); +} + +/* Initialize *SP to a value appropriate for the TZ setting NAME. + Return 0 on success, an errno value on failure. */ +static int +zoneinit(struct state *sp, char const *name) +{ + if (name && ! name[0]) { + /* + ** User wants it fast rather than right. + */ + sp->leapcnt = 0; /* so, we're off a little */ + sp->timecnt = 0; + sp->typecnt = 0; + sp->charcnt = 0; + sp->goback = sp->goahead = false; + init_ttinfo(&sp->ttis[0], 0, false, 0); + strcpy(sp->chars, utc); + return 0; + } else { + int err = tzload(name, sp, true); + if (err != 0 && name && name[0] != ':' && tzparse(name, sp, NULL)) + err = 0; + if (err == 0) + err = scrub_abbrs(sp); + return err; + } +} + +static void +tzset_unlocked(void) +{ + char const *name = getenv("TZ"); + struct state *sp = lclptr; + int lcl = name ? strlen(name) < sizeof lcl_TZname : -1; + if (lcl < 0 + ? lcl_is_set < 0 + : 0 < lcl_is_set && strcmp(lcl_TZname, name) == 0) + return; +#ifdef ALL_STATE + if (! sp) + lclptr = sp = malloc(sizeof *lclptr); +#endif /* defined ALL_STATE */ + if (sp) { + if (zoneinit(sp, name) != 0) + zoneinit(sp, ""); + if (0 < lcl) + strcpy(lcl_TZname, name); + } + settzname(); + lcl_is_set = lcl; +} + +void +tzset(void) +{ + if (lock() != 0) + return; + tzset_unlocked(); + unlock(); +} + +static void +gmtcheck(void) +{ + static bool gmt_is_set; + if (lock() != 0) + return; + if (! gmt_is_set) { +#ifdef ALL_STATE + gmtptr = malloc(sizeof *gmtptr); +#endif + if (gmtptr) + gmtload(gmtptr); + gmt_is_set = true; + } + unlock(); +} + +#if NETBSD_INSPIRED + +timezone_t +tzalloc(char const *name) +{ + timezone_t sp = malloc(sizeof *sp); + if (sp) { + int err = zoneinit(sp, name); + if (err != 0) { + free(sp); + errno = err; + return NULL; + } + } else if (!HAVE_MALLOC_ERRNO) + errno = ENOMEM; + return sp; +} + +void +tzfree(timezone_t sp) +{ + free(sp); +} + +/* +** NetBSD 6.1.4 has ctime_rz, but omit it because C23 deprecates ctime and +** POSIX.1-2024 removes ctime_r. Both have potential security problems that +** ctime_rz would share. Callers can instead use localtime_rz + strftime. +** +** NetBSD 6.1.4 has tzgetname, but omit it because it doesn't work +** in zones with three or more time zone abbreviations. +** Callers can instead use localtime_rz + strftime. +*/ + +#endif + +/* +** The easy way to behave "as if no library function calls" localtime +** is to not call it, so we drop its guts into "localsub", which can be +** freely called. (And no, the PANS doesn't require the above behavior, +** but it *is* desirable.) +** +** If successful and SETNAME is nonzero, +** set the applicable parts of tzname, timezone and altzone; +** however, it's OK to omit this step for proleptic TZ strings +** since in that case tzset should have already done this step correctly. +** SETNAME's type is int_fast32_t for compatibility with gmtsub, +** but it is actually a boolean and its value should be 0 or 1. +*/ + +/*ARGSUSED*/ +static struct tm * +localsub(struct state const *sp, time_t const *timep, int_fast32_t setname, + struct tm *const tmp) +{ + register const struct ttinfo * ttisp; + register int i; + register struct tm * result; + const time_t t = *timep; + + if (sp == NULL) { + /* Don't bother to set tzname etc.; tzset has already done it. */ + return gmtsub(gmtptr, timep, 0, tmp); + } + if ((sp->goback && t < sp->ats[0]) || + (sp->goahead && t > sp->ats[sp->timecnt - 1])) { + time_t newt; + register time_t seconds; + register time_t years; + + if (t < sp->ats[0]) + seconds = sp->ats[0] - t; + else seconds = t - sp->ats[sp->timecnt - 1]; + --seconds; + + /* Beware integer overflow, as SECONDS might + be close to the maximum time_t. */ + years = seconds / SECSPERREPEAT * YEARSPERREPEAT; + seconds = years * AVGSECSPERYEAR; + years += YEARSPERREPEAT; + if (t < sp->ats[0]) + newt = t + seconds + SECSPERREPEAT; + else + newt = t - seconds - SECSPERREPEAT; + + if (newt < sp->ats[0] || + newt > sp->ats[sp->timecnt - 1]) + return NULL; /* "cannot happen" */ + result = localsub(sp, &newt, setname, tmp); + if (result) { +#if defined ckd_add && defined ckd_sub + if (t < sp->ats[0] + ? ckd_sub(&result->tm_year, + result->tm_year, years) + : ckd_add(&result->tm_year, + result->tm_year, years)) + return NULL; +#else + register int_fast64_t newy; + + newy = result->tm_year; + if (t < sp->ats[0]) + newy -= years; + else newy += years; + if (! (INT_MIN <= newy && newy <= INT_MAX)) + return NULL; + result->tm_year = newy; +#endif + } + return result; + } + if (sp->timecnt == 0 || t < sp->ats[0]) { + i = 0; + } else { + register int lo = 1; + register int hi = sp->timecnt; + + while (lo < hi) { + register int mid = (lo + hi) >> 1; + + if (t < sp->ats[mid]) + hi = mid; + else lo = mid + 1; + } + i = sp->types[lo - 1]; + } + ttisp = &sp->ttis[i]; + /* + ** To get (wrong) behavior that's compatible with System V Release 2.0 + ** you'd replace the statement below with + ** t += ttisp->tt_utoff; + ** timesub(&t, 0L, sp, tmp); + */ + result = timesub(&t, ttisp->tt_utoff, sp, tmp); + if (result) { + result->tm_isdst = ttisp->tt_isdst; +#ifdef TM_ZONE + result->TM_ZONE = (char *) &sp->chars[ttisp->tt_desigidx]; +#endif /* defined TM_ZONE */ + if (setname) + update_tzname_etc(sp, ttisp); + } + return result; +} + +#if NETBSD_INSPIRED + +struct tm * +localtime_rz(struct state *sp, time_t const *timep, + struct tm *tmp) +{ + return localsub(sp, timep, 0, tmp); +} + +#endif + +static struct tm * +localtime_tzset(time_t const *timep, struct tm *tmp, bool setname) +{ + int err = lock(); + if (err) { + errno = err; + return NULL; + } + if (setname || !lcl_is_set) + tzset_unlocked(); + tmp = localsub(lclptr, timep, setname, tmp); + unlock(); + return tmp; +} + +struct tm * +localtime(const time_t *timep) +{ +#if !SUPPORT_C89 + static struct tm tm; +#endif + return localtime_tzset(timep, &tm, true); +} + +struct tm * +localtime_r(const time_t *restrict timep, struct tm *restrict tmp) +{ + return localtime_tzset(timep, tmp, false); +} + +/* +** gmtsub is to gmtime as localsub is to localtime. +*/ + +static struct tm * +gmtsub(ATTRIBUTE_MAYBE_UNUSED struct state const *sp, time_t const *timep, + int_fast32_t offset, struct tm *tmp) +{ + register struct tm * result; + + result = timesub(timep, offset, gmtptr, tmp); +#ifdef TM_ZONE + /* + ** Could get fancy here and deliver something such as + ** "+xx" or "-xx" if offset is non-zero, + ** but this is no time for a treasure hunt. + */ + tmp->TM_ZONE = ((char *) + (offset ? wildabbr : gmtptr ? gmtptr->chars : utc)); +#endif /* defined TM_ZONE */ + return result; +} + +/* +* Re-entrant version of gmtime. +*/ + +struct tm * +gmtime_r(time_t const *restrict timep, struct tm *restrict tmp) +{ + gmtcheck(); + return gmtsub(gmtptr, timep, 0, tmp); +} + +struct tm * +gmtime(const time_t *timep) +{ +#if !SUPPORT_C89 + static struct tm tm; +#endif + return gmtime_r(timep, &tm); +} + +#if STD_INSPIRED + +/* This function is obsolescent and may disappear in future releases. + Callers can instead use localtime_rz with a fixed-offset zone. */ + +struct tm * +offtime(const time_t *timep, long offset) +{ + gmtcheck(); + +#if !SUPPORT_C89 + static struct tm tm; +#endif + return gmtsub(gmtptr, timep, offset, &tm); +} + +#endif + +/* +** Return the number of leap years through the end of the given year +** where, to make the math easy, the answer for year zero is defined as zero. +*/ + +static time_t +leaps_thru_end_of_nonneg(time_t y) +{ + return y / 4 - y / 100 + y / 400; +} + +static time_t +leaps_thru_end_of(time_t y) +{ + return (y < 0 + ? -1 - leaps_thru_end_of_nonneg(-1 - y) + : leaps_thru_end_of_nonneg(y)); +} + +static struct tm * +timesub(const time_t *timep, int_fast32_t offset, + const struct state *sp, struct tm *tmp) +{ + register const struct lsinfo * lp; + register time_t tdays; + register const int * ip; + register int_fast32_t corr; + register int i; + int_fast32_t idays, rem, dayoff, dayrem; + time_t y; + + /* If less than SECSPERMIN, the number of seconds since the + most recent positive leap second; otherwise, do not add 1 + to localtime tm_sec because of leap seconds. */ + time_t secs_since_posleap = SECSPERMIN; + + corr = 0; + i = (sp == NULL) ? 0 : sp->leapcnt; + while (--i >= 0) { + lp = &sp->lsis[i]; + if (*timep >= lp->ls_trans) { + corr = lp->ls_corr; + if ((i == 0 ? 0 : lp[-1].ls_corr) < corr) + secs_since_posleap = *timep - lp->ls_trans; + break; + } + } + + /* Calculate the year, avoiding integer overflow even if + time_t is unsigned. */ + tdays = *timep / SECSPERDAY; + rem = *timep % SECSPERDAY; + rem += offset % SECSPERDAY - corr % SECSPERDAY + 3 * SECSPERDAY; + dayoff = offset / SECSPERDAY - corr / SECSPERDAY + rem / SECSPERDAY - 3; + rem %= SECSPERDAY; + /* y = (EPOCH_YEAR + + floor((tdays + dayoff) / DAYSPERREPEAT) * YEARSPERREPEAT), + sans overflow. But calculate against 1570 (EPOCH_YEAR - + YEARSPERREPEAT) instead of against 1970 so that things work + for localtime values before 1970 when time_t is unsigned. */ + dayrem = tdays % DAYSPERREPEAT; + dayrem += dayoff % DAYSPERREPEAT; + y = (EPOCH_YEAR - YEARSPERREPEAT + + ((1 + dayoff / DAYSPERREPEAT + dayrem / DAYSPERREPEAT + - ((dayrem % DAYSPERREPEAT) < 0) + + tdays / DAYSPERREPEAT) + * YEARSPERREPEAT)); + /* idays = (tdays + dayoff) mod DAYSPERREPEAT, sans overflow. */ + idays = tdays % DAYSPERREPEAT; + idays += dayoff % DAYSPERREPEAT + 2 * DAYSPERREPEAT; + idays %= DAYSPERREPEAT; + /* Increase Y and decrease IDAYS until IDAYS is in range for Y. */ + while (year_lengths[isleap(y)] <= idays) { + int tdelta = idays / DAYSPERLYEAR; + int_fast32_t ydelta = tdelta + !tdelta; + time_t newy = y + ydelta; + register int leapdays; + leapdays = leaps_thru_end_of(newy - 1) - + leaps_thru_end_of(y - 1); + idays -= ydelta * DAYSPERNYEAR; + idays -= leapdays; + y = newy; + } + +#ifdef ckd_add + if (ckd_add(&tmp->tm_year, y, -TM_YEAR_BASE)) { + errno = EOVERFLOW; + return NULL; + } +#else + if (!TYPE_SIGNED(time_t) && y < TM_YEAR_BASE) { + int signed_y = y; + tmp->tm_year = signed_y - TM_YEAR_BASE; + } else if ((!TYPE_SIGNED(time_t) || INT_MIN + TM_YEAR_BASE <= y) + && y - TM_YEAR_BASE <= INT_MAX) + tmp->tm_year = y - TM_YEAR_BASE; + else { + errno = EOVERFLOW; + return NULL; + } +#endif + tmp->tm_yday = idays; + /* + ** The "extra" mods below avoid overflow problems. + */ + tmp->tm_wday = (TM_WDAY_BASE + + ((tmp->tm_year % DAYSPERWEEK) + * (DAYSPERNYEAR % DAYSPERWEEK)) + + leaps_thru_end_of(y - 1) + - leaps_thru_end_of(TM_YEAR_BASE - 1) + + idays); + tmp->tm_wday %= DAYSPERWEEK; + if (tmp->tm_wday < 0) + tmp->tm_wday += DAYSPERWEEK; + tmp->tm_hour = rem / SECSPERHOUR; + rem %= SECSPERHOUR; + tmp->tm_min = rem / SECSPERMIN; + tmp->tm_sec = rem % SECSPERMIN; + + /* Use "... ??:??:60" at the end of the localtime minute containing + the second just before the positive leap second. */ + tmp->tm_sec += secs_since_posleap <= tmp->tm_sec; + + ip = mon_lengths[isleap(y)]; + for (tmp->tm_mon = 0; idays >= ip[tmp->tm_mon]; ++(tmp->tm_mon)) + idays -= ip[tmp->tm_mon]; + tmp->tm_mday = idays + 1; + tmp->tm_isdst = 0; +#ifdef TM_GMTOFF + tmp->TM_GMTOFF = offset; +#endif /* defined TM_GMTOFF */ + return tmp; +} + +/* +** Adapted from code provided by Robert Elz, who writes: +** The "best" way to do mktime I think is based on an idea of Bob +** Kridle's (so its said...) from a long time ago. +** It does a binary search of the time_t space. Since time_t's are +** just 32 bits, its a max of 32 iterations (even at 64 bits it +** would still be very reasonable). +*/ + +#ifndef WRONG +# define WRONG (-1) +#endif /* !defined WRONG */ + +/* +** Normalize logic courtesy Paul Eggert. +*/ + +static bool +increment_overflow(int *ip, int j) +{ +#ifdef ckd_add + return ckd_add(ip, *ip, j); +#else + register int const i = *ip; + + /* + ** If i >= 0 there can only be overflow if i + j > INT_MAX + ** or if j > INT_MAX - i; given i >= 0, INT_MAX - i cannot overflow. + ** If i < 0 there can only be overflow if i + j < INT_MIN + ** or if j < INT_MIN - i; given i < 0, INT_MIN - i cannot overflow. + */ + if ((i >= 0) ? (j > INT_MAX - i) : (j < INT_MIN - i)) + return true; + *ip += j; + return false; +#endif +} + +static bool +increment_overflow32(int_fast32_t *const lp, int const m) +{ +#ifdef ckd_add + return ckd_add(lp, *lp, m); +#else + register int_fast32_t const l = *lp; + + if ((l >= 0) ? (m > INT_FAST32_MAX - l) : (m < INT_FAST32_MIN - l)) + return true; + *lp += m; + return false; +#endif +} + +static bool +increment_overflow_time(time_t *tp, int_fast32_t j) +{ +#ifdef ckd_add + return ckd_add(tp, *tp, j); +#else + /* + ** This is like + ** 'if (! (TIME_T_MIN <= *tp + j && *tp + j <= TIME_T_MAX)) ...', + ** except that it does the right thing even if *tp + j would overflow. + */ + if (! (j < 0 + ? (TYPE_SIGNED(time_t) ? TIME_T_MIN - j <= *tp : -1 - j < *tp) + : *tp <= TIME_T_MAX - j)) + return true; + *tp += j; + return false; +#endif +} + +static bool +normalize_overflow(int *const tensptr, int *const unitsptr, const int base) +{ + register int tensdelta; + + tensdelta = (*unitsptr >= 0) ? + (*unitsptr / base) : + (-1 - (-1 - *unitsptr) / base); + *unitsptr -= tensdelta * base; + return increment_overflow(tensptr, tensdelta); +} + +static bool +normalize_overflow32(int_fast32_t *tensptr, int *unitsptr, int base) +{ + register int tensdelta; + + tensdelta = (*unitsptr >= 0) ? + (*unitsptr / base) : + (-1 - (-1 - *unitsptr) / base); + *unitsptr -= tensdelta * base; + return increment_overflow32(tensptr, tensdelta); +} + +static int +tmcomp(register const struct tm *const atmp, + register const struct tm *const btmp) +{ + register int result; + + if (atmp->tm_year != btmp->tm_year) + return atmp->tm_year < btmp->tm_year ? -1 : 1; + if ((result = (atmp->tm_mon - btmp->tm_mon)) == 0 && + (result = (atmp->tm_mday - btmp->tm_mday)) == 0 && + (result = (atmp->tm_hour - btmp->tm_hour)) == 0 && + (result = (atmp->tm_min - btmp->tm_min)) == 0) + result = atmp->tm_sec - btmp->tm_sec; + return result; +} + +/* Copy to *DEST from *SRC. Copy only the members needed for mktime, + as other members might not be initialized. */ +static void +mktmcpy(struct tm *dest, struct tm const *src) +{ + dest->tm_sec = src->tm_sec; + dest->tm_min = src->tm_min; + dest->tm_hour = src->tm_hour; + dest->tm_mday = src->tm_mday; + dest->tm_mon = src->tm_mon; + dest->tm_year = src->tm_year; + dest->tm_isdst = src->tm_isdst; +#if defined TM_GMTOFF && ! UNINIT_TRAP + dest->TM_GMTOFF = src->TM_GMTOFF; +#endif +} + +static time_t +time2sub(struct tm *const tmp, + struct tm *(*funcp)(struct state const *, time_t const *, + int_fast32_t, struct tm *), + struct state const *sp, + const int_fast32_t offset, + bool *okayp, + bool do_norm_secs) +{ + register int dir; + register int i, j; + register int saved_seconds; + register int_fast32_t li; + register time_t lo; + register time_t hi; + int_fast32_t y; + time_t newt; + time_t t; + struct tm yourtm, mytm; + + *okayp = false; + mktmcpy(&yourtm, tmp); + + if (do_norm_secs) { + if (normalize_overflow(&yourtm.tm_min, &yourtm.tm_sec, + SECSPERMIN)) + return WRONG; + } + if (normalize_overflow(&yourtm.tm_hour, &yourtm.tm_min, MINSPERHOUR)) + return WRONG; + if (normalize_overflow(&yourtm.tm_mday, &yourtm.tm_hour, HOURSPERDAY)) + return WRONG; + y = yourtm.tm_year; + if (normalize_overflow32(&y, &yourtm.tm_mon, MONSPERYEAR)) + return WRONG; + /* + ** Turn y into an actual year number for now. + ** It is converted back to an offset from TM_YEAR_BASE later. + */ + if (increment_overflow32(&y, TM_YEAR_BASE)) + return WRONG; + while (yourtm.tm_mday <= 0) { + if (increment_overflow32(&y, -1)) + return WRONG; + li = y + (1 < yourtm.tm_mon); + yourtm.tm_mday += year_lengths[isleap(li)]; + } + while (yourtm.tm_mday > DAYSPERLYEAR) { + li = y + (1 < yourtm.tm_mon); + yourtm.tm_mday -= year_lengths[isleap(li)]; + if (increment_overflow32(&y, 1)) + return WRONG; + } + for ( ; ; ) { + i = mon_lengths[isleap(y)][yourtm.tm_mon]; + if (yourtm.tm_mday <= i) + break; + yourtm.tm_mday -= i; + if (++yourtm.tm_mon >= MONSPERYEAR) { + yourtm.tm_mon = 0; + if (increment_overflow32(&y, 1)) + return WRONG; + } + } +#ifdef ckd_add + if (ckd_add(&yourtm.tm_year, y, -TM_YEAR_BASE)) + return WRONG; +#else + if (increment_overflow32(&y, -TM_YEAR_BASE)) + return WRONG; + if (! (INT_MIN <= y && y <= INT_MAX)) + return WRONG; + yourtm.tm_year = y; +#endif + if (yourtm.tm_sec >= 0 && yourtm.tm_sec < SECSPERMIN) + saved_seconds = 0; + else if (yourtm.tm_year < EPOCH_YEAR - TM_YEAR_BASE) { + /* + ** We can't set tm_sec to 0, because that might push the + ** time below the minimum representable time. + ** Set tm_sec to 59 instead. + ** This assumes that the minimum representable time is + ** not in the same minute that a leap second was deleted from, + ** which is a safer assumption than using 58 would be. + */ + if (increment_overflow(&yourtm.tm_sec, 1 - SECSPERMIN)) + return WRONG; + saved_seconds = yourtm.tm_sec; + yourtm.tm_sec = SECSPERMIN - 1; + } else { + saved_seconds = yourtm.tm_sec; + yourtm.tm_sec = 0; + } + /* + ** Do a binary search (this works whatever time_t's type is). + */ + lo = TIME_T_MIN; + hi = TIME_T_MAX; + for ( ; ; ) { + t = lo / 2 + hi / 2; + if (t < lo) + t = lo; + else if (t > hi) + t = hi; + if (! funcp(sp, &t, offset, &mytm)) { + /* + ** Assume that t is too extreme to be represented in + ** a struct tm; arrange things so that it is less + ** extreme on the next pass. + */ + dir = (t > 0) ? 1 : -1; + } else dir = tmcomp(&mytm, &yourtm); + if (dir != 0) { + if (t == lo) { + if (t == TIME_T_MAX) + return WRONG; + ++t; + ++lo; + } else if (t == hi) { + if (t == TIME_T_MIN) + return WRONG; + --t; + --hi; + } + if (lo > hi) + return WRONG; + if (dir > 0) + hi = t; + else lo = t; + continue; + } +#if defined TM_GMTOFF && ! UNINIT_TRAP + if (mytm.TM_GMTOFF != yourtm.TM_GMTOFF + && (yourtm.TM_GMTOFF < 0 + ? (-SECSPERDAY <= yourtm.TM_GMTOFF + && (mytm.TM_GMTOFF <= + (min_tz(INT_FAST32_MAX, LONG_MAX) + + yourtm.TM_GMTOFF))) + : (yourtm.TM_GMTOFF <= SECSPERDAY + && ((max_tz(INT_FAST32_MIN, LONG_MIN) + + yourtm.TM_GMTOFF) + <= mytm.TM_GMTOFF)))) { + /* MYTM matches YOURTM except with the wrong UT offset. + YOURTM.TM_GMTOFF is plausible, so try it instead. + It's OK if YOURTM.TM_GMTOFF contains uninitialized data, + since the guess gets checked. */ + time_t altt = t; + int_fast32_t diff = mytm.TM_GMTOFF - yourtm.TM_GMTOFF; + if (!increment_overflow_time(&altt, diff)) { + struct tm alttm; + if (funcp(sp, &altt, offset, &alttm) + && alttm.tm_isdst == mytm.tm_isdst + && alttm.TM_GMTOFF == yourtm.TM_GMTOFF + && tmcomp(&alttm, &yourtm) == 0) { + t = altt; + mytm = alttm; + } + } + } +#endif + if (yourtm.tm_isdst < 0 || mytm.tm_isdst == yourtm.tm_isdst) + break; + /* + ** Right time, wrong type. + ** Hunt for right time, right type. + ** It's okay to guess wrong since the guess + ** gets checked. + */ + if (sp == NULL) + return WRONG; + for (i = sp->typecnt - 1; i >= 0; --i) { + if (sp->ttis[i].tt_isdst != yourtm.tm_isdst) + continue; + for (j = sp->typecnt - 1; j >= 0; --j) { + if (sp->ttis[j].tt_isdst == yourtm.tm_isdst) + continue; + if (ttunspecified(sp, j)) + continue; + newt = (t + sp->ttis[j].tt_utoff + - sp->ttis[i].tt_utoff); + if (! funcp(sp, &newt, offset, &mytm)) + continue; + if (tmcomp(&mytm, &yourtm) != 0) + continue; + if (mytm.tm_isdst != yourtm.tm_isdst) + continue; + /* + ** We have a match. + */ + t = newt; + goto label; + } + } + return WRONG; + } + label: + newt = t + saved_seconds; + if ((newt < t) != (saved_seconds < 0)) + return WRONG; + t = newt; + if (funcp(sp, &t, offset, tmp)) + *okayp = true; + return t; +} + +static time_t +time2(struct tm * const tmp, + struct tm *(*funcp)(struct state const *, time_t const *, + int_fast32_t, struct tm *), + struct state const *sp, + const int_fast32_t offset, + bool *okayp) +{ + time_t t; + + /* + ** First try without normalization of seconds + ** (in case tm_sec contains a value associated with a leap second). + ** If that fails, try with normalization of seconds. + */ + t = time2sub(tmp, funcp, sp, offset, okayp, false); + return *okayp ? t : time2sub(tmp, funcp, sp, offset, okayp, true); +} + +static time_t +time1(struct tm *const tmp, + struct tm *(*funcp)(struct state const *, time_t const *, + int_fast32_t, struct tm *), + struct state const *sp, + const int_fast32_t offset) +{ + register time_t t; + register int samei, otheri; + register int sameind, otherind; + register int i; + register int nseen; + char seen[TZ_MAX_TYPES]; + unsigned char types[TZ_MAX_TYPES]; + bool okay; + + if (tmp == NULL) { + errno = EINVAL; + return WRONG; + } + if (tmp->tm_isdst > 1) + tmp->tm_isdst = 1; + t = time2(tmp, funcp, sp, offset, &okay); + if (okay) + return t; + if (tmp->tm_isdst < 0) +#ifdef PCTS + /* + ** POSIX Conformance Test Suite code courtesy Grant Sullivan. + */ + tmp->tm_isdst = 0; /* reset to std and try again */ +#else + return t; +#endif /* !defined PCTS */ + /* + ** We're supposed to assume that somebody took a time of one type + ** and did some math on it that yielded a "struct tm" that's bad. + ** We try to divine the type they started from and adjust to the + ** type they need. + */ + if (sp == NULL) + return WRONG; + for (i = 0; i < sp->typecnt; ++i) + seen[i] = false; + nseen = 0; + for (i = sp->timecnt - 1; i >= 0; --i) + if (!seen[sp->types[i]] && !ttunspecified(sp, sp->types[i])) { + seen[sp->types[i]] = true; + types[nseen++] = sp->types[i]; + } + for (sameind = 0; sameind < nseen; ++sameind) { + samei = types[sameind]; + if (sp->ttis[samei].tt_isdst != tmp->tm_isdst) + continue; + for (otherind = 0; otherind < nseen; ++otherind) { + otheri = types[otherind]; + if (sp->ttis[otheri].tt_isdst == tmp->tm_isdst) + continue; + tmp->tm_sec += (sp->ttis[otheri].tt_utoff + - sp->ttis[samei].tt_utoff); + tmp->tm_isdst = !tmp->tm_isdst; + t = time2(tmp, funcp, sp, offset, &okay); + if (okay) + return t; + tmp->tm_sec -= (sp->ttis[otheri].tt_utoff + - sp->ttis[samei].tt_utoff); + tmp->tm_isdst = !tmp->tm_isdst; + } + } + return WRONG; +} + +static time_t +mktime_tzname(struct state *sp, struct tm *tmp, bool setname) +{ + if (sp) + return time1(tmp, localsub, sp, setname); + else { + gmtcheck(); + return time1(tmp, gmtsub, gmtptr, 0); + } +} + +#if NETBSD_INSPIRED + +time_t +mktime_z(struct state *sp, struct tm *tmp) +{ + return mktime_tzname(sp, tmp, false); +} + +#endif + +time_t +mktime(struct tm *tmp) +{ + time_t t; + int err = lock(); + if (err) { + errno = err; + return -1; + } + tzset_unlocked(); + t = mktime_tzname(lclptr, tmp, true); + unlock(); + return t; +} + +#if STD_INSPIRED +/* This function is obsolescent and may disappear in future releases. + Callers can instead use mktime. */ +time_t +timelocal(struct tm *tmp) +{ + if (tmp != NULL) + tmp->tm_isdst = -1; /* in case it wasn't initialized */ + return mktime(tmp); +} +#endif + +#ifndef EXTERN_TIMEOFF +# ifndef timeoff +# define timeoff my_timeoff /* Don't collide with OpenBSD 7.4 . */ +# endif +# define EXTERN_TIMEOFF static +#endif + +/* This function is obsolescent and may disappear in future releases. + Callers can instead use mktime_z with a fixed-offset zone. */ +EXTERN_TIMEOFF time_t +timeoff(struct tm *tmp, long offset) +{ + if (tmp) + tmp->tm_isdst = 0; + gmtcheck(); + return time1(tmp, gmtsub, gmtptr, offset); +} + +time_t +timegm(struct tm *tmp) +{ + time_t t; + struct tm tmcpy; + mktmcpy(&tmcpy, tmp); + tmcpy.tm_wday = -1; + t = timeoff(&tmcpy, 0); + if (0 <= tmcpy.tm_wday) + *tmp = tmcpy; + return t; +} + +static int_fast32_t +leapcorr(struct state const *sp, time_t t) +{ + register struct lsinfo const * lp; + register int i; + + i = sp->leapcnt; + while (--i >= 0) { + lp = &sp->lsis[i]; + if (t >= lp->ls_trans) + return lp->ls_corr; + } + return 0; +} From 7aef49638e48326fbf4f55be72ba4b266ec491f7 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Fri, 15 Nov 2024 19:44:03 +0800 Subject: [PATCH 03/45] feat:[TD-32642] add timezone logic --- contrib/CMakeLists.txt | 2 +- include/client/taos.h | 10 +- include/common/ttime.h | 2 +- include/os/osSocket.h | 5 +- include/os/osTime.h | 3 + include/os/osTimezone.h | 16 +- include/util/taoserror.h | 3 + include/util/tconfig.h | 3 +- include/util/tutil.h | 5 - source/client/inc/clientInt.h | 8 +- source/client/src/clientEnv.c | 1 + source/client/src/clientMain.c | 97 +++++++ source/client/test/clientTests.cpp | 155 +++++++++++ source/common/src/tglobal.c | 18 +- source/common/src/ttime.c | 6 +- source/common/test/commonTests.cpp | 6 +- source/dnode/mgmt/mgmt_dnode/src/dmHandle.c | 4 +- source/dnode/mnode/impl/src/mndMain.c | 2 +- source/dnode/mnode/impl/src/mndProfile.c | 22 +- source/libs/command/src/command.c | 2 +- source/libs/parser/src/parInsertSql.c | 2 +- source/libs/parser/src/parTranslater.c | 2 +- source/libs/scalar/src/sclvector.c | 2 +- source/os/src/osEnv.c | 11 +- source/os/src/osSocket.c | 3 +- source/os/src/osTime.c | 122 ++++++++ source/os/src/osTimezone.c | 290 ++++++-------------- source/util/src/tconfig.c | 16 +- source/util/src/terror.c | 3 + source/util/src/tutil.c | 19 -- 30 files changed, 547 insertions(+), 293 deletions(-) diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index e52a5b4120..1380f036da 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -635,7 +635,7 @@ if(${TD_LINUX} AND ${BUILD_WITH_S3}) endif() execute_process( - COMMAND make TZDIR=${SHARE_OUTPUT_PATH}/timezone all posix_only + COMMAND make TZDIR=${SHARE_OUTPUT_PATH}/timezone clean all posix_only WORKING_DIRECTORY "${TD_CONTRIB_DIR}/tz" ) diff --git a/include/client/taos.h b/include/client/taos.h index f820536983..57fb6cfc0a 100644 --- a/include/client/taos.h +++ b/include/client/taos.h @@ -64,6 +64,14 @@ typedef enum { TSDB_MAX_OPTIONS } TSDB_OPTION; +typedef enum { + TSDB_OPTION_CONNECTION_CHARSET, // charset, Same as the scope supported by the system + TSDB_OPTION_CONNECTION_TIMEZONE, // timezone, Same as the scope supported by the system + TSDB_OPTION_CONNECTION_USER_IP, // user ip + TSDB_OPTION_CONNECTION_USER_APP, // user app + TSDB_MAX_CONNECTION_OPTIONS = 100 +} TSDB_OPTION_CONNECTION; + typedef enum { TSDB_OPTION_CONNECT_CHARSET, TSDB_OPTION_CONNECT_TIMEZONE, @@ -166,7 +174,7 @@ typedef struct TAOS_STMT_OPTIONS { DLL_EXPORT void taos_cleanup(void); DLL_EXPORT int taos_options(TSDB_OPTION option, const void *arg, ...); -DLL_EXPORT int taos_options_connect(TAOS *taos, TSDB_OPTION_CONNECT option, const void *arg, ...); +DLL_EXPORT int taos_options_connection(TAOS *taos, TSDB_OPTION_CONNECTION option, const void *arg, ...); DLL_EXPORT setConfRet taos_set_config(const char *config); DLL_EXPORT int taos_init(void); DLL_EXPORT TAOS *taos_connect(const char *ip, const char *user, const char *pass, const char *db, uint16_t port); diff --git a/include/common/ttime.h b/include/common/ttime.h index 8df4ebb5df..65cbc34fc3 100644 --- a/include/common/ttime.h +++ b/include/common/ttime.h @@ -81,7 +81,7 @@ int32_t taosTimeCountIntervalForFill(int64_t skey, int64_t ekey, int64_t interva int32_t parseAbsoluteDuration(const char* token, int32_t tokenlen, int64_t* ts, char* unit, int32_t timePrecision); int32_t parseNatualDuration(const char* token, int32_t tokenLen, int64_t* duration, char* unit, int32_t timePrecision, bool negativeAllow); -int32_t taosParseTime(const char* timestr, int64_t* pTime, int32_t len, int32_t timePrec, int8_t dayligth); +int32_t taosParseTime(const char* timestr, int64_t* pTime, int32_t len, int32_t timePrec); void deltaToUtcInitOnce(); char getPrecisionUnit(int32_t precision); diff --git a/include/os/osSocket.h b/include/os/osSocket.h index 49acf285ee..ef5b700784 100644 --- a/include/os/osSocket.h +++ b/include/os/osSocket.h @@ -26,7 +26,6 @@ #define epoll_create EPOLL_CREATE_FUNC_TAOS_FORBID #define epoll_ctl EPOLL_CTL_FUNC_TAOS_FORBID #define epoll_wait EPOLL_WAIT_FUNC_TAOS_FORBID -#define inet_addr INET_ADDR_FUNC_TAOS_FORBID #define inet_ntoa INET_NTOA_FUNC_TAOS_FORBID #endif @@ -55,10 +54,11 @@ #define __BIG_ENDIAN BIG_ENDIAN #define __LITTLE_ENDIAN LITTLE_ENDIAN #define __PDP_ENDIAN PDP_ENDIAN - +#include #else #include #include +#include #if defined(_TD_DARWIN_64) #include @@ -163,7 +163,6 @@ int32_t taosBlockSIGPIPE(); int32_t taosGetIpv4FromFqdn(const char *fqdn, uint32_t *ip); int32_t taosGetFqdn(char *); void tinet_ntoa(char *ipstr, uint32_t ip); -uint32_t ip2uint(const char *const ip_addr); int32_t taosIgnSIGPIPE(); const char *taosInetNtoa(struct in_addr ipInt, char *dstStr, int32_t len); diff --git a/include/os/osTime.h b/include/os/osTime.h index 5d74146e9c..6142edcfa7 100644 --- a/include/os/osTime.h +++ b/include/os/osTime.h @@ -42,6 +42,7 @@ extern "C" { #define MILLISECOND_PER_SECOND ((int64_t)1000LL) #endif +#include "osTimezone.h" #define MILLISECOND_PER_MINUTE (MILLISECOND_PER_SECOND * 60) #define MILLISECOND_PER_HOUR (MILLISECOND_PER_MINUTE * 60) #define MILLISECOND_PER_DAY (MILLISECOND_PER_HOUR * 24) @@ -98,6 +99,8 @@ time_t taosMktime(struct tm *timep); int64_t user_mktime64(const uint32_t year, const uint32_t mon, const uint32_t day, const uint32_t hour, const uint32_t min, const uint32_t sec, int64_t time_zone); +struct tm *taosLocalTimeRz(timezone_t state, const time_t *timep, struct tm *result); +time_t taosMktimeRz(timezone_t state, struct tm *timep); #ifdef __cplusplus } #endif diff --git a/include/os/osTimezone.h b/include/os/osTimezone.h index cf3ca6a96a..52f7f661af 100644 --- a/include/os/osTimezone.h +++ b/include/os/osTimezone.h @@ -1160,13 +1160,7 @@ struct tzhead { # define TZDEFAULT "/etc/localtime" /* default zone */ #endif #ifndef TZDIR -# define TZDIR "/usr/local/share/timezone" /* TZif directory */ -#endif - -// If the error is in a third-party library, place this header file under the third-party library header file. -// When you want to use this feature, you should find or add the same function in the following section. -#ifndef ALLOW_FORBID_FUNC -#define tzset TZSET_FUNC_TAOS_FORBID +# define TZDIR "/Users/mingmingwanng/source_code/TDengine/debug/build/share/timezone" /* TZif directory */ #endif enum TdTimezone { @@ -1197,9 +1191,13 @@ enum TdTimezone { TdEastZone12 }; -int32_t taosGetSystemTimezone(char *outTimezone, enum TdTimezone *tsTimezone); -int32_t taosSetSystemTimezone(const char *inTimezone, char *outTimezone, int8_t *outDaylight, enum TdTimezone *tsTimezone); +void getTimezoneStr(char *tz); + +int32_t taosGetSystemTimezone(char *outTimezone); +int32_t taosSetGlobalTimezone(const char *tz); +int32_t taosFormatTimezoneStr(time_t t, const char* tzStr, timezone_t sp, char *outTimezoneStr); +int32_t taosIsValidateTimezone(const char *tz); #ifdef __cplusplus } #endif diff --git a/include/util/taoserror.h b/include/util/taoserror.h index 9a8b39b84c..f712a06bfd 100644 --- a/include/util/taoserror.h +++ b/include/util/taoserror.h @@ -1023,6 +1023,9 @@ int32_t taosGetErrSize(); #define TSDB_CODE_AUDIT_FAIL_SEND_AUDIT_RECORD TAOS_DEF_ERROR_CODE(0, 0x6101) #define TSDB_CODE_AUDIT_FAIL_GENERATE_JSON TAOS_DEF_ERROR_CODE(0, 0x6102) +//TIMEZONE +#define TSDB_CODE_INVALID_TIMEZONE TAOS_DEF_ERROR_CODE(0, 0x6200) + #ifdef __cplusplus } #endif diff --git a/include/util/tconfig.h b/include/util/tconfig.h index 3fc247982f..947ef67902 100644 --- a/include/util/tconfig.h +++ b/include/util/tconfig.h @@ -34,7 +34,8 @@ typedef enum { CFG_STYPE_APOLLO_URL, CFG_STYPE_ARG_LIST, CFG_STYPE_TAOS_OPTIONS, - CFG_STYPE_ALTER_CMD, + CFG_STYPE_ALTER_CLIENT_CMD, + CFG_STYPE_ALTER_SERVER_CMD, } ECfgSrcType; typedef enum { diff --git a/include/util/tutil.h b/include/util/tutil.h index 87710b091d..3d243f3e31 100644 --- a/include/util/tutil.h +++ b/include/util/tutil.h @@ -48,11 +48,6 @@ int32_t taosHexStrToByteArray(char hexstr[], char bytes[]); int32_t tintToHex(uint64_t val, char hex[]); int32_t titoa(uint64_t val, size_t radix, char str[]); -char *taosIpStr(uint32_t ipInt); -uint32_t ip2uint(const char *const ip_addr); -void taosIp2String(uint32_t ip, char *str); -void taosIpPort2String(uint32_t ip, uint16_t port, char *str); - void *tmemmem(const char *haystack, int hlen, const char *needle, int nlen); int32_t parseCfgReal(const char *str, float *out); diff --git a/source/client/inc/clientInt.h b/source/client/inc/clientInt.h index e651d5b37e..6da950df5d 100644 --- a/source/client/inc/clientInt.h +++ b/source/client/inc/clientInt.h @@ -149,11 +149,11 @@ typedef struct { } SWhiteListInfo; typedef struct { -// TIMEZONE *tz; - char charset[TD_LOCALE_LEN]; + timezone_t timezone; + char charset[TD_CHARSET_LEN]; char app[TSDB_APP_NAME_LEN]; uint32_t ip; -}optionInfo; +}SOptionInfo; typedef struct STscObj { char user[TSDB_USER_LEN]; @@ -177,6 +177,7 @@ typedef struct STscObj { SPassInfo passInfo; SWhiteListInfo whiteListInfo; STscNotifyInfo userDroppedInfo; + SOptionInfo optionInfo; } STscObj; typedef struct STscDbg { @@ -341,6 +342,7 @@ extern int32_t clientReqRefPool; extern int32_t clientConnRefPool; extern int32_t timestampDeltaLimit; extern int64_t lastClusterId; +extern SHashObj* pTimezoneMap; __async_send_cb_fn_t getMsgRspHandle(int32_t msgType); diff --git a/source/client/src/clientEnv.c b/source/client/src/clientEnv.c index 43b7382645..9e08e3e249 100644 --- a/source/client/src/clientEnv.c +++ b/source/client/src/clientEnv.c @@ -70,6 +70,7 @@ int64_t lastClusterId = 0; int32_t clientReqRefPool = -1; int32_t clientConnRefPool = -1; int32_t clientStop = -1; +SHashObj* pTimezoneMap = NULL; int32_t timestampDeltaLimit = 900; // s diff --git a/source/client/src/clientMain.c b/source/client/src/clientMain.c index 64631fd754..2ee094bc18 100644 --- a/source/client/src/clientMain.c +++ b/source/client/src/clientMain.c @@ -51,6 +51,103 @@ int taos_options(TSDB_OPTION option, const void *arg, ...) { atomic_store_32(&lock, 0); return ret; } + +static int32_t setConnectionOption(TAOS *taos, TSDB_OPTION_CONNECTION option, const char* val){ + if (taos == NULL) { + return TSDB_CODE_INVALID_PARA; + } + + if (option != TSDB_MAX_CONNECTION_OPTIONS && (option < TSDB_OPTION_CONNECTION_CHARSET && option > TSDB_OPTION_CONNECTION_USER_APP)){ + return TSDB_CODE_INVALID_PARA; + } + + STscObj *pObj = acquireTscObj(*(int64_t *)taos); + if (NULL == pObj) { + tscError("invalid parameter for %s", __func__); + return terrno; + } + + int32_t code = 0; + if (option == TSDB_OPTION_CONNECTION_CHARSET) { + if (val != NULL) { + if (!taosValidateEncodec(val)) { + code = terrno; + goto END; + } + tstrncpy(pObj->optionInfo.charset, val, TD_CHARSET_LEN); + }else{ + pObj->optionInfo.charset[0] = 0; + } + } else if (option == TSDB_OPTION_CONNECTION_TIMEZONE) { + if (val != NULL){ + if (strlen(val) == 0){ + code = TSDB_CODE_INVALID_PARA; + goto END; + } + timezone_t *tmp = taosHashGet(pTimezoneMap, val, strlen(val)); + if (tmp && *tmp){ + pObj->optionInfo.timezone = *tmp; + goto END; + } + + tscDebug("set timezone to %s", val); + timezone_t tz = tzalloc(val); + if (!tz) { + tscError("%s unknown timezone %s", __func__, val); + code = TAOS_SYSTEM_ERROR(errno); + goto END; + } + code = taosHashPut(pTimezoneMap, val, strlen(val), &tz, sizeof(timezone_t)); + if (code != 0){ + tzfree(tz); + goto END; + } + pObj->optionInfo.timezone = tz; + } else { + pObj->optionInfo.timezone = NULL; + } + } else if (option == TSDB_OPTION_CONNECTION_USER_APP) { + if (val != NULL) { + tstrncpy(pObj->optionInfo.app, val, TSDB_APP_NAME_LEN); + } else { + pObj->optionInfo.app[0] = 0; + } + } else if (option == TSDB_OPTION_CONNECTION_USER_IP) { + if (val != NULL) { + pObj->optionInfo.ip = inet_addr(val); + } else { + pObj->optionInfo.ip = 0; + } + } + +END: + releaseTscObj(*(int64_t *)taos); + return code; +} + +int taos_options_connection(TAOS *taos, TSDB_OPTION_CONNECTION option, const void *arg, ...){ + static int32_t lock_c = 0; + + for (int i = 1; atomic_val_compare_exchange_32(&lock_c, 0, 1) != 0; ++i) { + if (i % 1000 == 0) { + tscInfo("haven't acquire lock after spin %d times.", i); + (void)sched_yield(); + } + } + + if (pTimezoneMap == NULL){ + pTimezoneMap = taosHashInit(0, taosGetDefaultHashFunction(TSDB_DATA_TYPE_BINARY), false, HASH_ENTRY_LOCK); + if (pTimezoneMap == NULL) { + atomic_store_32(&lock_c, 0); + return terrno; + } + taosHashSetFreeFp(pTimezoneMap, (_hash_free_fn_t)tzfree); + } + int ret = setConnectionOption(taos, option, (const char *)arg); + atomic_store_32(&lock_c, 0); + return ret; +} + // this function may be called by user or system, or by both simultaneously. void taos_cleanup(void) { tscDebug("start to cleanup client environment"); diff --git a/source/client/test/clientTests.cpp b/source/client/test/clientTests.cpp index 307ef7e06f..125df8f6b0 100644 --- a/source/client/test/clientTests.cpp +++ b/source/client/test/clientTests.cpp @@ -1610,4 +1610,159 @@ TEST(clientCase, timezone_Test) { } } +time_t time_winter = 1731323281; // 2024-11-11 19:08:01+0800 +time_t time_summer = 1731323281 - 120 * 24 * 60 * 60; + +struct test_times +{ + const char *name; + time_t t; + const char *timezone; +} test_tz[] = { + {"", time_winter, " (UTC, +0000)"}, + {"America/New_York", time_winter, "America/New_York (EST, -0500)"}, // 2024-11-11 19:08:01+0800 + {"America/New_York", time_summer, "America/New_York (EDT, -0400)"}, + {"Asia/Kolkata", time_winter, "Asia/Kolkata (IST, +0530)"}, + {"Asia/Shanghai", time_winter, "Asia/Shanghai (CST, +0800)"}, + {"Europe/London", time_winter, "Europe/London (GMT, +0000)"}, + {"Europe/London", time_summer, "Europe/London (BST, +0100)"} +}; + +void timezone_str_test(const char* tz, time_t t, const char* tzStr) { + int code = setenv("TZ", tz, 1); + ASSERT(-1 != code); + tzset(); + + char str1[TD_TIMEZONE_LEN] = {0}; + ASSERT(taosFormatTimezoneStr(t, tz, NULL, str1) == 0); + ASSERT_STREQ(str1, tzStr); +} + +void timezone_rz_str_test(const char* tz, time_t t, const char* tzStr) { + timezone_t sp = tzalloc(tz); + ASSERT(sp); + + char str1[TD_TIMEZONE_LEN] = {0}; + ASSERT(taosFormatTimezoneStr(t, tz, sp, str1) == 0); + ASSERT_STREQ(str1, tzStr); + tzfree(sp); +} + +TEST(clientCase, format_timezone_Test) { + for (unsigned int i = 0; i < sizeof (test_tz) / sizeof (test_tz[0]); ++i){ + timezone_str_test(test_tz[i].name, test_tz[i].t, test_tz[i].timezone); + timezone_str_test(test_tz[i].name, test_tz[i].t, test_tz[i].timezone); + } +} + +TEST(clientCase, get_tz_Test) { + { + char tz[TD_TIMEZONE_LEN] = {0}; + getTimezoneStr(tz); + ASSERT_STREQ(tz, "Asia/Shanghai"); + +// getTimezoneStr(tz); +// ASSERT_STREQ(tz, "Asia/Shanghai"); +// +// getTimezoneStr(tz); +// ASSERT_STREQ(tz, "n/a"); + } +} + +struct { + const char * env; + time_t expected; +} test_mk[] = { + {"MST", 832935315}, + {"", 832910115}, + {":UTC", 832910115}, + {"UTC", 832910115}, + {"UTC0", 832910115} +}; + + +TEST(clientCase, mktime_Test){ + struct tm tm; + time_t t; + + memset (&tm, 0, sizeof (tm)); + tm.tm_isdst = 0; + tm.tm_year = 96; /* years since 1900 */ + tm.tm_mon = 4; + tm.tm_mday = 24; + tm.tm_hour = 3; + tm.tm_min = 55; + tm.tm_sec = 15; + + for (unsigned int i = 0; i < sizeof (test_mk) / sizeof (test_mk[0]); ++i) + { + setenv ("TZ", test_mk[i].env, 1); + t = taosMktime (&tm); + ASSERT (t == test_mk[i].expected); + } +} + +TEST(clientCase, mktime_rz_Test){ + struct tm tm; + time_t t; + + memset (&tm, 0, sizeof (tm)); + tm.tm_isdst = 0; + tm.tm_year = 96; /* years since 1900 */ + tm.tm_mon = 4; + tm.tm_mday = 24; + tm.tm_hour = 3; + tm.tm_min = 55; + tm.tm_sec = 15; + + for (unsigned int i = 0; i < sizeof (test_mk) / sizeof (test_mk[0]); ++i) + { + timezone_t tz = tzalloc(test_mk[i].env); + ASSERT(tz); + t = taosMktimeRz(tz, &tm); + ASSERT (t == test_mk[i].expected); + tzfree(tz); + } +} + +TEST(testCase, localtime_performance_Test) { + timezone_t sp = tzalloc("Asia/Shanghai"); + ASSERT(sp); + + int cnt = 10000000; + int times = 10; + int64_t time_localtime = 0; + int64_t time_localtime_rz = 0; +// int cnt = 1000000; + for (int i = 0; i < times; ++i) { + int64_t t1 = taosGetTimestampNs(); + for (int j = 0; j < cnt; ++j) { + time_t t = time_winter - j; + struct tm tm1; + ASSERT (taosLocalTime(&t, &tm1, NULL, 0)); + } + int64_t tmp = taosGetTimestampNs() - t1; + printf("localtime cost:%" PRId64 " ns, run %" PRId64 " times", tmp, cnt); + time_localtime += tmp/cnt; + + timezone; + printf("\n"); + + + + int64_t t2 = taosGetTimestampNs(); + for (int j = 0; j < cnt; ++j) { + time_t t = time_winter - j; + struct tm tm1; + ASSERT (taosLocalTimeRz(sp, &t, &tm1)); + } + tmp = taosGetTimestampNs() - t2; + printf("localtime_rz cost:%" PRId64 " ns, run %" PRId64 " times", tmp, cnt); + time_localtime_rz += tmp/cnt; + printf("\n\n"); + } + printf("average: localtime cost:%" PRId64 " ns, localtime_rz cost:%" PRId64 " ns", time_localtime/times, time_localtime_rz/times); + tzfree(sp); +} + #pragma GCC diagnostic pop diff --git a/source/common/src/tglobal.c b/source/common/src/tglobal.c index 834615bdaa..57b3a7d67c 100644 --- a/source/common/src/tglobal.c +++ b/source/common/src/tglobal.c @@ -652,6 +652,7 @@ static int32_t taosAddClientCfg(SConfig *pCfg) { static int32_t taosAddSystemCfg(SConfig *pCfg) { SysNameInfo info = taosGetSysNameInfo(); + (void)taosGetSystemTimezone(tsTimezoneStr); TAOS_CHECK_RETURN(cfgAddTimezone(pCfg, "timezone", tsTimezoneStr, CFG_SCOPE_BOTH, CFG_DYN_CLIENT)); TAOS_CHECK_RETURN(cfgAddLocale(pCfg, "locale", tsLocale, CFG_SCOPE_BOTH, CFG_DYN_CLIENT)); TAOS_CHECK_RETURN(cfgAddCharset(pCfg, "charset", tsCharset, CFG_SCOPE_BOTH, CFG_DYN_NONE)); @@ -1311,15 +1312,6 @@ static int32_t taosSetClientCfg(SConfig *pCfg) { static int32_t taosSetSystemCfg(SConfig *pCfg) { SConfigItem *pItem = NULL; - TAOS_CHECK_GET_CFG_ITEM(pCfg, pItem, "timezone"); - if (0 == strlen(pItem->str)) { - uError("timezone is not set"); - } else { - TAOS_CHECK_RETURN(osSetTimezone(pItem->str)); - uDebug("timezone format changed from %s to %s", pItem->str, tsTimezoneStr); - } - TAOS_CHECK_RETURN(cfgSetItem(pCfg, "timezone", tsTimezoneStr, pItem->stype, true)); - TAOS_CHECK_GET_CFG_ITEM(pCfg, pItem, "locale"); const char *locale = pItem->str; @@ -2239,13 +2231,7 @@ static int32_t taosCfgDynamicOptionsForClient(SConfig *pCfg, const char *name) { break; } case 't': { - if (strcasecmp("timezone", name) == 0) { - TAOS_CHECK_GOTO(osSetTimezone(pItem->str), &lino, _out); - uInfo("%s set from %s to %s", name, tsTimezoneStr, pItem->str); - - TAOS_CHECK_GOTO(cfgSetItem(pCfg, "timezone", tsTimezoneStr, pItem->stype, false), &lino, _out); - matched = true; - } else if (strcasecmp("tempDir", name) == 0) { + if (strcasecmp("tempDir", name) == 0) { uInfo("%s set from %s to %s", name, tsTempDir, pItem->str); tstrncpy(tsTempDir, pItem->str, PATH_MAX); TAOS_CHECK_GOTO(taosExpandDir(tsTempDir, tsTempDir, PATH_MAX), &lino, _out); diff --git a/source/common/src/ttime.c b/source/common/src/ttime.c index 638f0411b7..c18ac32e46 100644 --- a/source/common/src/ttime.c +++ b/source/common/src/ttime.c @@ -36,7 +36,7 @@ static int32_t parseTimezone(char* str, int64_t* tzOffset); static int32_t (*parseLocaltimeFp[])(char* timestr, int32_t len, int64_t* utime, int32_t timePrec, char delim) = { parseLocaltime, parseLocaltimeDst}; -int32_t taosParseTime(const char* timestr, int64_t* utime, int32_t len, int32_t timePrec, int8_t day_light) { +int32_t taosParseTime(const char* timestr, int64_t* utime, int32_t len, int32_t timePrec) { /* parse datatime string in with tz */ if (strnchr(timestr, 'T', len, false) != NULL) { if (checkTzPresent(timestr, len)) { @@ -532,7 +532,7 @@ int32_t convertStringToTimestamp(int16_t type, char* inputData, int64_t timePrec TAOS_RETURN(terrno); } (void)memcpy(newColData, varDataVal(inputData), charLen); - int32_t ret = taosParseTime(newColData, timeVal, charLen, (int32_t)timePrec, tsDaylight); + int32_t ret = taosParseTime(newColData, timeVal, charLen, (int32_t)timePrec); if (ret != TSDB_CODE_SUCCESS) { taosMemoryFree(newColData); TAOS_RETURN(TSDB_CODE_INVALID_TIMESTAMP); @@ -549,7 +549,7 @@ int32_t convertStringToTimestamp(int16_t type, char* inputData, int64_t timePrec TAOS_RETURN(TSDB_CODE_FAILED); } newColData[len] = 0; - int32_t ret = taosParseTime(newColData, timeVal, len, (int32_t)timePrec, tsDaylight); + int32_t ret = taosParseTime(newColData, timeVal, len, (int32_t)timePrec); if (ret != TSDB_CODE_SUCCESS) { taosMemoryFree(newColData); TAOS_RETURN(ret); diff --git a/source/common/test/commonTests.cpp b/source/common/test/commonTests.cpp index b85af42d1c..054c3d5f58 100644 --- a/source/common/test/commonTests.cpp +++ b/source/common/test/commonTests.cpp @@ -442,15 +442,15 @@ TEST(timeTest, timestamp2tm) { int64_t ts, tmp_ts = 0; struct STm tm; - ASSERT_EQ(TSDB_CODE_SUCCESS, taosParseTime(ts_str_ns, &ts, strlen(ts_str_ns), TSDB_TIME_PRECISION_NANO, 0)); + ASSERT_EQ(TSDB_CODE_SUCCESS, taosParseTime(ts_str_ns, &ts, strlen(ts_str_ns), TSDB_TIME_PRECISION_NANO)); test_timestamp_tm_conversion(ts, TSDB_TIME_PRECISION_NANO, 2023 - 1900, 9 /* mon start from 0*/, 12, 11, 29, 0, 775726171L); - ASSERT_EQ(TSDB_CODE_SUCCESS, taosParseTime(ts_str_us, &ts, strlen(ts_str_us), TSDB_TIME_PRECISION_MICRO, 0)); + ASSERT_EQ(TSDB_CODE_SUCCESS, taosParseTime(ts_str_us, &ts, strlen(ts_str_us), TSDB_TIME_PRECISION_MICRO)); test_timestamp_tm_conversion(ts, TSDB_TIME_PRECISION_MICRO, 2023 - 1900, 9 /* mon start from 0*/, 12, 11, 29, 0, 775726000L); - ASSERT_EQ(TSDB_CODE_SUCCESS, taosParseTime(ts_str_ms, &ts, strlen(ts_str_ms), TSDB_TIME_PRECISION_MILLI, 0)); + ASSERT_EQ(TSDB_CODE_SUCCESS, taosParseTime(ts_str_ms, &ts, strlen(ts_str_ms), TSDB_TIME_PRECISION_MILLI)); test_timestamp_tm_conversion(ts, TSDB_TIME_PRECISION_MILLI, 2023 - 1900, 9 /* mon start from 0*/, 12, 11, 29, 0, 775000000L); diff --git a/source/dnode/mgmt/mgmt_dnode/src/dmHandle.c b/source/dnode/mgmt/mgmt_dnode/src/dmHandle.c index 1446faab77..f65fe290a2 100644 --- a/source/dnode/mgmt/mgmt_dnode/src/dmHandle.c +++ b/source/dnode/mgmt/mgmt_dnode/src/dmHandle.c @@ -198,7 +198,7 @@ void dmSendStatusReq(SDnodeMgmt *pMgmt) { req.clusterCfg.monitorParas.tsSlowLogThresholdTest = tsSlowLogThresholdTest; tstrncpy(req.clusterCfg.monitorParas.tsSlowLogExceptDb, tsSlowLogExceptDb, TSDB_DB_NAME_LEN); char timestr[32] = "1970-01-01 00:00:00.00"; - if (taosParseTime(timestr, &req.clusterCfg.checkTime, (int32_t)strlen(timestr), TSDB_TIME_PRECISION_MILLI, 0) != 0) { + if (taosParseTime(timestr, &req.clusterCfg.checkTime, (int32_t)strlen(timestr), TSDB_TIME_PRECISION_MILLI) != 0) { dError("failed to parse time since %s", tstrerror(code)); } memcpy(req.clusterCfg.timezone, tsTimezoneStr, TD_TIMEZONE_LEN); @@ -356,7 +356,7 @@ int32_t dmProcessConfigReq(SDnodeMgmt *pMgmt, SRpcMsg *pMsg) { SConfig *pCfg = taosGetCfg(); - code = cfgSetItem(pCfg, cfgReq.config, cfgReq.value, CFG_STYPE_ALTER_CMD, true); + code = cfgSetItem(pCfg, cfgReq.config, cfgReq.value, CFG_STYPE_ALTER_SERVER_CMD, true); if (code != 0) { if (strncasecmp(cfgReq.config, "resetlog", strlen("resetlog")) == 0) { code = 0; diff --git a/source/dnode/mnode/impl/src/mndMain.c b/source/dnode/mnode/impl/src/mndMain.c index 08ebf52ec6..c2edf6d6d8 100644 --- a/source/dnode/mnode/impl/src/mndMain.c +++ b/source/dnode/mnode/impl/src/mndMain.c @@ -713,7 +713,7 @@ SMnode *mndOpen(const char *path, const SMnodeOpt *pOption) { } char timestr[24] = "1970-01-01 00:00:00.00"; - code = taosParseTime(timestr, &pMnode->checkTime, (int32_t)strlen(timestr), TSDB_TIME_PRECISION_MILLI, 0); + code = taosParseTime(timestr, &pMnode->checkTime, (int32_t)strlen(timestr), TSDB_TIME_PRECISION_MILLI); if (code < 0) { mError("failed to parse time since %s", tstrerror(code)); (void)taosThreadRwlockDestroy(&pMnode->lock); diff --git a/source/dnode/mnode/impl/src/mndProfile.c b/source/dnode/mnode/impl/src/mndProfile.c index a1ffee9b06..fd44e223b9 100644 --- a/source/dnode/mnode/impl/src/mndProfile.c +++ b/source/dnode/mnode/impl/src/mndProfile.c @@ -232,7 +232,7 @@ static int32_t mndProcessConnectReq(SRpcMsg *pReq) { SConnObj *pConn = NULL; int32_t code = 0; SConnectReq connReq = {0}; - char ip[24] = {0}; + char ip[TD_IP_LEN] = {0}; const STraceId *trace = &pReq->info.traceId; if ((code = tDeserializeSConnectReq(pReq->pCont, pReq->contLen, &connReq)) != 0) { @@ -244,7 +244,7 @@ static int32_t mndProcessConnectReq(SRpcMsg *pReq) { goto _OVER; } - taosIp2String(pReq->info.conn.clientIp, ip); + tinet_ntoa(ip, pReq->info.conn.clientIp); if ((code = mndCheckOperPrivilege(pMnode, pReq->info.conn.user, MND_OPER_CONNECT)) != 0) { mGError("user:%s, failed to login from %s since %s", pReq->info.conn.user, ip, tstrerror(code)); goto _OVER; @@ -896,9 +896,10 @@ static int32_t mndRetrieveConns(SRpcMsg *pReq, SShowObj *pShow, SSDataBlock *pBl return code; } - char endpoint[TSDB_IPv4ADDR_LEN + 6 + VARSTR_HEADER_SIZE] = {0}; - (void)sprintf(&endpoint[VARSTR_HEADER_SIZE], "%s:%d", taosIpStr(pConn->ip), pConn->port); - varDataLen(endpoint) = strlen(&endpoint[VARSTR_HEADER_SIZE]); + char endpoint[TD_IP_LEN + 6 + VARSTR_HEADER_SIZE] = {0}; + tinet_ntoa(varDataVal(endpoint), pConn->ip); + (void)sprintf(varDataVal(endpoint) + strlen(varDataVal(endpoint)), ":%d", pConn->port); + varDataLen(endpoint) = strlen(varDataVal(endpoint)); pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); code = colDataSetVal(pColInfo, numOfRows, (const char *)endpoint, false); if (code != 0) { @@ -1006,8 +1007,9 @@ static int32_t packQueriesIntoBlock(SShowObj *pShow, SConnObj *pConn, SSDataBloc return code; } - char endpoint[TSDB_IPv4ADDR_LEN + 6 + VARSTR_HEADER_SIZE] = {0}; - (void)sprintf(&endpoint[VARSTR_HEADER_SIZE], "%s:%d", taosIpStr(pConn->ip), pConn->port); + char endpoint[TD_IP_LEN + 6 + VARSTR_HEADER_SIZE] = {0}; + tinet_ntoa(varDataVal(endpoint), pConn->ip); + (void)sprintf(varDataVal(endpoint) + strlen(varDataVal(endpoint)), ":%d", pConn->port); varDataLen(endpoint) = strlen(&endpoint[VARSTR_HEADER_SIZE]); pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); code = colDataSetVal(pColInfo, curRowIndex, (const char *)endpoint, false); @@ -1165,9 +1167,9 @@ static int32_t mndRetrieveApps(SRpcMsg *pReq, SShowObj *pShow, SSDataBlock *pBlo return code; } - char ip[TSDB_IPv4ADDR_LEN + 6 + VARSTR_HEADER_SIZE] = {0}; - (void)sprintf(&ip[VARSTR_HEADER_SIZE], "%s", taosIpStr(pApp->ip)); - varDataLen(ip) = strlen(&ip[VARSTR_HEADER_SIZE]); + char ip[TD_IP_LEN + VARSTR_HEADER_SIZE] = {0}; + tinet_ntoa(varDataVal(ip), pApp->ip); + varDataLen(ip) = strlen(varDataVal(ip)); pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); code = colDataSetVal(pColInfo, numOfRows, (const char *)ip, false); if (code != 0) { diff --git a/source/libs/command/src/command.c b/source/libs/command/src/command.c index 716296345f..62cdd5d7ab 100644 --- a/source/libs/command/src/command.c +++ b/source/libs/command/src/command.c @@ -901,7 +901,7 @@ static int32_t execAlterLocal(SAlterLocalStmt* pStmt) { return terrno; } - if (cfgSetItem(tsCfg, pStmt->config, pStmt->value, CFG_STYPE_ALTER_CMD, true)) { + if (cfgSetItem(tsCfg, pStmt->config, pStmt->value, CFG_STYPE_ALTER_CLIENT_CMD, true)) { return terrno; } diff --git a/source/libs/parser/src/parInsertSql.c b/source/libs/parser/src/parInsertSql.c index 1c26a7c70e..81deefcaf1 100644 --- a/source/libs/parser/src/parInsertSql.c +++ b/source/libs/parser/src/parInsertSql.c @@ -267,7 +267,7 @@ static int parseTimestampOrInterval(const char** end, SToken* pToken, int16_t ti } } else { // parse the RFC-3339/ISO-8601 timestamp format string *isTs = true; - if (taosParseTime(pToken->z, ts, pToken->n, timePrec, tsDaylight) != TSDB_CODE_SUCCESS) { + if (taosParseTime(pToken->z, ts, pToken->n, timePrec) != TSDB_CODE_SUCCESS) { if ((pToken->n == 0) || (pToken->type != TK_NK_STRING && pToken->type != TK_NK_HEX && pToken->type != TK_NK_BIN)) { return buildSyntaxErrMsg(pMsgBuf, "invalid timestamp format", pToken->z); diff --git a/source/libs/parser/src/parTranslater.c b/source/libs/parser/src/parTranslater.c index e475d34055..824764a9f6 100755 --- a/source/libs/parser/src/parTranslater.c +++ b/source/libs/parser/src/parTranslater.c @@ -1949,7 +1949,7 @@ static int32_t parseTimeFromValueNode(STranslateContext* pCxt, SValueNode* pVal) return TSDB_CODE_SUCCESS; } else if (IS_VAR_DATA_TYPE(pVal->node.resType.type) || TSDB_DATA_TYPE_TIMESTAMP == pVal->node.resType.type) { if (TSDB_CODE_SUCCESS == taosParseTime(pVal->literal, &pVal->datum.i, pVal->node.resType.bytes, - pVal->node.resType.precision, tsDaylight)) { + pVal->node.resType.precision)) { return TSDB_CODE_SUCCESS; } char* pEnd = NULL; diff --git a/source/libs/scalar/src/sclvector.c b/source/libs/scalar/src/sclvector.c index 8db0562c63..7e37d00bfe 100644 --- a/source/libs/scalar/src/sclvector.c +++ b/source/libs/scalar/src/sclvector.c @@ -242,7 +242,7 @@ int32_t getVectorBigintValueFn(int32_t srcType, _getBigintValue_fn_t *p) { static FORCE_INLINE int32_t varToTimestamp(char *buf, SScalarParam *pOut, int32_t rowIndex, int32_t *overflow) { int64_t value = 0; int32_t code = TSDB_CODE_SUCCESS; - if (taosParseTime(buf, &value, strlen(buf), pOut->columnData->info.precision, tsDaylight) != TSDB_CODE_SUCCESS) { + if (taosParseTime(buf, &value, strlen(buf), pOut->columnData->info.precision) != TSDB_CODE_SUCCESS) { value = 0; code = TSDB_CODE_SCALAR_CONVERT_ERROR; } diff --git a/source/os/src/osEnv.c b/source/os/src/osEnv.c index 05c9936c2e..1550e75432 100644 --- a/source/os/src/osEnv.c +++ b/source/os/src/osEnv.c @@ -28,7 +28,6 @@ char tsTimezoneStr[TD_TIMEZONE_LEN] = {0}; enum TdTimezone tsTimezone = TdZeroZone; char tsLocale[TD_LOCALE_LEN] = {0}; char tsCharset[TD_CHARSET_LEN] = {0}; -int8_t tsDaylight = 0; bool tsEnableCoreFile = 1; int64_t tsPageSizeKB = 0; int64_t tsOpenMax = 0; @@ -49,14 +48,6 @@ int32_t osDefaultInit() { taosSeedRand(taosSafeRand()); taosGetSystemLocale(tsLocale, tsCharset); - code = taosGetSystemTimezone(tsTimezoneStr, &tsTimezone); - if(code != 0) { - return code; - } - if (strlen(tsTimezoneStr) > 0) { // ignore empty timezone - if ((code = taosSetSystemTimezone(tsTimezoneStr, tsTimezoneStr, &tsDaylight, &tsTimezone)) != TSDB_CODE_SUCCESS) - return code; - } taosGetSystemInfo(); @@ -124,7 +115,7 @@ bool osDataSpaceSufficient() { return tsDataSpace.size.avail > tsDataSpace.reser bool osTempSpaceSufficient() { return tsTempSpace.size.avail > tsTempSpace.reserved; } -int32_t osSetTimezone(const char *tz) { return taosSetSystemTimezone(tz, tsTimezoneStr, &tsDaylight, &tsTimezone); } +int32_t osSetTimezone(const char *tz) { return taosSetGlobalTimezone(tz); } void osSetSystemLocale(const char *inLocale, const char *inCharSet) { (void)memcpy(tsLocale, inLocale, strlen(inLocale) + 1); diff --git a/source/os/src/osSocket.c b/source/os/src/osSocket.c index 5f983d5480..d1f43ea05f 100644 --- a/source/os/src/osSocket.c +++ b/source/os/src/osSocket.c @@ -384,7 +384,8 @@ int32_t taosGetFqdn(char *fqdn) { } void tinet_ntoa(char *ipstr, uint32_t ip) { - (void)snprintf(ipstr, TD_IP_LEN, "%d.%d.%d.%d", ip & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, ip >> 24); + unsigned char *bytes = (unsigned char *) &ip; + (void)snprintf(ipstr, TD_IP_LEN, "%d.%d.%d.%d", bytes[0], bytes[1], bytes[2], bytes[3]); } int32_t taosIgnSIGPIPE() { diff --git a/source/os/src/osTime.c b/source/os/src/osTime.c index d4d9936154..3798a96e9d 100644 --- a/source/os/src/osTime.c +++ b/source/os/src/osTime.c @@ -539,6 +539,128 @@ struct tm *taosLocalTime(const time_t *timep, struct tm *result, char *buf, int3 return result; } +time_t taosMktimeRz(timezone_t state, struct tm *timep) { +#ifdef WINDOWS + #if 0 + struct tm tm1 = {0}; + LARGE_INTEGER t; + FILETIME f; + SYSTEMTIME s; + FILETIME ff; + SYSTEMTIME ss; + LARGE_INTEGER offset; + + time_t tt = 0; + localtime_s(&tm1, &tt); + ss.wYear = tm1.tm_year + 1900; + ss.wMonth = tm1.tm_mon + 1; + ss.wDay = tm1.tm_mday; + ss.wHour = tm1.tm_hour; + ss.wMinute = tm1.tm_min; + ss.wSecond = tm1.tm_sec; + ss.wMilliseconds = 0; + SystemTimeToFileTime(&ss, &ff); + offset.QuadPart = ff.dwHighDateTime; + offset.QuadPart <<= 32; + offset.QuadPart |= ff.dwLowDateTime; + + s.wYear = timep->tm_year + 1900; + s.wMonth = timep->tm_mon + 1; + s.wDay = timep->tm_mday; + s.wHour = timep->tm_hour; + s.wMinute = timep->tm_min; + s.wSecond = timep->tm_sec; + s.wMilliseconds = 0; + SystemTimeToFileTime(&s, &f); + t.QuadPart = f.dwHighDateTime; + t.QuadPart <<= 32; + t.QuadPart |= f.dwLowDateTime; + + t.QuadPart -= offset.QuadPart; + return (time_t)(t.QuadPart / 10000000); +#else + time_t result = mktime(timep); + if (result != -1) { + return result; + } +#ifdef _MSC_VER +#if _MSC_VER >= 1900 + int64_t tz = _timezone; +#endif +#endif + return user_mktime64(timep->tm_year + 1900, timep->tm_mon + 1, timep->tm_mday, timep->tm_hour, timep->tm_min, + timep->tm_sec, tz); +#endif +#else + time_t r = mktime_z(state, timep); + if (r == (time_t)-1) { + terrno = TAOS_SYSTEM_ERROR(errno); + } + return r; +#endif +} + +struct tm *taosLocalTimeRz(timezone_t state, const time_t *timep, struct tm *result) { + struct tm *res = NULL; + if (timep == NULL || result == NULL) { + return NULL; + } +#ifdef WINDOWS + if (*timep < -2208988800LL) { + if (buf != NULL) { + snprintf(buf, bufSize, "NaN"); + } + return NULL; + } else if (*timep < 0) { + SYSTEMTIME ss, s; + FILETIME ff, f; + + LARGE_INTEGER offset; + struct tm tm1; + time_t tt = 0; + if (localtime_s(&tm1, &tt) != 0) { + if (buf != NULL) { + snprintf(buf, bufSize, "NaN"); + } + return NULL; + } + ss.wYear = tm1.tm_year + 1900; + ss.wMonth = tm1.tm_mon + 1; + ss.wDay = tm1.tm_mday; + ss.wHour = tm1.tm_hour; + ss.wMinute = tm1.tm_min; + ss.wSecond = tm1.tm_sec; + ss.wMilliseconds = 0; + SystemTimeToFileTime(&ss, &ff); + offset.QuadPart = ff.dwHighDateTime; + offset.QuadPart <<= 32; + offset.QuadPart |= ff.dwLowDateTime; + offset.QuadPart += *timep * 10000000; + f.dwLowDateTime = offset.QuadPart & 0xffffffff; + f.dwHighDateTime = (offset.QuadPart >> 32) & 0xffffffff; + FileTimeToSystemTime(&f, &s); + result->tm_sec = s.wSecond; + result->tm_min = s.wMinute; + result->tm_hour = s.wHour; + result->tm_mday = s.wDay; + result->tm_mon = s.wMonth - 1; + result->tm_year = s.wYear - 1900; + result->tm_wday = s.wDayOfWeek; + result->tm_yday = 0; + result->tm_isdst = 0; + } else { + if (localtime_s(result, timep) != 0) { + if (buf != NULL) { + snprintf(buf, bufSize, "NaN"); + } + return NULL; + } + } +#else + return localtime_rz(state, timep, result); +#endif +} + static int isLeapYear(time_t year) { if (year % 4) return 0; diff --git a/source/os/src/osTimezone.c b/source/os/src/osTimezone.c index f9a9dac85e..1b54f3a42c 100644 --- a/source/os/src/osTimezone.c +++ b/source/os/src/osTimezone.c @@ -740,8 +740,6 @@ char *tz_win[554][2] = {{"Asia/Shanghai", "China Standard Time"}, #include #endif -static int isdst_now = 0; - void parseTimeStr(char *p, char to[5]) { for (int i = 0; i < 5; ++i) { if (strlen(p) > i) { @@ -756,27 +754,16 @@ void parseTimeStr(char *p, char to[5]) { } } -int32_t taosSetSystemTimezone(const char *inTimezoneStr, char *outTimezoneStr, int8_t *outDaylight, - enum TdTimezone *tsTimezone) { - if (inTimezoneStr == NULL || inTimezoneStr[0] == 0) { - terrno = TSDB_CODE_INVALID_PARA; - return terrno; - } +int32_t taosIsValidateTimezone(const char *tz) { + return true; +} - int32_t code = TSDB_CODE_SUCCESS; - size_t len = strlen(inTimezoneStr); - if (len >= TD_TIMEZONE_LEN) { +int32_t taosSetGlobalTimezone(const char *tz) { + if (tz == NULL || tz[0] == 0) { terrno = TSDB_CODE_INVALID_PARA; return terrno; } - char buf[TD_TIMEZONE_LEN] = {0}; - for (int32_t i = 0; i < len; i++) { - if (inTimezoneStr[i] == ' ' || inTimezoneStr[i] == '(') { - buf[i] = 0; - break; - } - buf[i] = inTimezoneStr[i]; - } + int32_t code = TSDB_CODE_SUCCESS; #ifdef WINDOWS char winStr[TD_LOCALE_LEN * 2]; @@ -825,44 +812,100 @@ int32_t taosSetSystemTimezone(const char *inTimezoneStr, char *outTimezoneStr, i if (outTimezoneStr != inTimezoneStr) { tstrncpy(outTimezoneStr, inTimezoneStr, TD_TIMEZONE_LEN); } - *outDaylight = 0; - -#elif defined(_TD_DARWIN_64) - - code = setenv("TZ", buf, 1); - if (-1 == code) { - terrno = TAOS_SYSTEM_ERROR(errno); - return terrno; - } - tzset(); - int32_t tz = (int32_t)((-timezone * MILLISECOND_PER_SECOND) / MILLISECOND_PER_HOUR); - *tsTimezone = tz; - tz += isdst_now; - - snprintf(outTimezoneStr, TD_TIMEZONE_LEN, "%s (%s, %s%02d00)", buf, tzname[isdst_now], tz >= 0 ? "+" : "-", abs(tz)); - *outDaylight = isdst_now; - +// *outDaylight = 0; #else - code = setenv("TZ", buf, 1); + code = setenv("TZ", tz, 1); if (-1 == code) { terrno = TAOS_SYSTEM_ERROR(errno); return terrno; } tzset(); - int32_t tz = (int32_t)((-timezone * MILLISECOND_PER_SECOND) / MILLISECOND_PER_HOUR); - *tsTimezone = tz; - tz += isdst_now; - (void)snprintf(outTimezoneStr, TD_TIMEZONE_LEN, "%s (%s, %s%02d00)", buf, tzname[isdst_now], tz >= 0 ? "+" : "-", abs(tz)); - *outDaylight = isdst_now; + time_t tx1 = taosGetTimestampSec(); + return taosFormatTimezoneStr(tx1, tz, NULL, tsTimezoneStr); #endif - return code; } -int32_t taosGetSystemTimezone(char *outTimezoneStr, enum TdTimezone *tsTimezone) { - int32_t code = 0; +int32_t taosFormatTimezoneStr(time_t t, const char* tz, timezone_t sp, char *outTimezoneStr){ + struct tm tm1; + if (sp == NULL) { + if (taosLocalTime(&t, &tm1, NULL, 0) == NULL) { + uError("failed to get local time"); + return TSDB_CODE_TIME_ERROR; + } + } else { + if (taosLocalTimeRz(sp, &t, &tm1) == NULL) { + uError("failed to get rz time"); + return TSDB_CODE_TIME_ERROR; + } + } + + /* + * format example: + * + * Asia/Shanghai (CST, +0800) + * Europe/London (BST, +0100) + * n/a (UTC, +0000) + */ + + char str1[TD_TIMEZONE_LEN] = {0}; + if (strftime(str1, sizeof(str1), "%Z", &tm1) == 0){ + uError("failed to get timezone name"); + return TSDB_CODE_TIME_ERROR; + } + + char str2[TD_TIMEZONE_LEN] = {0}; + if (strftime(str2, sizeof(str2), "%z", &tm1) == 0){ + uError("failed to get timezone offset"); + return TSDB_CODE_TIME_ERROR; + } + (void)snprintf(outTimezoneStr, TD_TIMEZONE_LEN, "%s (%s, %s)", tz, str1, str2); + return 0; +} + +void getTimezoneStr(char *tz) { + do { + int n = readlink("/r/localtime", tz, TD_TIMEZONE_LEN - 1); + if (n < 0) { + uWarn("[tz] failed to readlink /etc/localtime, reason:%s", strerror(errno)); + break; + } + + char *zi = strstr(tz, "zoneinfo"); + if (zi == NULL) { + uWarn("[tz] failed to find zoneinfo in /etc/localtime"); + break; + } + zi += sizeof("zoneinfo"); + memcpy(tz, zi, TD_TIMEZONE_LEN - (zi - tz)); + goto END; + } while (0); + + + TdFilePtr pFile = taosOpenFile("/etc/timezone", TD_FILE_READ); + if (pFile == NULL) { + uWarn("[tz] failed to open /etc/timezone, reason:%s", strerror(errno)); + goto END; + } + int len = taosReadFile(pFile, tz, TD_TIMEZONE_LEN - 1); + TAOS_UNUSED(taosCloseFile(&pFile)); + if (len <= 0) { + uWarn("[tz] failed to read /etc/timezone, len:%d", len); + goto END; + } + if (tz[len - 1] == '\n') { + tz[len - 1] = '\0'; + } + +END: + if (tz[0] == '\0') { + memcpy(tz, "n/a", sizeof("n/a")); + } +} + +int32_t taosGetSystemTimezone(char *outTimezoneStr) { #ifdef WINDOWS char value[100]; char keyPath[100]; @@ -895,162 +938,11 @@ int32_t taosGetSystemTimezone(char *outTimezoneStr, enum TdTimezone *tsTimezone) } } return 0; -#elif defined(_TD_DARWIN_64) - char buf[4096] = {0}; - char *tz = NULL; - { - int n = readlink("/etc/localtime", buf, sizeof(buf)); - if (n < 0) { - return TSDB_CODE_TIME_ERROR; - } - buf[n] = '\0'; - - char *zi = strstr(buf, "zoneinfo"); - if (!zi) { - return TSDB_CODE_TIME_ERROR; - } - tz = zi + strlen("zoneinfo") + 1; - - code = setenv("TZ", tz, 1); - if (-1 == code) { - terrno = TAOS_SYSTEM_ERROR(errno); - return terrno; - } - tzset(); - } - - /* - * NOTE: do not remove it. - * Enforce set the correct daylight saving time(DST) flag according - * to current time - */ - time_t tx1 = taosGetTimestampSec(); - struct tm tm1; - if (taosLocalTime(&tx1, &tm1, NULL, 0) == NULL) { - return TSDB_CODE_TIME_ERROR; - } - daylight = tm1.tm_isdst; - isdst_now = tm1.tm_isdst; - - /* - * format example: - * - * Asia/Shanghai (CST, +0800) - * Europe/London (BST, +0100) - */ - snprintf(outTimezoneStr, TD_TIMEZONE_LEN, "%s (%s, %+03ld00)", tz, tm1.tm_isdst ? tzname[daylight] : tzname[0], - -timezone / 3600); - return 0; #else - - char buf[4096] = {0}; - char *tz = NULL; - { - int n = readlink("/etc/localtime", buf, sizeof(buf)-1); - if (n < 0) { - if (taosCheckExistFile("/etc/timezone")) { - /* - * NOTE: do not remove it. - * Enforce set the correct daylight saving time(DST) flag according - * to current time - */ - time_t tx1 = taosGetTimestampSec(); - struct tm tm1; - if(taosLocalTime(&tx1, &tm1, NULL, 0) == NULL) { - return TSDB_CODE_TIME_ERROR; - } - /* load time zone string from /etc/timezone */ - // FILE *f = fopen("/etc/timezone", "r"); - errno = 0; - TdFilePtr pFile = taosOpenFile("/etc/timezone", TD_FILE_READ); - char buf[68] = {0}; - if (pFile != NULL) { - int len = taosReadFile(pFile, buf, 64); - if (len < 0) { - TAOS_UNUSED(taosCloseFile(&pFile)); - return TSDB_CODE_TIME_ERROR; - } - - TAOS_UNUSED(taosCloseFile(&pFile)); - - buf[sizeof(buf) - 1] = 0; - char *lineEnd = strstr(buf, "\n"); - if (lineEnd != NULL) { - *lineEnd = 0; - } - - // for CentOS system, /etc/timezone does not exist. Ignore the TZ environment variables - if (strlen(buf) > 0) { - code = setenv("TZ", buf, 1); - if (-1 == code) { - terrno = TAOS_SYSTEM_ERROR(errno); - return terrno; - } - } - } - // get and set default timezone - tzset(); - /* - * get CURRENT time zone. - * system current time zone is affected by daylight saving time(DST) - * - * e.g., the local time zone of London in DST is GMT+01:00, - * otherwise is GMT+00:00 - */ - int32_t tz = (-timezone * MILLISECOND_PER_SECOND) / MILLISECOND_PER_HOUR; - *tsTimezone = tz; - tz += daylight; - - /* - * format example: - * - * Asia/Shanghai (CST, +0800) - * Europe/London (BST, +0100) - */ - (void)snprintf(outTimezoneStr, TD_TIMEZONE_LEN, "%s (%s, %s%02d00)", buf, tzname[daylight], tz >= 0 ? "+" : "-", - abs(tz)); - } else { - return TSDB_CODE_TIME_ERROR; - } - return 0; - } - buf[n] = '\0'; - - char *zi = strstr(buf, "zoneinfo"); - if (!zi) { - return TSDB_CODE_TIME_ERROR; - } - tz = zi + strlen("zoneinfo") + 1; - - code = setenv("TZ", tz, 1); - if (-1 == code) { - terrno = TAOS_SYSTEM_ERROR(errno); - return terrno; - } - tzset(); - } - - /* - * NOTE: do not remove it. - * Enforce set the correct daylight saving time(DST) flag according - * to current time - */ + char tz[TD_TIMEZONE_LEN] = {0}; + getTimezoneStr(tz); time_t tx1 = taosGetTimestampSec(); - struct tm tm1; - if(taosLocalTime(&tx1, &tm1, NULL, 0) == NULL) { - return TSDB_CODE_TIME_ERROR; - } - isdst_now = tm1.tm_isdst; - - /* - * format example: - * - * Asia/Shanghai (CST, +0800) - * Europe/London (BST, +0100) - */ - (void)snprintf(outTimezoneStr, TD_TIMEZONE_LEN, "%s (%s, %+03ld00)", tz, tm1.tm_isdst ? tzname[daylight] : tzname[0], - -timezone / 3600); - return 0; + return taosFormatTimezoneStr(tx1, tz, NULL, outTimezoneStr); #endif } diff --git a/source/util/src/tconfig.c b/source/util/src/tconfig.c index d6852b0566..96056e3678 100644 --- a/source/util/src/tconfig.c +++ b/source/util/src/tconfig.c @@ -245,6 +245,17 @@ static int32_t doSetConf(SConfigItem *pItem, const char *value, ECfgSrcType styp } static int32_t cfgSetTimezone(SConfigItem *pItem, const char *value, ECfgSrcType stype) { + if (stype == CFG_STYPE_ALTER_SERVER_CMD || (pItem->dynScope & CFG_DYN_CLIENT) == 0){ + uError("failed to config timezone, not support"); + TAOS_RETURN(TSDB_CODE_INVALID_CFG); + } + + if(!taosIsValidateTimezone(value)){ + uError("invalid timezone:%s", value); + TAOS_RETURN(TSDB_CODE_INVALID_TIMEZONE); + } + TAOS_CHECK_RETURN(osSetTimezone(value)); + TAOS_CHECK_RETURN(doSetConf(pItem, value, stype)); if (strlen(value) == 0) { uError("cfg:%s, type:%s src:%s, value:%s, skip to set timezone", pItem->name, cfgDtypeStr(pItem->dtype), @@ -252,7 +263,6 @@ static int32_t cfgSetTimezone(SConfigItem *pItem, const char *value, ECfgSrcType TAOS_RETURN(TSDB_CODE_SUCCESS); } - TAOS_CHECK_RETURN(osSetTimezone(value)); TAOS_RETURN(TSDB_CODE_SUCCESS); } @@ -634,6 +644,10 @@ const char *cfgStypeStr(ECfgSrcType type) { return "taos_options"; case CFG_STYPE_ENV_CMD: return "env_cmd"; + case CFG_STYPE_ALTER_CLIENT_CMD: + return "alter_client_cmd"; + case CFG_STYPE_ALTER_SERVER_CMD: + return "alter_server_cmd"; default: return "invalid"; } diff --git a/source/util/src/terror.c b/source/util/src/terror.c index d660edd0b8..e74157ef66 100644 --- a/source/util/src/terror.c +++ b/source/util/src/terror.c @@ -866,6 +866,9 @@ TAOS_DEFINE_ERROR(TSDB_CODE_UTIL_QUEUE_OUT_OF_MEMORY, "Queue out of memory TAOS_DEFINE_ERROR(TSDB_CODE_AUDIT_NOT_FORMAT_TO_JSON, "can't format to json") TAOS_DEFINE_ERROR(TSDB_CODE_AUDIT_FAIL_SEND_AUDIT_RECORD, "Failed to send out audit record") TAOS_DEFINE_ERROR(TSDB_CODE_AUDIT_FAIL_GENERATE_JSON, "Failed to generate json") + +//TIMEZONE +TAOS_DEFINE_ERROR(TSDB_CODE_INVALID_TIMEZONE, "Invalid timezone") #ifdef TAOS_ERROR_C }; #endif diff --git a/source/util/src/tutil.c b/source/util/src/tutil.c index 48338e7996..898112647d 100644 --- a/source/util/src/tutil.c +++ b/source/util/src/tutil.c @@ -416,25 +416,6 @@ int32_t taosHexStrToByteArray(char hexstr[], char bytes[]) { return 0; } -char *taosIpStr(uint32_t ipInt) { - static char ipStrArray[3][30]; - static int32_t ipStrIndex = 0; - - char *ipStr = ipStrArray[(ipStrIndex++) % 3]; - // sprintf(ipStr, "0x%x:%u.%u.%u.%u", ipInt, ipInt & 0xFF, (ipInt >> 8) & 0xFF, (ipInt >> 16) & 0xFF, (uint8_t)(ipInt - // >> 24)); - sprintf(ipStr, "%u.%u.%u.%u", ipInt & 0xFF, (ipInt >> 8) & 0xFF, (ipInt >> 16) & 0xFF, (uint8_t)(ipInt >> 24)); - return ipStr; -} - -void taosIp2String(uint32_t ip, char *str) { - sprintf(str, "%u.%u.%u.%u", ip & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, (uint8_t)(ip >> 24)); -} - -void taosIpPort2String(uint32_t ip, uint16_t port, char *str) { - sprintf(str, "%u.%u.%u.%u:%u", ip & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, (uint8_t)(ip >> 24), port); -} - size_t tstrncspn(const char *str, size_t size, const char *reject, size_t rsize) { if (rsize == 0 || rsize == 1) { char *p = strnchr(str, reject[0], size, false); From 69da972796b6279b3ddfa2df01a1986e74e04ec4 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Fri, 22 Nov 2024 18:31:51 +0800 Subject: [PATCH 04/45] feat:[TD-32642] add timezone logic --- include/common/ttime.h | 27 +- include/libs/function/function.h | 1 + include/libs/nodes/querynodes.h | 9 +- include/libs/parser/parser.h | 1 + include/os/osTime.h | 11 +- source/client/src/clientImpl.c | 3 +- source/client/src/clientMain.c | 3 +- source/client/test/CMakeLists.txt | 4 +- source/client/test/clientTests.cpp | 433 +++++++++---- source/common/src/tdatablock.c | 2 +- source/common/src/tglobal.c | 4 +- source/common/src/tmisce.c | 5 +- source/common/src/tname.c | 67 -- source/common/src/ttime.c | 172 +++--- source/common/test/commonTests.cpp | 10 +- source/dnode/mgmt/mgmt_dnode/src/dmHandle.c | 2 +- source/dnode/mnode/impl/src/mndMain.c | 2 +- source/libs/function/src/builtins.c | 8 +- source/libs/parser/inc/parUtil.h | 2 +- source/libs/parser/src/parAstCreater.c | 3 + source/libs/parser/src/parInsertSql.c | 30 +- source/libs/parser/src/parTranslater.c | 14 +- source/libs/scalar/src/filter.c | 36 -- source/libs/scalar/src/scalar.c | 8 +- source/libs/scalar/src/sclfunc.c | 55 +- source/libs/scalar/src/sclvector.c | 7 +- source/os/src/osTime.c | 302 +++------ source/os/src/osTimezone.c | 643 +++++++++++++++++++- source/os/test/osTimeTests.cpp | 23 +- source/util/src/tconfig.c | 16 +- source/util/src/tlog.c | 27 +- tests/system-test/2-query/timezone.py | 37 +- tests/system-test/2-query/timezone_conf.py | 34 ++ tools/shell/src/shellEngine.c | 2 +- utils/test/c/CMakeLists.txt | 9 + utils/test/c/timezone_test.c | 97 +++ utils/test/c/tmqDemo.c | 2 +- utils/test/c/tmqSim.c | 4 +- utils/tsim/src/simExe.c | 2 +- 39 files changed, 1438 insertions(+), 679 deletions(-) create mode 100644 tests/system-test/2-query/timezone_conf.py create mode 100644 utils/test/c/timezone_test.c diff --git a/include/common/ttime.h b/include/common/ttime.h index 65cbc34fc3..41dd0bf6be 100644 --- a/include/common/ttime.h +++ b/include/common/ttime.h @@ -58,19 +58,7 @@ static FORCE_INLINE int64_t taosGetTimestamp(int32_t precision) { * precision == TSDB_TIME_PRECISION_MILLI, it returns timestamp in millisecond. * precision == TSDB_TIME_PRECISION_NANO, it returns timestamp in nanosecond. */ -static FORCE_INLINE int64_t taosGetTimestampToday(int32_t precision) { - int64_t factor = (precision == TSDB_TIME_PRECISION_MILLI) ? 1000 - : (precision == TSDB_TIME_PRECISION_MICRO) ? 1000000 - : 1000000000; - time_t t = taosTime(NULL); - struct tm tm; - (void) taosLocalTime(&t, &tm, NULL, 0); //tztodo - tm.tm_hour = 0; - tm.tm_min = 0; - tm.tm_sec = 0; - - return (int64_t)taosMktime(&tm) * factor; -} +int64_t taosGetTimestampToday(int32_t precision, timezone_t tz); int64_t taosTimeAdd(int64_t t, int64_t duration, char unit, int32_t precision); @@ -81,13 +69,12 @@ int32_t taosTimeCountIntervalForFill(int64_t skey, int64_t ekey, int64_t interva int32_t parseAbsoluteDuration(const char* token, int32_t tokenlen, int64_t* ts, char* unit, int32_t timePrecision); int32_t parseNatualDuration(const char* token, int32_t tokenLen, int64_t* duration, char* unit, int32_t timePrecision, bool negativeAllow); -int32_t taosParseTime(const char* timestr, int64_t* pTime, int32_t len, int32_t timePrec); -void deltaToUtcInitOnce(); +int32_t taosParseTime(const char* timestr, int64_t* pTime, int32_t len, int32_t timePrec, timezone_t tz); char getPrecisionUnit(int32_t precision); int64_t convertTimePrecision(int64_t ts, int32_t fromPrecision, int32_t toPrecision); int32_t convertTimeFromPrecisionToUnit(int64_t time, int32_t fromPrecision, char toUnit, int64_t* pRes); -int32_t convertStringToTimestamp(int16_t type, char* inputData, int64_t timePrec, int64_t* timeVal); +int32_t convertStringToTimestamp(int16_t type, char* inputData, int64_t timePrec, int64_t* timeVal, timezone_t tz); int32_t getDuration(int64_t val, char unit, int64_t* result, int32_t timePrecision); int32_t taosFormatUtcTime(char* buf, int32_t bufLen, int64_t ts, int32_t precision); @@ -97,8 +84,8 @@ struct STm { int64_t fsec; // in NANOSECOND }; -int32_t taosTs2Tm(int64_t ts, int32_t precision, struct STm* tm); -int32_t taosTm2Ts(struct STm* tm, int64_t* ts, int32_t precision); +int32_t taosTs2Tm(int64_t ts, int32_t precision, struct STm* tm, timezone_t tz); +int32_t taosTm2Ts(struct STm* tm, int64_t* ts, int32_t precision, timezone_t tz); /// @brief convert a timestamp to a formatted string /// @param format the timestamp format, must null terminated @@ -107,7 +94,7 @@ int32_t taosTm2Ts(struct STm* tm, int64_t* ts, int32_t precision); /// formats array; If not NULL, [formats] will be used instead of [format] to skip parse formats again. /// @param out output buffer, should be initialized by memset /// @notes remember to free the generated formats -int32_t taosTs2Char(const char* format, SArray** formats, int64_t ts, int32_t precision, char* out, int32_t outLen); +int32_t taosTs2Char(const char* format, SArray** formats, int64_t ts, int32_t precision, char* out, int32_t outLen, timezone_t tz); /// @brief convert a formatted timestamp string to a timestamp /// @param format must null terminated /// @param [in, out] formats, see taosTs2Char @@ -115,7 +102,7 @@ int32_t taosTs2Char(const char* format, SArray** formats, int64_t ts, int32_t pr /// @retval 0 for success, otherwise error occured /// @notes remember to free the generated formats even when error occured int32_t taosChar2Ts(const char* format, SArray** formats, const char* tsStr, int64_t* ts, int32_t precision, char* errMsg, - int32_t errMsgLen); + int32_t errMsgLen, timezone_t tz); int32_t TEST_ts2char(const char* format, int64_t ts, int32_t precision, char* out, int32_t outLen); int32_t TEST_char2ts(const char* format, int64_t* ts, int32_t precision, const char* tsStr); diff --git a/include/libs/function/function.h b/include/libs/function/function.h index 51d9e752a4..cd3ad7597e 100644 --- a/include/libs/function/function.h +++ b/include/libs/function/function.h @@ -292,6 +292,7 @@ struct SScalarParam { void *param; // other parameter, such as meta handle from vnode, to extract table name/tag value int32_t numOfRows; int32_t numOfQualified; // number of qualified elements in the final results + timezone_t tz; }; #define cleanupResultRowEntry(p) p->initialized = false diff --git a/include/libs/nodes/querynodes.h b/include/libs/nodes/querynodes.h index 4763077ed9..c6d0869404 100644 --- a/include/libs/nodes/querynodes.h +++ b/include/libs/nodes/querynodes.h @@ -129,8 +129,9 @@ typedef struct SValueNode { double d; char* p; } datum; - int64_t typeData; - int8_t unit; + int64_t typeData; + int8_t unit; + timezone_t tz; } SValueNode; typedef struct SLeftValueNode { @@ -159,6 +160,7 @@ typedef struct SOperatorNode { EOperatorType opType; SNode* pLeft; SNode* pRight; + timezone_t tz; } SOperatorNode; typedef struct SLogicConditionNode { @@ -191,6 +193,8 @@ typedef struct SFunctionNode { int32_t originalFuncId; ETrimType trimType; bool dual; // whether select stmt without from stmt, true for without. +// char timezone[TD_TIMEZONE_LEN]; + timezone_t tz; } SFunctionNode; typedef struct STableNode { @@ -399,6 +403,7 @@ typedef struct SCaseWhenNode { SNode* pCase; SNode* pElse; SNodeList* pWhenThenList; + timezone_t tz; } SCaseWhenNode; typedef struct SWindowOffsetNode { diff --git a/include/libs/parser/parser.h b/include/libs/parser/parser.h index 832e4f8863..bfea9dbbe7 100644 --- a/include/libs/parser/parser.h +++ b/include/libs/parser/parser.h @@ -101,6 +101,7 @@ typedef struct SParseContext { int8_t biMode; SArray* pSubMetaList; setQueryFn setQueryFp; + timezone_t timezone; } SParseContext; int32_t qParseSql(SParseContext* pCxt, SQuery** pQuery); diff --git a/include/os/osTime.h b/include/os/osTime.h index 6142edcfa7..8367926dd6 100644 --- a/include/os/osTime.h +++ b/include/os/osTime.h @@ -92,15 +92,16 @@ static FORCE_INLINE int64_t taosGetMonoTimestampMs() { } char *taosStrpTime(const char *buf, const char *fmt, struct tm *tm); -struct tm *taosLocalTime(const time_t *timep, struct tm *result, char *buf, int32_t bufSize); -struct tm *taosLocalTimeNolock(struct tm *result, const time_t *timep, int dst); +struct tm *taosLocalTime(const time_t *timep, struct tm *result, char *buf, int32_t bufSize, timezone_t tz); +struct tm *taosGmTimeR(const time_t *timep, struct tm *result); +time_t taosTimeGm(struct tm *tmp); time_t taosTime(time_t *t); -time_t taosMktime(struct tm *timep); +time_t taosMktime(struct tm *timep, timezone_t tz); int64_t user_mktime64(const uint32_t year, const uint32_t mon, const uint32_t day, const uint32_t hour, const uint32_t min, const uint32_t sec, int64_t time_zone); -struct tm *taosLocalTimeRz(timezone_t state, const time_t *timep, struct tm *result); -time_t taosMktimeRz(timezone_t state, struct tm *timep); +//struct tm *taosLocalTimeRz(timezone_t state, const time_t *timep, struct tm *result); +//time_t taosMktimeRz(timezone_t state, struct tm *timep); #ifdef __cplusplus } #endif diff --git a/source/client/src/clientImpl.c b/source/client/src/clientImpl.c index 74fd4e13a7..f6ebc1f809 100644 --- a/source/client/src/clientImpl.c +++ b/source/client/src/clientImpl.c @@ -300,7 +300,8 @@ int32_t parseSql(SRequestObj* pRequest, bool topicQuery, SQuery** pQuery, SStmtC .svrVer = pTscObj->sVer, .nodeOffline = (pTscObj->pAppInfo->onlineDnodes < pTscObj->pAppInfo->totalDnodes), .isStmtBind = pRequest->isStmtBind, - .setQueryFp = setQueryRequest}; + .setQueryFp = setQueryRequest, + .timezone = pTscObj->optionInfo.timezone,}; cxt.mgmtEpSet = getEpSet_s(&pTscObj->pAppInfo->mgmtEp); int32_t code = catalogGetHandle(pTscObj->pAppInfo->clusterId, &cxt.pCatalog); diff --git a/source/client/src/clientMain.c b/source/client/src/clientMain.c index 2ee094bc18..c9e571b91b 100644 --- a/source/client/src/clientMain.c +++ b/source/client/src/clientMain.c @@ -1342,7 +1342,8 @@ int32_t createParseContext(const SRequestObj *pRequest, SParseContext **pCxt, SS .allocatorId = pRequest->allocatorRefId, .parseSqlFp = clientParseSql, .parseSqlParam = pWrapper, - .setQueryFp = setQueryRequest}; + .setQueryFp = setQueryRequest, + .timezone = pTscObj->optionInfo.timezone}; int8_t biMode = atomic_load_8(&((STscObj *)pTscObj)->biMode); (*pCxt)->biMode = biMode; return TSDB_CODE_SUCCESS; diff --git a/source/client/test/CMakeLists.txt b/source/client/test/CMakeLists.txt index 054b5af2b9..82c12653af 100644 --- a/source/client/test/CMakeLists.txt +++ b/source/client/test/CMakeLists.txt @@ -35,12 +35,12 @@ TARGET_INCLUDE_DIRECTORIES( PRIVATE "${TD_SOURCE_DIR}/source/client/inc" ) -IF(${TD_LINUX}) +#IF(${TD_LINUX}) add_test( NAME clientTest COMMAND clientTest ) -ENDIF () +#ENDIF () TARGET_INCLUDE_DIRECTORIES( tmqTest diff --git a/source/client/test/clientTests.cpp b/source/client/test/clientTests.cpp index 125df8f6b0..97d863b330 100644 --- a/source/client/test/clientTests.cpp +++ b/source/client/test/clientTests.cpp @@ -1489,127 +1489,352 @@ TEST(clientCase, sub_tb_mt_test) { } } -TEST(clientCase, timezone_Test) { - { - // taos_options( TSDB_OPTION_TIMEZONE, "UTC-8"); - int code = taos_options(TSDB_OPTION_TIMEZONE, "UTC-8"); - ASSERT_TRUE(code == 0); - TAOS* pConn = taos_connect("localhost", "root", "taosdata", NULL, 0); - ASSERT_NE(pConn, nullptr); +TAOS* getConnWithGlobalOption(const char *tz){ + int code = taos_options(TSDB_OPTION_TIMEZONE, tz); + ASSERT(code == 0); + TAOS* pConn = taos_connect("localhost", "root", "taosdata", NULL, 0); + ASSERT(pConn != nullptr); + return pConn; +} - TAOS_RES* pRes = taos_query(pConn, "drop database if exists db1"); - ASSERT_EQ(taos_errno(pRes), TSDB_CODE_SUCCESS); - taos_free_result(pRes); +TAOS* getConnWithOption(const char *tz){ + TAOS* pConn = taos_connect("localhost", "root", "taosdata", NULL, 0); + ASSERT(pConn != nullptr); + if (tz != NULL){ + int code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_TIMEZONE, tz); + ASSERT(code == 0); + } + return pConn; +} - pRes = taos_query(pConn, "create database db1"); - ASSERT_EQ(taos_errno(pRes), TSDB_CODE_SUCCESS); - taos_free_result(pRes); +void execQuery(TAOS* pConn, const char *sql){ + TAOS_RES* pRes = taos_query(pConn, sql); + ASSERT(taos_errno(pRes) == TSDB_CODE_SUCCESS); + taos_free_result(pRes); +} - pRes = taos_query(pConn, "create table db1.t1 (ts timestamp, v int)"); - ASSERT_EQ(taos_errno(pRes), TSDB_CODE_SUCCESS); - taos_free_result(pRes); +void execQueryFail(TAOS* pConn, const char *sql){ + TAOS_RES* pRes = taos_query(pConn, sql); + ASSERT(taos_errno(pRes) != TSDB_CODE_SUCCESS); + taos_free_result(pRes); +} - char sql[256] = {0}; - (void)sprintf(sql, "insert into db1.t1 values('2023-09-16 17:00:00', 1)"); - pRes = taos_query(pConn, sql); - ASSERT_EQ(taos_errno(pRes), TSDB_CODE_SUCCESS); - taos_free_result(pRes); +void checkRows(TAOS* pConn, const char *sql, int32_t expectedRows){ + TAOS_RES* pRes = taos_query(pConn, sql); + ASSERT(taos_errno(pRes) == TSDB_CODE_SUCCESS); + TAOS_ROW pRow = NULL; + int rows = 0; + while ((pRow = taos_fetch_row(pRes)) != NULL) { + rows++; + } + ASSERT(rows == expectedRows); + taos_free_result(pRes); +} - pRes = taos_query(pConn, "select * from db1.t1 where ts == '2023-09-16 17:00:00'"); - ASSERT_EQ(taos_errno(pRes), TSDB_CODE_SUCCESS); - - TAOS_ROW pRow = NULL; - TAOS_FIELD* pFields = taos_fetch_fields(pRes); - int32_t numOfFields = taos_num_fields(pRes); - - char str[512] = {0}; - int rows = 0; - while ((pRow = taos_fetch_row(pRes)) != NULL) { - rows++; +void check_timezone(TAOS* pConn, const char *sql, const char* tz){ + TAOS_RES *pRes = taos_query(pConn, sql); + ASSERT(taos_errno(pRes) == 0); + TAOS_ROW row = NULL; + while ((row = taos_fetch_row(pRes)) != NULL) { + if (strcmp((const char*)row[0], "timezone") == 0){ + ASSERT(strstr((const char*)row[1], tz) != NULL); } - ASSERT_TRUE(rows == 1); + } + taos_free_result(pRes); +} - taos_free_result(pRes); +void check_sql_result_partial(TAOS* pConn, const char *sql, const char* result){ + TAOS_RES *pRes = taos_query(pConn, sql); + ASSERT(taos_errno(pRes) == 0); + TAOS_ROW row = NULL; + while ((row = taos_fetch_row(pRes)) != NULL) { + ASSERT(strstr((const char*)row[0], result) != NULL); + } + taos_free_result(pRes); +} + +int64_t get_sql_result(TAOS* pConn, const char *sql){ + int64_t ts = 0; + TAOS_RES *pRes = taos_query(pConn, sql); + ASSERT(taos_errno(pRes) == 0); + TAOS_ROW row = NULL; + while ((row = taos_fetch_row(pRes)) != NULL) { + ts = *(int64_t*)row[0]; + } + taos_free_result(pRes); + return ts; +} + +void check_sql_result(TAOS* pConn, const char *sql, const char* result){ + TAOS_RES *pRes = taos_query(pConn, sql); + ASSERT(taos_errno(pRes) == 0); + TAOS_ROW row = NULL; + while ((row = taos_fetch_row(pRes)) != NULL) { + ASSERT (strcmp((const char*)row[0], result) == 0); + } + taos_free_result(pRes); +} + +void check_sql_result_integer(TAOS* pConn, const char *sql, int64_t result){ + TAOS_RES *pRes = taos_query(pConn, sql); + ASSERT(taos_errno(pRes) == 0); + TAOS_ROW row = NULL; + while ((row = taos_fetch_row(pRes)) != NULL) { + ASSERT (*(int64_t*)row[0] == result); + } + taos_free_result(pRes); +} + +void check_set_timezone(TAOS* optionFunc(const char *tz)){ + { + TAOS* pConn = optionFunc("UTC-8"); + check_timezone(pConn, "show local variables", "UTC-8"); + + execQuery(pConn, "drop database if exists db1"); + execQuery(pConn, "create database db1"); + execQuery(pConn, "create table db1.t1 (ts timestamp, v int)"); + + execQuery(pConn, "insert into db1.t1 values('2023-09-16 17:00:00', 1)"); + checkRows(pConn, "select * from db1.t1 where ts == '2023-09-16 17:00:00'", 1); taos_close(pConn); } { - // taos_options( TSDB_OPTION_TIMEZONE, "UTC+8"); - int code = taos_options(TSDB_OPTION_TIMEZONE, "UTC+8"); - ASSERT_TRUE(code == 0); - TAOS* pConn = taos_connect("localhost", "root", "taosdata", NULL, 0); - ASSERT_NE(pConn, nullptr); - - TAOS_RES* pRes = taos_query(pConn, "select * from db1.t1 where ts == '2023-09-16 01:00:00'"); - ASSERT_EQ(taos_errno(pRes), TSDB_CODE_SUCCESS); - - TAOS_ROW pRow = NULL; - TAOS_FIELD* pFields = taos_fetch_fields(pRes); - int32_t numOfFields = taos_num_fields(pRes); - - int rows = 0; - char str[512] = {0}; - while ((pRow = taos_fetch_row(pRes)) != NULL) { - rows++; - } - ASSERT_TRUE(rows == 1); - - taos_free_result(pRes); - - char sql[256] = {0}; - (void)sprintf(sql, "insert into db1.t1 values('2023-09-16 17:00:01', 1)"); - pRes = taos_query(pConn, sql); - ASSERT_EQ(taos_errno(pRes), TSDB_CODE_SUCCESS); - - taos_free_result(pRes); + TAOS* pConn = optionFunc("UTC+8"); + check_timezone(pConn, "show local variables", "UTC+8"); + checkRows(pConn, "select * from db1.t1 where ts == '2023-09-16 01:00:00'", 1); + execQuery(pConn, "insert into db1.t1 values('2023-09-16 17:00:01', 1)"); taos_close(pConn); } { - // taos_options( TSDB_OPTION_TIMEZONE, "UTC+0"); - int code = taos_options(TSDB_OPTION_TIMEZONE, "UTC+0"); - ASSERT_TRUE(code == 0); - TAOS* pConn = taos_connect("localhost", "root", "taosdata", NULL, 0); - ASSERT_NE(pConn, nullptr); - - TAOS_RES* pRes = taos_query(pConn, "select * from db1.t1 where ts == '2023-09-16 09:00:00'"); - ASSERT_EQ(taos_errno(pRes), TSDB_CODE_SUCCESS); - - TAOS_ROW pRow = NULL; - TAOS_FIELD* pFields = taos_fetch_fields(pRes); - int32_t numOfFields = taos_num_fields(pRes); - - int rows = 0; - char str[512] = {0}; - while ((pRow = taos_fetch_row(pRes)) != NULL) { - rows++; - } - ASSERT_TRUE(rows == 1); - taos_free_result(pRes); - - { - TAOS_RES* pRes = taos_query(pConn, "select * from db1.t1 where ts == '2023-09-17 01:00:01'"); - ASSERT_EQ(taos_errno(pRes), TSDB_CODE_SUCCESS); - - TAOS_ROW pRow = NULL; - TAOS_FIELD* pFields = taos_fetch_fields(pRes); - int32_t numOfFields = taos_num_fields(pRes); - - int rows = 0; - char str[512] = {0}; - while ((pRow = taos_fetch_row(pRes)) != NULL) { - rows++; - } - ASSERT_TRUE(rows == 1); - taos_free_result(pRes); - } + TAOS* pConn = optionFunc("UTC+0"); + check_timezone(pConn, "show local variables", "UTC+0"); + checkRows(pConn, "select * from db1.t1 where ts == '2023-09-16 09:00:00'", 1); + checkRows(pConn, "select * from db1.t1 where ts == '2023-09-17 01:00:01'", 1); taos_close(pConn); } } +TEST(clientCase, set_timezone_Test) { + check_set_timezone(getConnWithGlobalOption); + check_set_timezone(getConnWithOption); +} + +TEST(clientCase, alter_timezone_Test) { + TAOS* pConn = getConnWithGlobalOption("UTC-8"); + check_timezone(pConn, "show local variables", "UTC-8"); + + execQuery(pConn, "alter local 'timezone Asia/Kolkata'"); + check_timezone(pConn, "show local variables", "Asia/Kolkata"); + + execQueryFail(pConn, "alter dnode 1 'timezone Asia/Kolkata'"); +} + +struct insert_params +{ + const char *tz; + const char *tbname; + const char *t1; + const char *t2; +}; + +struct insert_params params1[] = { + {"UTC", "ntb", "2023-09-16 17:00:00", "2023-09-16 17:00:00+08:00"}, + {"UTC", "ctb1", "2023-09-16 17:00:00", "2023-09-16 17:00:00+08:00"}, +}; + +struct insert_params params2[] = { + {"UTC+9", "ntb", "2023-09-16 08:00:00", "2023-09-16 08:00:00-01:00"}, + {"UTC+9", "ctb1", "2023-09-16 08:00:00", "2023-09-16 11:00:00+02:00"}, +}; + +void do_insert(struct insert_params params){ + TAOS* pConn = getConnWithOption(params.tz); + char sql[1024] = {0}; + (void)snprintf(sql, sizeof(sql), "insert into db1.%s values('%s', '%s', 1)", params.tbname, params.t1, params.t2); + execQuery(pConn, sql); + taos_close(pConn); +} + +void do_select(struct insert_params params){ + TAOS* pConn = getConnWithOption(params.tz); + char sql[1024] = {0}; + (void)snprintf(sql, sizeof(sql), "select * from db1.%s where ts == '%s' and c1 == '%s'", params.tbname, params.t1, params.t2); + checkRows(pConn, sql, 1); + taos_close(pConn); +} + +// test insert string and integer to timestamp both normal table and child table(and tag) +TEST(clientCase, insert_with_timezone_Test) { + /* + * 1. prepare data, create db and tables + */ + TAOS* pConn1 = getConnWithOption("UTC+2"); + execQuery(pConn1, "drop database if exists db1"); + execQuery(pConn1, "create database db1"); + execQuery(pConn1, "create table db1.ntb (ts timestamp, c1 timestamp, c2 int)"); + execQuery(pConn1, "create table db1.stb (ts timestamp, c1 timestamp, c2 int) tags(t1 timestamp, t2 timestamp, t3 int)"); + execQuery(pConn1, "create table db1.ctb1 using db1.stb tags(\"2023-09-16 17:00:00+05:00\", \"2023-09-16 17:00:00\", 1)"); + execQuery(pConn1, "create table db1.ctb2 using db1.stb tags(1732178775000, 1732178775000, 1)"); + execQuery(pConn1, "insert into db1.ntb values(1732178775133, 1732178775133, 1)"); + execQuery(pConn1, "insert into db1.ctb1 values(1732178775133, 1732178775133, 1)"); + execQuery(pConn1, "insert into db1.ctb2 values(1732178775133, 1732178775133, 1)"); + + /* + * 2. test tag and timestamp with integer format + */ + TAOS* pConn2 = getConnWithOption("UTC-2"); + checkRows(pConn2, "select * from db1.stb where t1 == '2023-09-16 17:00:00+05:00' and t2 == '2023-09-16 21:00:00'", 1); + checkRows(pConn2, "select * from db1.stb where t1 == '2024-11-21 16:46:15+08:00' and t2 == '2024-11-21 09:46:15+01:00'", 1); + checkRows(pConn2, "select * from db1.ntb where ts == '2024-11-21 09:46:15.133+01:00' and c1 == '2024-11-21 10:46:15.133'", 1); + checkRows(pConn2, "select * from db1.ctb1 where ts == '2024-11-21 09:46:15.133+01:00' and c1 == '2024-11-21 10:46:15.133'", 1); + + check_sql_result(pConn2, "select TO_ISO8601(ts) from db1.ctb1", "2024-11-21T10:46:15.133+0200"); // 2024-01-01 23:00:00+0200 + + + /* + * 3. test timestamp with string format + */ + for (unsigned int i = 0; i < sizeof (params1) / sizeof (params1[0]); ++i){ + do_insert(params1[i]); + do_select(params1[i]); + do_select(params2[i]); + } + + /* + * 4. test NULL timezone, use default timezone UTC-8 + */ + TAOS* pConn3 = getConnWithOption(NULL); + checkRows(pConn3, "select * from db1.stb where t1 == '2023-09-16 20:00:00' and t2 == '2023-09-17 03:00:00'", 2); + checkRows(pConn3, "select * from db1.stb where t1 == 1732178775000 and t2 == 1732178775000", 1); + checkRows(pConn3, "select * from db1.ntb where ts == '2024-11-21 16:46:15.133' and c1 == '2024-11-21 16:46:15.133'", 1); + checkRows(pConn3, "select * from db1.ctb1 where ts == '2023-09-17 01:00:00' and c1 == '2023-09-16 17:00:00'", 1); + + /* + * 5. test multi connection with different timezone + */ + checkRows(pConn2, "select * from db1.ctb1 where ts == '2024-11-21 09:46:15.133+01:00' and c1 == '2024-11-21 10:46:15.133'", 1); + checkRows(pConn1, "select * from db1.ctb1 where ts == '2024-11-21 09:46:15.133+01:00' and c1 == '2024-11-21 06:46:15.133'", 1); + + taos_close(pConn1); + taos_close(pConn2); + taos_close(pConn3); +} + +TEST(clientCase, func_timezone_Test) { + TAOS* pConn = getConnWithGlobalOption("UTC+8"); + check_sql_result(pConn, "select timezone()", "UTC+8 (UTC, -0800)"); + taos_close(pConn); + + pConn = getConnWithOption("UTC-2"); + + execQuery(pConn, "drop database if exists db1"); + execQuery(pConn, "create database db1"); + execQuery(pConn, "create table db1.ntb (ts timestamp, c1 binary(32), c2 int)"); + execQuery(pConn, "insert into db1.ntb values(1704142800000, '2024-01-01 23:00:00', 1)"); // 2024-01-01 23:00:00+0200 + + // test timezone + check_sql_result(pConn, "select timezone()", "UTC-test (UTC, +0200)"); + + // test timetruncate + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 23:00:00', 1d, 0))", "2024-01-01T02:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 01:00:00', 1d, 0))", "2023-12-31T02:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 01:00:00+0300', 1d, 0))", "2023-12-31T02:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 01:00:00-0300', 1d, 0))", "2024-01-01T02:00:00.000+0200"); + + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-04 23:00:00', 1w, 0))", "2024-01-04T02:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-04 01:00:00', 1w, 0))", "2023-12-28T02:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-04 01:00:00+0300', 1w, 0))", "2023-12-28T02:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-04 01:00:00-0300', 1w, 0))", "2024-01-04T02:00:00.000+0200"); + + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 23:00:00', 1d, 1))", "2024-01-01T00:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 01:00:00', 1d, 1))", "2024-01-01T00:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 01:00:00+0500', 1d, 1))", "2023-12-31T00:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 01:00:00-0300', 1d, 1))", "2024-01-01T00:00:00.000+0200"); + + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-04 23:00:00', 1w, 1))", "2024-01-04T00:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-04 01:00:00', 1w, 1))", "2024-01-04T00:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-04 01:00:00+0500', 1w, 1))", "2023-12-28T00:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-04 01:00:00-0300', 1w, 1))", "2024-01-04T00:00:00.000+0200"); + + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE(1704142800000, 1d, 0))", "2024-01-01T02:00:00.000+0200"); // 2024-01-01 23:00:00+0200 + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE(ts, 1w, 1)) from db1.ntb", "2023-12-28T00:00:00.000+0200"); // 2024-01-01 23:00:00+0200 + +// int64_t now = get_sql_result(pConn, "select now(),now() + 2d"); +// int64_t locationNow = now + 2 * 3600; +// check_sql_result_partial(pConn, "select TO_ISO8601(today())", "T00:00:00.000+0200"); // 2024-01-01 23:00:00+0200 +// check_sql_result_partial(pConn, "select TO_ISO8601(now())", "+0200"); // 2024-01-01 23:00:00+0200 + + // WEEKDAY + check_sql_result_integer(pConn, "select WEEKDAY('2024-01-01')", 0); + check_sql_result_integer(pConn, "select WEEKDAY('2024-01-01 03:00:00')", 0); + check_sql_result_integer(pConn, "select WEEKDAY('2024-01-01 23:00:00+0200')", 0); + check_sql_result_integer(pConn, "select WEEKDAY('2024-01-01 23:00:00-1100')", 1); + check_sql_result_integer(pConn, "select WEEKDAY(1704142800000)", 0); + check_sql_result_integer(pConn, "select WEEKDAY(ts) from db1.ntb", 1); + + // DAYOFWEEK + check_sql_result_integer(pConn, "select DAYOFWEEK('2024-01-01')", 2); + check_sql_result_integer(pConn, "select DAYOFWEEK('2024-01-01 03:00:00')", 2); + check_sql_result_integer(pConn, "select DAYOFWEEK('2024-01-01 23:00:00+0200')", 2); + check_sql_result_integer(pConn, "select DAYOFWEEK('2024-01-01 23:00:00-1100')", 3); + check_sql_result_integer(pConn, "select DAYOFWEEK(1704142800000)", 2); + check_sql_result_integer(pConn, "select DAYOFWEEK(ts) from db1.ntb", 3); + + // WEEK + check_sql_result_integer(pConn, "select WEEK('2024-01-07')", 1); + check_sql_result_integer(pConn, "select WEEK('2024-01-07 02:00:00')", 1); + check_sql_result_integer(pConn, "select WEEK('2024-01-07 02:00:00+0200')", 1); + check_sql_result_integer(pConn, "select WEEK('2024-01-07 02:00:00+1100')", 0); + check_sql_result_integer(pConn, "select WEEK(1704142800000)", 0); // 2024-01-01 23:00:00+0200 + check_sql_result_integer(pConn, "select WEEK(ts) from db1.ntb", 0); // 2024-01-01 23:00:00+0200 + + check_sql_result_integer(pConn, "select WEEK('2024-01-07', 3)", 1); + check_sql_result_integer(pConn, "select WEEK('2024-01-07 02:00:00', 3)", 1); + check_sql_result_integer(pConn, "select WEEK('2024-01-07 02:00:00+0200', 3)", 1); + check_sql_result_integer(pConn, "select WEEK('2024-01-01 02:00:00+1100', 3)", 52); + check_sql_result_integer(pConn, "select WEEK(1704142800000, 3)", 1); // 2024-01-01 23:00:00+0200 + check_sql_result_integer(pConn, "select WEEK(ts, 3) from db1.ntb", 1); // 2024-01-01 23:00:00+0200 + + // WEEKOFYEAR + check_sql_result_integer(pConn, "select WEEKOFYEAR('2024-01-07')", 1); + check_sql_result_integer(pConn, "select WEEKOFYEAR('2024-01-07 02:00:00')", 1); + check_sql_result_integer(pConn, "select WEEKOFYEAR('2024-01-07 02:00:00+0200')", 1); + check_sql_result_integer(pConn, "select WEEKOFYEAR('2024-01-01 02:00:00+1100')", 52); + check_sql_result_integer(pConn, "select WEEKOFYEAR(1704142800000)", 1); // 2024-01-01 23:00:00+0200 + check_sql_result_integer(pConn, "select WEEKOFYEAR(ts) from db1.ntb", 1); // 2024-01-01 23:00:00+0200 + + // TO_ISO8601 + check_sql_result(pConn, "select TO_ISO8601(ts) from db1.ntb", "2024-01-01T23:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(ts,'-08') from db1.ntb", "2024-01-01T13:00:00.000-08"); + check_sql_result(pConn, "select TO_ISO8601(1)", "1970-01-01T02:00:00.001+0200"); + check_sql_result(pConn, "select TO_ISO8601(1,'+0800')", "1970-01-01T08:00:00.001+0800"); + + // TO_UNIXTIMESTAMP + check_sql_result_integer(pConn, "select TO_UNIXTIMESTAMP(c1) from db1.ntb", 1704121200000); // use timezone in server UTC-8 + check_sql_result_integer(pConn, "select TO_UNIXTIMESTAMP('2024-01-01T23:00:00.000+0200')", 1704142800000); + check_sql_result_integer(pConn, "select TO_UNIXTIMESTAMP('2024-01-01T13:00:00.000-08')", 1704142800000); + check_sql_result_integer(pConn, "select TO_UNIXTIMESTAMP('2024-01-01T23:00:00.001')", 1704142800001); + + // TO_TIMESTAMP + check_sql_result_integer(pConn, "select TO_TIMESTAMP(c1,'yyyy-mm-dd hh24:mi:ss') from db1.ntb", 1704121200000); // use timezone in server UTC-8 + check_sql_result_integer(pConn, "select TO_TIMESTAMP('2024-01-01 23:00:00+02:00', 'yyyy-mm-dd hh24:mi:ss tzh')", 1704142800000); + check_sql_result_integer(pConn, "select TO_TIMESTAMP('2024-01-01T13:00:00-08', 'yyyy-mm-ddThh24:mi:ss tzh')", 1704142800000); + check_sql_result_integer(pConn, "select TO_TIMESTAMP('2024/01/01 23:00:00', 'yyyy/mm/dd hh24:mi:ss')", 1704142800000); + + // TO_CHAR + check_sql_result(pConn, "select TO_CHAR(ts,'yyyy-mm-dd hh24:mi:ss') from db1.ntb", "2024-01-02 05:00:00"); // use timezone in server UTC-8 + check_sql_result(pConn, "select TO_CHAR(cast(1704142800000 as timestamp), 'yyyy-mm-dd hh24:mi:ss tzh')", "2024-01-01 23:00:00 +02"); + check_sql_result(pConn, "select TO_CHAR(cast(1704142800000 as timestamp), 'yyyy-mm-dd hh24:mi:ss')", "2024-01-01 23:00:00"); + + taos_close(pConn); + +} + time_t time_winter = 1731323281; // 2024-11-11 19:08:01+0800 time_t time_summer = 1731323281 - 120 * 24 * 60 * 60; @@ -1697,7 +1922,7 @@ TEST(clientCase, mktime_Test){ for (unsigned int i = 0; i < sizeof (test_mk) / sizeof (test_mk[0]); ++i) { setenv ("TZ", test_mk[i].env, 1); - t = taosMktime (&tm); + t = taosMktime (&tm, NULL); ASSERT (t == test_mk[i].expected); } } @@ -1719,7 +1944,7 @@ TEST(clientCase, mktime_rz_Test){ { timezone_t tz = tzalloc(test_mk[i].env); ASSERT(tz); - t = taosMktimeRz(tz, &tm); + t = taosMktime(&tm, tz); ASSERT (t == test_mk[i].expected); tzfree(tz); } @@ -1739,13 +1964,12 @@ TEST(testCase, localtime_performance_Test) { for (int j = 0; j < cnt; ++j) { time_t t = time_winter - j; struct tm tm1; - ASSERT (taosLocalTime(&t, &tm1, NULL, 0)); + ASSERT (taosLocalTime(&t, &tm1, NULL, 0, NULL)); } int64_t tmp = taosGetTimestampNs() - t1; printf("localtime cost:%" PRId64 " ns, run %" PRId64 " times", tmp, cnt); time_localtime += tmp/cnt; - timezone; printf("\n"); @@ -1754,7 +1978,7 @@ TEST(testCase, localtime_performance_Test) { for (int j = 0; j < cnt; ++j) { time_t t = time_winter - j; struct tm tm1; - ASSERT (taosLocalTimeRz(sp, &t, &tm1)); + ASSERT (taosLocalTime(&t, &tm1, NULL, 0, sp)); } tmp = taosGetTimestampNs() - t2; printf("localtime_rz cost:%" PRId64 " ns, run %" PRId64 " times", tmp, cnt); @@ -1765,4 +1989,5 @@ TEST(testCase, localtime_performance_Test) { tzfree(sp); } + #pragma GCC diagnostic pop diff --git a/source/common/src/tdatablock.c b/source/common/src/tdatablock.c index 60d1f34cf7..d0f02835e8 100644 --- a/source/common/src/tdatablock.c +++ b/source/common/src/tdatablock.c @@ -2473,7 +2473,7 @@ static int32_t formatTimestamp(char* buf, size_t cap, int64_t val, int precision } } struct tm ptm = {0}; - if (taosLocalTime(&tt, &ptm, buf, cap) == NULL) { + if (taosLocalTime(&tt, &ptm, buf, cap, NULL) == NULL) { code = TSDB_CODE_INTERNAL_ERROR; TSDB_CHECK_CODE(code, lino, _end); } diff --git a/source/common/src/tglobal.c b/source/common/src/tglobal.c index 57b3a7d67c..3641f3232c 100644 --- a/source/common/src/tglobal.c +++ b/source/common/src/tglobal.c @@ -2231,7 +2231,9 @@ static int32_t taosCfgDynamicOptionsForClient(SConfig *pCfg, const char *name) { break; } case 't': { - if (strcasecmp("tempDir", name) == 0) { + if (strcasecmp("timezone", name) == 0) { + matched = true; + } else if (strcasecmp("tempDir", name) == 0) { uInfo("%s set from %s to %s", name, tsTempDir, pItem->str); tstrncpy(tsTempDir, pItem->str, PATH_MAX); TAOS_CHECK_GOTO(taosExpandDir(tsTempDir, tsTempDir, PATH_MAX), &lino, _out); diff --git a/source/common/src/tmisce.c b/source/common/src/tmisce.c index 10375ba857..03918ff6f8 100644 --- a/source/common/src/tmisce.c +++ b/source/common/src/tmisce.c @@ -292,7 +292,10 @@ int32_t dumpConfToDataBlock(SSDataBlock* pBlock, int32_t startCol) { char value[TSDB_CONFIG_VALUE_LEN + VARSTR_HEADER_SIZE] = {0}; int32_t valueLen = 0; - TAOS_CHECK_GOTO(cfgDumpItemValue(pItem, &value[VARSTR_HEADER_SIZE], TSDB_CONFIG_VALUE_LEN, &valueLen), NULL, _exit); + if (pItem->dtype == CFG_DTYPE_TIMEZONE){ + + } + TAOS_CHECK_GOTO(cfgDumpItemValue(pItem, varDataVal(value), TSDB_CONFIG_VALUE_LEN, &valueLen), NULL, _exit); varDataSetLen(value, valueLen); pColInfo = taosArrayGet(pBlock->pDataBlock, col++); diff --git a/source/common/src/tname.c b/source/common/src/tname.c index 964741e2c4..9ced37eb38 100644 --- a/source/common/src/tname.c +++ b/source/common/src/tname.c @@ -20,73 +20,6 @@ #define VALID_NAME_TYPE(x) ((x) == TSDB_DB_NAME_T || (x) == TSDB_TABLE_NAME_T) -#if 0 -int64_t taosGetIntervalStartTimestamp(int64_t startTime, int64_t slidingTime, int64_t intervalTime, char timeUnit, int16_t precision) { - if (slidingTime == 0) { - return startTime; - } - int64_t start = startTime; - if (timeUnit == 'n' || timeUnit == 'y') { - start /= 1000; - if (precision == TSDB_TIME_PRECISION_MICRO) { - start /= 1000; - } - struct tm tm; - time_t t = (time_t)start; - taosLocalTime(&t, &tm, NULL, 0); - tm.tm_sec = 0; - tm.tm_min = 0; - tm.tm_hour = 0; - tm.tm_mday = 1; - - if (timeUnit == 'y') { - tm.tm_mon = 0; - tm.tm_year = (int)(tm.tm_year / slidingTime * slidingTime); - } else { - int mon = tm.tm_year * 12 + tm.tm_mon; - mon = (int)(mon / slidingTime * slidingTime); - tm.tm_year = mon / 12; - tm.tm_mon = mon % 12; - } - - start = mktime(&tm) * 1000L; - if (precision == TSDB_TIME_PRECISION_MICRO) { - start *= 1000L; - } - } else { - int64_t delta = startTime - intervalTime; - int32_t factor = delta > 0? 1:-1; - - start = (delta / slidingTime + factor) * slidingTime; - - if (timeUnit == 'd' || timeUnit == 'w') { - /* - * here we revised the start time of day according to the local time zone, - * but in case of DST, the start time of one day need to be dynamically decided. - */ - // todo refactor to extract function that is available for Linux/Windows/Mac platform -#if defined(WINDOWS) && _MSC_VER >= 1900 - // see https://docs.microsoft.com/en-us/cpp/c-runtime-library/daylight-dstbias-timezone-and-tzname?view=vs-2019 - int64_t timezone = _timezone; - int32_t daylight = _daylight; - char** tzname = _tzname; -#endif - - int64_t t = (precision == TSDB_TIME_PRECISION_MILLI) ? MILLISECOND_PER_SECOND : MILLISECOND_PER_SECOND * 1000L; - start += timezone * t; - } - - int64_t end = start + intervalTime - 1; - if (end < startTime) { - start += slidingTime; - } - } - - return start; -} - -#endif - void toName(int32_t acctId, const char* pDbName, const char* pTableName, SName* pName) { if (pName == NULL){ return; diff --git a/source/common/src/ttime.c b/source/common/src/ttime.c index c18ac32e46..574b2d2fce 100644 --- a/source/common/src/ttime.c +++ b/source/common/src/ttime.c @@ -27,28 +27,24 @@ static int32_t parseFraction(char* str, char** end, int32_t timePrec, int64_t* pFraction); static int32_t parseTimeWithTz(const char* timestr, int64_t* time, int32_t timePrec, char delim); -static int32_t parseLocaltime(char* timestr, int32_t len, int64_t* utime, int32_t timePrec, char delim); -static int32_t parseLocaltimeDst(char* timestr, int32_t len, int64_t* utime, int32_t timePrec, char delim); +static int32_t parseLocaltimeDst(char* timestr, int32_t len, int64_t* utime, int32_t timePrec, char delim, timezone_t tz); static char* forwardToTimeStringEnd(char* str); static bool checkTzPresent(const char* str, int32_t len); static int32_t parseTimezone(char* str, int64_t* tzOffset); -static int32_t (*parseLocaltimeFp[])(char* timestr, int32_t len, int64_t* utime, int32_t timePrec, char delim) = { - parseLocaltime, parseLocaltimeDst}; - -int32_t taosParseTime(const char* timestr, int64_t* utime, int32_t len, int32_t timePrec) { +int32_t taosParseTime(const char* timestr, int64_t* utime, int32_t len, int32_t timePrec, timezone_t tz) { /* parse datatime string in with tz */ if (strnchr(timestr, 'T', len, false) != NULL) { if (checkTzPresent(timestr, len)) { return parseTimeWithTz(timestr, utime, timePrec, 'T'); } else { - return parseLocaltimeDst((char*)timestr, len, utime, timePrec, 'T'); + return parseLocaltimeDst((char*)timestr, len, utime, timePrec, 'T', tz); } } else { if (checkTzPresent(timestr, len)) { return parseTimeWithTz(timestr, utime, timePrec, 0); } else { - return parseLocaltimeDst((char*)timestr, len, utime, timePrec, 0); + return parseLocaltimeDst((char*)timestr, len, utime, timePrec, 0, tz); } } } @@ -165,7 +161,7 @@ int32_t parseTimezone(char* str, int64_t* tzOffset) { i += 2; } - if (hour > 12 || hour < 0) { + if (hour > 13 || hour < 0) { TAOS_RETURN(TSDB_CODE_INVALID_PARA); } @@ -176,7 +172,7 @@ int32_t parseTimezone(char* str, int64_t* tzOffset) { } int64_t minute = strnatoi(&str[i], 2); - if (minute > 59 || (hour == 12 && minute > 0)) { + if (minute > 59 || minute < 0) { TAOS_RETURN(TSDB_CODE_INVALID_PARA); } @@ -233,7 +229,10 @@ int32_t parseTimeWithTz(const char* timestr, int64_t* time, int32_t timePrec, ch int64_t seconds = user_mktime64(tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, 0); // int64_t seconds = gmtime(&tm); #else - int64_t seconds = timegm(&tm); + int64_t seconds = taosTimeGm(&tm); + if (seconds == -1){ + TAOS_RETURN(TAOS_SYSTEM_ERROR(errno)); + } #endif int64_t fraction = 0; @@ -298,48 +297,7 @@ static FORCE_INLINE bool validateTm(struct tm* pTm) { return true; } -int32_t parseLocaltime(char* timestr, int32_t len, int64_t* utime, int32_t timePrec, char delim) { - *utime = 0; - struct tm tm = {0}; - - char* str; - if (delim == 'T') { - str = taosStrpTime(timestr, "%Y-%m-%dT%H:%M:%S", &tm); - } else if (delim == 0) { - str = taosStrpTime(timestr, "%Y-%m-%d %H:%M:%S", &tm); - } else { - str = NULL; - } - - if (str == NULL || (((str - timestr) < len) && (*str != '.')) || !validateTm(&tm)) { - // if parse failed, try "%Y-%m-%d" format - str = taosStrpTime(timestr, "%Y-%m-%d", &tm); - if (str == NULL || (((str - timestr) < len) && (*str != '.')) || !validateTm(&tm)) { - TAOS_RETURN(TSDB_CODE_INVALID_PARA); - } - } - -#ifdef _MSC_VER -#if _MSC_VER >= 1900 - int64_t timezone = _timezone; -#endif -#endif - - int64_t seconds = - user_mktime64(tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, timezone); - - int64_t fraction = 0; - - if (*str == '.') { - /* parse the second fraction part */ - TAOS_CHECK_RETURN(parseFraction(str + 1, &str, timePrec, &fraction)); - } - - *utime = TSDB_TICK_PER_SECOND(timePrec) * seconds + fraction; - TAOS_RETURN(TSDB_CODE_SUCCESS); -} - -int32_t parseLocaltimeDst(char* timestr, int32_t len, int64_t* utime, int32_t timePrec, char delim) { +int32_t parseLocaltimeDst(char* timestr, int32_t len, int64_t* utime, int32_t timePrec, char delim, timezone_t tz) { *utime = 0; struct tm tm = {0}; tm.tm_isdst = -1; @@ -361,8 +319,7 @@ int32_t parseLocaltimeDst(char* timestr, int32_t len, int64_t* utime, int32_t ti } } - /* mktime will be affected by TZ, set by using taos_options */ - int64_t seconds = taosMktime(&tm); //tztodo + int64_t seconds = taosMktime(&tm, tz); int64_t fraction = 0; if (*str == '.') { @@ -523,7 +480,7 @@ int32_t convertTimeFromPrecisionToUnit(int64_t time, int32_t fromPrecision, char TAOS_RETURN(TSDB_CODE_SUCCESS); } -int32_t convertStringToTimestamp(int16_t type, char* inputData, int64_t timePrec, int64_t* timeVal) { +int32_t convertStringToTimestamp(int16_t type, char* inputData, int64_t timePrec, int64_t* timeVal, timezone_t tz) { int32_t charLen = varDataLen(inputData); char* newColData; if (type == TSDB_DATA_TYPE_BINARY || type == TSDB_DATA_TYPE_VARBINARY) { @@ -532,7 +489,7 @@ int32_t convertStringToTimestamp(int16_t type, char* inputData, int64_t timePrec TAOS_RETURN(terrno); } (void)memcpy(newColData, varDataVal(inputData), charLen); - int32_t ret = taosParseTime(newColData, timeVal, charLen, (int32_t)timePrec); + int32_t ret = taosParseTime(newColData, timeVal, charLen, (int32_t)timePrec, tz); if (ret != TSDB_CODE_SUCCESS) { taosMemoryFree(newColData); TAOS_RETURN(TSDB_CODE_INVALID_TIMESTAMP); @@ -549,7 +506,7 @@ int32_t convertStringToTimestamp(int16_t type, char* inputData, int64_t timePrec TAOS_RETURN(TSDB_CODE_FAILED); } newColData[len] = 0; - int32_t ret = taosParseTime(newColData, timeVal, len, (int32_t)timePrec); + int32_t ret = taosParseTime(newColData, timeVal, len, (int32_t)timePrec, tz); if (ret != TSDB_CODE_SUCCESS) { taosMemoryFree(newColData); TAOS_RETURN(ret); @@ -681,7 +638,10 @@ int64_t taosTimeAdd(int64_t t, int64_t duration, char unit, int32_t precision) { struct tm tm; time_t tt = (time_t)(t / TSDB_TICK_PER_SECOND(precision)); - struct tm* ptm = taosLocalTime(&tt, &tm, NULL, 0); + if(taosGmTimeR(&tt, &tm) == NULL) { + uError("failed to convert time to gm time, code:%d", errno); + return t; + } int32_t mon = tm.tm_year * 12 + tm.tm_mon + (int32_t)numOfMonth; tm.tm_year = mon / 12; tm.tm_mon = mon % 12; @@ -692,7 +652,12 @@ int64_t taosTimeAdd(int64_t t, int64_t duration, char unit, int32_t precision) { if (tm.tm_mday > daysOfMonth[tm.tm_mon]) { tm.tm_mday = daysOfMonth[tm.tm_mon]; } - return (int64_t)(taosMktime(&tm) * TSDB_TICK_PER_SECOND(precision) + fraction); + tt = taosTimeGm(&tm); + if (tt == -1){ + uError("failed to convert gm time to time, code:%d", errno); + return t; + } + return (int64_t)(tt * TSDB_TICK_PER_SECOND(precision) + fraction); } /** @@ -731,7 +696,7 @@ int32_t taosTimeCountIntervalForFill(int64_t skey, int64_t ekey, int64_t interva ekey = skey; skey = tmp; } - int32_t ret; + int32_t ret = 0; if (unit != 'n' && unit != 'y') { ret = (int32_t)((ekey - skey) / interval); @@ -742,11 +707,17 @@ int32_t taosTimeCountIntervalForFill(int64_t skey, int64_t ekey, int64_t interva struct tm tm; time_t t = (time_t)skey; - struct tm* ptm = taosLocalTime(&t, &tm, NULL, 0); + if (taosLocalTime(&t, &tm, NULL, 0, NULL) == NULL) { + uError("%s failed to convert time to local time, code:%d", __FUNCTION__, errno); + return ret; + } int32_t smon = tm.tm_year * 12 + tm.tm_mon; t = (time_t)ekey; - ptm = taosLocalTime(&t, &tm, NULL, 0); + if (taosLocalTime(&t, &tm, NULL, 0, NULL) == NULL) { + uError("%s failed to convert time to local time, code:%d", __FUNCTION__, errno); + return ret; + } int32_t emon = tm.tm_year * 12 + tm.tm_mon; if (unit == 'y') { @@ -770,7 +741,10 @@ int64_t taosTimeTruncate(int64_t ts, const SInterval* pInterval) { start /= (int64_t)(TSDB_TICK_PER_SECOND(precision)); struct tm tm; time_t tt = (time_t)start; - struct tm* ptm = taosLocalTime(&tt, &tm, NULL, 0); + if (taosLocalTime(&tt, &tm, NULL, 0, NULL) == NULL){ + uError("%s failed to convert time to local time, code:%d", __FUNCTION__, errno); + return ts; + } tm.tm_sec = 0; tm.tm_min = 0; tm.tm_hour = 0; @@ -786,7 +760,12 @@ int64_t taosTimeTruncate(int64_t ts, const SInterval* pInterval) { tm.tm_mon = mon % 12; } - start = (int64_t)(taosMktime(&tm) * TSDB_TICK_PER_SECOND(precision)); + tt = taosMktime(&tm, NULL); + if (tt == -1){ + uError("%s failed to convert local time to time, code:%d", __FUNCTION__, errno); + return ts; + } + start = (int64_t)(tt * TSDB_TICK_PER_SECOND(precision)); } else { if (IS_CALENDAR_TIME_DURATION(pInterval->intervalUnit)) { int64_t news = (ts / pInterval->sliding) * pInterval->sliding; @@ -931,7 +910,7 @@ int32_t taosFormatUtcTime(char* buf, int32_t bufLen, int64_t t, int32_t precisio TAOS_RETURN(TSDB_CODE_INVALID_PARA); } - if (NULL == taosLocalTime(", &ptm, buf, bufLen)) { + if (NULL == taosLocalTime(", &ptm, buf, bufLen, NULL)) { TAOS_RETURN(TAOS_SYSTEM_ERROR(errno)); } int32_t length = (int32_t)strftime(ts, 40, "%Y-%m-%dT%H:%M:%S", &ptm); @@ -942,17 +921,17 @@ int32_t taosFormatUtcTime(char* buf, int32_t bufLen, int64_t t, int32_t precisio TAOS_RETURN(TSDB_CODE_SUCCESS); } -int32_t taosTs2Tm(int64_t ts, int32_t precision, struct STm* tm) { +int32_t taosTs2Tm(int64_t ts, int32_t precision, struct STm* tm, timezone_t tz) { tm->fsec = ts % TICK_PER_SECOND[precision] * (TICK_PER_SECOND[TSDB_TIME_PRECISION_NANO] / TICK_PER_SECOND[precision]); time_t t = ts / TICK_PER_SECOND[precision]; - if (NULL == taosLocalTime(&t, &tm->tm, NULL, 0)) { //tztodo + if (NULL == taosLocalTime(&t, &tm->tm, NULL, 0, tz)) { TAOS_RETURN(TAOS_SYSTEM_ERROR(errno)); } return TSDB_CODE_SUCCESS; } -int32_t taosTm2Ts(struct STm* tm, int64_t* ts, int32_t precision) { - *ts = taosMktime(&tm->tm); //tztodo +int32_t taosTm2Ts(struct STm* tm, int64_t* ts, int32_t precision, timezone_t tz) { + *ts = taosMktime(&tm->tm, tz); *ts *= TICK_PER_SECOND[precision]; *ts += tm->fsec / (TICK_PER_SECOND[TSDB_TIME_PRECISION_NANO] / TICK_PER_SECOND[precision]); return TSDB_CODE_SUCCESS; @@ -1420,7 +1399,8 @@ static int32_t tm2char(const SArray* formats, const struct STm* tm, char* s, int s += 9; break; case TSFKW_TZH: - (void)sprintf(s, "%s%02d", tsTimezone < 0 ? "-" : "+", tsTimezone); + (void)sprintf(s, "%c%02d", (tm->tm.tm_gmtoff >= 0) ? '+' : '-', + abs((int) tm->tm.tm_gmtoff) / 3600); s += strlen(s); break; case TSFKW_YYYY: @@ -1552,13 +1532,13 @@ static bool needMoreDigits(SArray* formats, int32_t curIdx) { /// @retval -2 if datetime err, like 2023-13-32 25:61:69 /// @retval -3 if not supported static int32_t char2ts(const char* s, SArray* formats, int64_t* ts, int32_t precision, const char** sErrPos, - int32_t* fErrIdx) { + int32_t* fErrIdx, timezone_t tz) { int32_t size = taosArrayGetSize(formats); int32_t pm = 0; // default am int32_t hour12 = 0; // default HH24 int32_t year = 0, mon = 0, yd = 0, md = 1, wd = 0; int32_t hour = 0, min = 0, sec = 0, us = 0, ms = 0, ns = 0; - int32_t tzSign = 1, tz = tsTimezone; + int32_t tzHour = 0; int32_t err = 0; bool withYD = false, withMD = false; @@ -1685,8 +1665,7 @@ static int32_t char2ts(const char* s, SArray* formats, int64_t* ts, int32_t prec } } break; case TSFKW_TZH: { - tzSign = *s == '-' ? -1 : 1; - const char* newPos = tsFormatStr2Int32(&tz, s, -1, needMoreDigits(formats, i)); + const char* newPos = tsFormatStr2Int32(&tzHour, s, -1, needMoreDigits(formats, i)); if (NULL == newPos) err = -1; else { @@ -1839,14 +1818,16 @@ static int32_t char2ts(const char* s, SArray* formats, int64_t* ts, int32_t prec tm.tm.tm_min = min; tm.tm.tm_sec = sec; if (!checkTm(&tm.tm)) return -2; - if (tz < -12 || tz > 12) return -2; + if (tzHour < -13 || tzHour > 13) return -2; tm.fsec = ms * 1000000 + us * 1000 + ns; - int32_t ret = taosTm2Ts(&tm, ts, precision); - *ts += 60 * 60 * (tsTimezone - tz) * TICK_PER_SECOND[precision]; + int32_t ret = taosTm2Ts(&tm, ts, precision, tz); + if (tzHour != 0) { + *ts += (tm.tm.tm_gmtoff - tzHour * 3600) * TICK_PER_SECOND[precision]; + } return ret; } -int32_t taosTs2Char(const char* format, SArray** formats, int64_t ts, int32_t precision, char* out, int32_t outLen) { +int32_t taosTs2Char(const char* format, SArray** formats, int64_t ts, int32_t precision, char* out, int32_t outLen, timezone_t tz) { if (!*formats) { *formats = taosArrayInit(8, sizeof(TSFormatNode)); if (!*formats) { @@ -1855,12 +1836,12 @@ int32_t taosTs2Char(const char* format, SArray** formats, int64_t ts, int32_t pr TAOS_CHECK_RETURN(parseTsFormat(format, *formats)); } struct STm tm; - TAOS_CHECK_RETURN(taosTs2Tm(ts, precision, &tm)); + TAOS_CHECK_RETURN(taosTs2Tm(ts, precision, &tm, tz)); return tm2char(*formats, &tm, out, outLen); } int32_t taosChar2Ts(const char* format, SArray** formats, const char* tsStr, int64_t* ts, int32_t precision, - char* errMsg, int32_t errMsgLen) { + char* errMsg, int32_t errMsgLen, timezone_t tz) { const char* sErrPos; int32_t fErrIdx; if (!*formats) { @@ -1870,7 +1851,7 @@ int32_t taosChar2Ts(const char* format, SArray** formats, const char* tsStr, int } TAOS_CHECK_RETURN(parseTsFormat(format, *formats)); } - int32_t code = char2ts(tsStr, *formats, ts, precision, &sErrPos, &fErrIdx); + int32_t code = char2ts(tsStr, *formats, ts, precision, &sErrPos, &fErrIdx, tz); if (code == -1) { TSFormatNode* fNode = (taosArrayGet(*formats, fErrIdx)); snprintf(errMsg, errMsgLen, "mismatch format for: %s and %s", sErrPos, @@ -1895,7 +1876,7 @@ int32_t TEST_ts2char(const char* format, int64_t ts, int32_t precision, char* ou } TAOS_CHECK_RETURN(parseTsFormat(format, formats)); struct STm tm; - TAOS_CHECK_GOTO(taosTs2Tm(ts, precision, &tm), NULL, _exit); + TAOS_CHECK_GOTO(taosTs2Tm(ts, precision, &tm, NULL), NULL, _exit); TAOS_CHECK_GOTO(tm2char(formats, &tm, out, outLen), NULL, _exit); _exit: @@ -1908,7 +1889,7 @@ int32_t TEST_char2ts(const char* format, int64_t* ts, int32_t precision, const c int32_t fErrIdx; SArray* formats = taosArrayInit(4, sizeof(TSFormatNode)); TAOS_CHECK_RETURN(parseTsFormat(format, formats)); - int32_t code = char2ts(tsStr, formats, ts, precision, &sErrPos, &fErrIdx); + int32_t code = char2ts(tsStr, formats, ts, precision, &sErrPos, &fErrIdx, NULL); if (code == -1) { (void)printf("failed position: %s\n", sErrPos); (void)printf("failed format: %s\n", ((TSFormatNode*)taosArrayGet(formats, fErrIdx))->key->name); @@ -2024,3 +2005,26 @@ bool checkRecursiveTsmaInterval(int64_t baseInterval, int8_t baseUnit, int64_t i } return true; } + +int64_t taosGetTimestampToday(int32_t precision, timezone_t tz) { + int64_t factor = (precision == TSDB_TIME_PRECISION_SECONDS) ? 1 + : (precision == TSDB_TIME_PRECISION_MILLI) ? 1000 + : (precision == TSDB_TIME_PRECISION_MICRO) ? 1000000 + : 1000000000; + time_t t = taosTime(NULL); + struct tm tm; + if (taosLocalTime(&t, &tm, NULL, 0, tz) == NULL){ + uError("%s failed to get local time, code:%d", __FUNCTION__, errno); + return t; + } + tm.tm_hour = 0; + tm.tm_min = 0; + tm.tm_sec = 0; + + time_t tmp = taosMktime(&tm, tz); + if (tmp == (time_t)-1) { + uError("%s failed to get timestamp of today, code:%d", __FUNCTION__, errno); + return t; + } + return (int64_t)tmp * factor; +} \ No newline at end of file diff --git a/source/common/test/commonTests.cpp b/source/common/test/commonTests.cpp index 054c3d5f58..19fe6b00d3 100644 --- a/source/common/test/commonTests.cpp +++ b/source/common/test/commonTests.cpp @@ -429,9 +429,9 @@ void test_timestamp_tm_conversion(int64_t ts, int32_t precision, int32_t y, int3 struct STm tm; taosFormatUtcTime(buf, 128, ts, precision); printf("formated ts of %ld, precision: %d is: %s\n", ts, precision, buf); - taosTs2Tm(ts, precision, &tm); + taosTs2Tm(ts, precision, &tm, NULL); check_tm(&tm, y, mon, d, h, m, s, fsec); - taosTm2Ts(&tm, &ts_tmp, precision); + taosTm2Ts(&tm, &ts_tmp, precision, NULL); ASSERT_EQ(ts, ts_tmp); } @@ -442,15 +442,15 @@ TEST(timeTest, timestamp2tm) { int64_t ts, tmp_ts = 0; struct STm tm; - ASSERT_EQ(TSDB_CODE_SUCCESS, taosParseTime(ts_str_ns, &ts, strlen(ts_str_ns), TSDB_TIME_PRECISION_NANO)); + ASSERT_EQ(TSDB_CODE_SUCCESS, taosParseTime(ts_str_ns, &ts, strlen(ts_str_ns), TSDB_TIME_PRECISION_NANO, NULL)); test_timestamp_tm_conversion(ts, TSDB_TIME_PRECISION_NANO, 2023 - 1900, 9 /* mon start from 0*/, 12, 11, 29, 0, 775726171L); - ASSERT_EQ(TSDB_CODE_SUCCESS, taosParseTime(ts_str_us, &ts, strlen(ts_str_us), TSDB_TIME_PRECISION_MICRO)); + ASSERT_EQ(TSDB_CODE_SUCCESS, taosParseTime(ts_str_us, &ts, strlen(ts_str_us), TSDB_TIME_PRECISION_MICRO, NULL)); test_timestamp_tm_conversion(ts, TSDB_TIME_PRECISION_MICRO, 2023 - 1900, 9 /* mon start from 0*/, 12, 11, 29, 0, 775726000L); - ASSERT_EQ(TSDB_CODE_SUCCESS, taosParseTime(ts_str_ms, &ts, strlen(ts_str_ms), TSDB_TIME_PRECISION_MILLI)); + ASSERT_EQ(TSDB_CODE_SUCCESS, taosParseTime(ts_str_ms, &ts, strlen(ts_str_ms), TSDB_TIME_PRECISION_MILLI, NULL)); test_timestamp_tm_conversion(ts, TSDB_TIME_PRECISION_MILLI, 2023 - 1900, 9 /* mon start from 0*/, 12, 11, 29, 0, 775000000L); diff --git a/source/dnode/mgmt/mgmt_dnode/src/dmHandle.c b/source/dnode/mgmt/mgmt_dnode/src/dmHandle.c index f65fe290a2..756caad9ad 100644 --- a/source/dnode/mgmt/mgmt_dnode/src/dmHandle.c +++ b/source/dnode/mgmt/mgmt_dnode/src/dmHandle.c @@ -198,7 +198,7 @@ void dmSendStatusReq(SDnodeMgmt *pMgmt) { req.clusterCfg.monitorParas.tsSlowLogThresholdTest = tsSlowLogThresholdTest; tstrncpy(req.clusterCfg.monitorParas.tsSlowLogExceptDb, tsSlowLogExceptDb, TSDB_DB_NAME_LEN); char timestr[32] = "1970-01-01 00:00:00.00"; - if (taosParseTime(timestr, &req.clusterCfg.checkTime, (int32_t)strlen(timestr), TSDB_TIME_PRECISION_MILLI) != 0) { + if (taosParseTime(timestr, &req.clusterCfg.checkTime, (int32_t)strlen(timestr), TSDB_TIME_PRECISION_MILLI, NULL) != 0) { dError("failed to parse time since %s", tstrerror(code)); } memcpy(req.clusterCfg.timezone, tsTimezoneStr, TD_TIMEZONE_LEN); diff --git a/source/dnode/mnode/impl/src/mndMain.c b/source/dnode/mnode/impl/src/mndMain.c index c2edf6d6d8..def57a87c5 100644 --- a/source/dnode/mnode/impl/src/mndMain.c +++ b/source/dnode/mnode/impl/src/mndMain.c @@ -713,7 +713,7 @@ SMnode *mndOpen(const char *path, const SMnodeOpt *pOption) { } char timestr[24] = "1970-01-01 00:00:00.00"; - code = taosParseTime(timestr, &pMnode->checkTime, (int32_t)strlen(timestr), TSDB_TIME_PRECISION_MILLI); + code = taosParseTime(timestr, &pMnode->checkTime, (int32_t)strlen(timestr), TSDB_TIME_PRECISION_MILLI, NULL); if (code < 0) { mError("failed to parse time since %s", tstrerror(code)); (void)taosThreadRwlockDestroy(&pMnode->lock); diff --git a/source/libs/function/src/builtins.c b/source/libs/function/src/builtins.c index 6837ecbf20..4248f8a393 100644 --- a/source/libs/function/src/builtins.c +++ b/source/libs/function/src/builtins.c @@ -186,11 +186,11 @@ static int32_t countTrailingSpaces(const SValueNode* pVal, bool isLtrim) { return numOfSpaces; } -static int32_t addTimezoneParam(SNodeList* pList) { +static int32_t addTimezoneParam(SNodeList* pList, timezone_t tz) { char buf[TD_TIME_STR_LEN] = {0}; time_t t = taosTime(NULL); struct tm tmInfo; - if (taosLocalTime(&t, &tmInfo, buf, sizeof(buf)) != NULL) { + if (taosLocalTime(&t, &tmInfo, buf, sizeof(buf), tz) != NULL) { (void)strftime(buf, sizeof(buf), "%z", &tmInfo); } int32_t len = (int32_t)strlen(buf); @@ -1446,7 +1446,7 @@ static int32_t translateToIso8601(SFunctionNode* pFunc, char* pErrBuf, int32_t l return buildFuncErrMsg(pErrBuf, len, TSDB_CODE_FUNC_FUNTION_ERROR, "Invalid timzone format"); } } else { // add default client timezone - int32_t code = addTimezoneParam(pFunc->pParameterList); + int32_t code = addTimezoneParam(pFunc->pParameterList, pFunc->tz); if (code != TSDB_CODE_SUCCESS) { return code; } @@ -1501,7 +1501,7 @@ static int32_t translateTimeTruncate(SFunctionNode* pFunc, char* pErrBuf, int32_ } // add client timezone as param - code = addTimezoneParam(pFunc->pParameterList); + code = addTimezoneParam(pFunc->pParameterList, pFunc->tz); if (code != TSDB_CODE_SUCCESS) { return code; } diff --git a/source/libs/parser/inc/parUtil.h b/source/libs/parser/inc/parUtil.h index 857c7604a9..89628ea97e 100644 --- a/source/libs/parser/inc/parUtil.h +++ b/source/libs/parser/inc/parUtil.h @@ -135,7 +135,7 @@ int32_t trimString(const char* src, int32_t len, char* dst, int32_t dlen); int32_t getVnodeSysTableTargetName(int32_t acctId, SNode* pWhere, SName* pName); int32_t checkAndTrimValue(SToken* pToken, char* tmpTokenBuf, SMsgBuf* pMsgBuf, int8_t type); int32_t parseTagValue(SMsgBuf* pMsgBuf, const char** pSql, uint8_t precision, SSchema* pTagSchema, SToken* pToken, - SArray* pTagName, SArray* pTagVals, STag** pTag); + SArray* pTagName, SArray* pTagVals, STag** pTag, timezone_t tz); int32_t parseTbnameToken(SMsgBuf* pMsgBuf, char* tname, SToken* pToken, bool* pFoundCtbName); int32_t buildCatalogReq(const SParseMetaCache* pMetaCache, SCatalogReq* pCatalogReq); diff --git a/source/libs/parser/src/parAstCreater.c b/source/libs/parser/src/parAstCreater.c index 269e9e4a04..cceebd627e 100644 --- a/source/libs/parser/src/parAstCreater.c +++ b/source/libs/parser/src/parAstCreater.c @@ -420,6 +420,7 @@ SNode* createValueNode(SAstCreateContext* pCxt, int32_t dataType, const SToken* val->node.resType.precision = TSDB_TIME_PRECISION_MILLI; } val->translate = false; + val->tz = pCxt->pQueryCxt->timezone; return (SNode*)val; _err: return NULL; @@ -1041,6 +1042,7 @@ SNode* createFunctionNode(SAstCreateContext* pCxt, const SToken* pFuncName, SNod CHECK_MAKE_NODE(func); COPY_STRING_FORM_ID_TOKEN(func->functionName, pFuncName); func->pParameterList = pParameterList; + func->tz = pCxt->pQueryCxt->timezone; return (SNode*)func; _err: nodesDestroyList(pParameterList); @@ -1497,6 +1499,7 @@ SNode* createCaseWhenNode(SAstCreateContext* pCxt, SNode* pCase, SNodeList* pWhe pCaseWhen->pCase = pCase; pCaseWhen->pWhenThenList = pWhenThenList; pCaseWhen->pElse = pElse; + pCaseWhen->tz = pCxt->pQueryCxt->timezone; return (SNode*)pCaseWhen; _err: nodesDestroyNode(pCase); diff --git a/source/libs/parser/src/parInsertSql.c b/source/libs/parser/src/parInsertSql.c index 81deefcaf1..c89735472d 100644 --- a/source/libs/parser/src/parInsertSql.c +++ b/source/libs/parser/src/parInsertSql.c @@ -247,13 +247,13 @@ static int32_t parseBoundColumns(SInsertParseContext* pCxt, const char** pSql, E } static int parseTimestampOrInterval(const char** end, SToken* pToken, int16_t timePrec, int64_t* ts, int64_t* interval, - SMsgBuf* pMsgBuf, bool* isTs) { + SMsgBuf* pMsgBuf, bool* isTs, timezone_t tz) { if (pToken->type == TK_NOW) { *isTs = true; *ts = taosGetTimestamp(timePrec); } else if (pToken->type == TK_TODAY) { *isTs = true; - *ts = taosGetTimestampToday(timePrec); + *ts = taosGetTimestampToday(timePrec, tz); } else if (pToken->type == TK_NK_INTEGER) { *isTs = true; if (TSDB_CODE_SUCCESS != toInteger(pToken->z, pToken->n, 10, ts)) { @@ -267,7 +267,7 @@ static int parseTimestampOrInterval(const char** end, SToken* pToken, int16_t ti } } else { // parse the RFC-3339/ISO-8601 timestamp format string *isTs = true; - if (taosParseTime(pToken->z, ts, pToken->n, timePrec) != TSDB_CODE_SUCCESS) { + if (taosParseTime(pToken->z, ts, pToken->n, timePrec, tz) != TSDB_CODE_SUCCESS) { if ((pToken->n == 0) || (pToken->type != TK_NK_STRING && pToken->type != TK_NK_HEX && pToken->type != TK_NK_BIN)) { return buildSyntaxErrMsg(pMsgBuf, "invalid timestamp format", pToken->z); @@ -277,7 +277,7 @@ static int parseTimestampOrInterval(const char** end, SToken* pToken, int16_t ti *ts = taosGetTimestamp(timePrec); } else if (IS_TODAY_STR(pToken->z, pToken->n)) { *isTs = true; - *ts = taosGetTimestampToday(timePrec); + *ts = taosGetTimestampToday(timePrec, tz); } else if (TSDB_CODE_SUCCESS == toIntegerPure(pToken->z, pToken->n, 10, ts)) { *isTs = true; } else { @@ -289,7 +289,7 @@ static int parseTimestampOrInterval(const char** end, SToken* pToken, int16_t ti return TSDB_CODE_SUCCESS; } -static int parseTime(const char** end, SToken* pToken, int16_t timePrec, int64_t* time, SMsgBuf* pMsgBuf) { +static int parseTime(const char** end, SToken* pToken, int16_t timePrec, int64_t* time, SMsgBuf* pMsgBuf, timezone_t tz) { int32_t index = 0, i = 0; int64_t interval = 0, tempInterval = 0; int64_t ts = 0, tempTs = 0; @@ -297,7 +297,7 @@ static int parseTime(const char** end, SToken* pToken, int16_t timePrec, int64_t const char* pTokenEnd = *end; if (TSDB_CODE_SUCCESS != - parseTimestampOrInterval(&pTokenEnd, pToken, timePrec, &ts, &interval, pMsgBuf, &firstIsTS)) { + parseTimestampOrInterval(&pTokenEnd, pToken, timePrec, &ts, &interval, pMsgBuf, &firstIsTS, tz)) { return buildSyntaxErrMsg(pMsgBuf, "invalid timestamp format", pToken->z); } @@ -371,7 +371,7 @@ static int parseTime(const char** end, SToken* pToken, int16_t timePrec, int64_t } if (TSDB_CODE_SUCCESS != - parseTimestampOrInterval(&pTokenEnd, &valueToken, timePrec, &tempTs, &tempInterval, pMsgBuf, &secondIsTs)) { + parseTimestampOrInterval(&pTokenEnd, &valueToken, timePrec, &tempTs, &tempInterval, pMsgBuf, &secondIsTs, tz)) { return buildSyntaxErrMsg(pMsgBuf, "invalid timestamp format", pToken->z); } @@ -477,7 +477,7 @@ static int32_t parseVarbinary(SToken* pToken, uint8_t** pData, uint32_t* nData, } static int32_t parseTagToken(const char** end, SToken* pToken, SSchema* pSchema, int16_t timePrec, STagVal* val, - SMsgBuf* pMsgBuf) { + SMsgBuf* pMsgBuf, timezone_t tz) { int64_t iv; uint64_t uv; char* endptr = NULL; @@ -700,7 +700,7 @@ static int32_t parseTagToken(const char** end, SToken* pToken, SSchema* pSchema, break; } case TSDB_DATA_TYPE_TIMESTAMP: { - if (parseTime(end, pToken, timePrec, &iv, pMsgBuf) != TSDB_CODE_SUCCESS) { + if (parseTime(end, pToken, timePrec, &iv, pMsgBuf, tz) != TSDB_CODE_SUCCESS) { return buildSyntaxErrMsg(pMsgBuf, "invalid timestamp", pToken->z); } @@ -732,7 +732,7 @@ static int32_t parseBoundTagsClause(SInsertParseContext* pCxt, SVnodeModifyOpStm } int32_t parseTagValue(SMsgBuf* pMsgBuf, const char** pSql, uint8_t precision, SSchema* pTagSchema, SToken* pToken, - SArray* pTagName, SArray* pTagVals, STag** pTag) { + SArray* pTagName, SArray* pTagVals, STag** pTag, timezone_t tz) { bool isNull = isNullValue(pTagSchema->type, pToken); if (!isNull && pTagName) { if (NULL == taosArrayPush(pTagName, pTagSchema->name)) { @@ -755,7 +755,7 @@ int32_t parseTagValue(SMsgBuf* pMsgBuf, const char** pSql, uint8_t precision, SS if (isNull) return 0; STagVal val = {0}; - int32_t code = parseTagToken(pSql, pToken, pTagSchema, precision, &val, pMsgBuf); + int32_t code = parseTagToken(pSql, pToken, pTagSchema, precision, &val, pMsgBuf, tz); if (TSDB_CODE_SUCCESS == code) { if (NULL == taosArrayPush(pTagVals, &val)){ code = terrno; @@ -975,7 +975,7 @@ static int32_t parseTagsClauseImpl(SInsertParseContext* pCxt, SVnodeModifyOpStmt code = buildSyntaxErrMsg(&pCxt->msg, "not expected tags values ", token.z); } if (TSDB_CODE_SUCCESS == code) { - code = parseTagValue(&pCxt->msg, &pStmt->pSql, precision, pTagSchema, &token, pTagName, pTagVals, &pTag); + code = parseTagValue(&pCxt->msg, &pStmt->pSql, precision, pTagSchema, &token, pTagName, pTagVals, &pTag, pCxt->pComCxt->timezone); } } @@ -1672,7 +1672,7 @@ static int32_t parseValueTokenImpl(SInsertParseContext* pCxt, const char** pSql, break; } case TSDB_DATA_TYPE_TIMESTAMP: { - if (parseTime(pSql, pToken, timePrec, &pVal->value.val, &pCxt->msg) != TSDB_CODE_SUCCESS) { + if (parseTime(pSql, pToken, timePrec, &pVal->value.val, &pCxt->msg, pCxt->pComCxt->timezone) != TSDB_CODE_SUCCESS) { return buildSyntaxErrMsg(&pCxt->msg, "invalid timestamp", pToken->z); } break; @@ -1785,7 +1785,7 @@ static int32_t processCtbTagsAfterCtbName(SInsertParseContext* pCxt, SVnodeModif if (code == TSDB_CODE_SUCCESS) { code = parseTagValue(&pCxt->msg, NULL, precision, pTagSchema, pTagToken, pStbRowsCxt->aTagNames, - pStbRowsCxt->aTagVals, &pStbRowsCxt->pTag); + pStbRowsCxt->aTagVals, &pStbRowsCxt->pTag, pCxt->pComCxt->timezone); } } if (code == TSDB_CODE_SUCCESS && !pStbRowsCxt->isJsonTag) { @@ -1844,7 +1844,7 @@ static int32_t doGetStbRowValues(SInsertParseContext* pCxt, SVnodeModifyOpStmt* } if (code == TSDB_CODE_SUCCESS) { code = parseTagValue(&pCxt->msg, ppSql, precision, (SSchema*)pTagSchema, pToken, pTagNames, pTagVals, - &pStbRowsCxt->pTag); + &pStbRowsCxt->pTag, pCxt->pComCxt->timezone); } } } else if (pCols->pColIndex[i] == tbnameIdx) { diff --git a/source/libs/parser/src/parTranslater.c b/source/libs/parser/src/parTranslater.c index 824764a9f6..b73adfe256 100755 --- a/source/libs/parser/src/parTranslater.c +++ b/source/libs/parser/src/parTranslater.c @@ -1949,7 +1949,7 @@ static int32_t parseTimeFromValueNode(STranslateContext* pCxt, SValueNode* pVal) return TSDB_CODE_SUCCESS; } else if (IS_VAR_DATA_TYPE(pVal->node.resType.type) || TSDB_DATA_TYPE_TIMESTAMP == pVal->node.resType.type) { if (TSDB_CODE_SUCCESS == taosParseTime(pVal->literal, &pVal->datum.i, pVal->node.resType.bytes, - pVal->node.resType.precision)) { + pVal->node.resType.precision, pVal->tz)) { return TSDB_CODE_SUCCESS; } char* pEnd = NULL; @@ -14008,7 +14008,7 @@ static int32_t buildKVRowForBindTags(STranslateContext* pCxt, SCreateSubTableCla if (pSchema->type == TSDB_DATA_TYPE_JSON) { isJson = true; } - code = parseTagValue(&pCxt->msgBuf, &tagStr, precision, pSchema, &token, tagName, pTagArray, ppTag); + code = parseTagValue(&pCxt->msgBuf, &tagStr, precision, pSchema, &token, tagName, pTagArray, ppTag, pCxt->pParseCxt->timezone); } if (TSDB_CODE_SUCCESS == code) { @@ -14069,7 +14069,7 @@ static int32_t buildKVRowForAllTags(STranslateContext* pCxt, SCreateSubTableClau if (pTagSchema->type == TSDB_DATA_TYPE_JSON) { isJson = true; } - code = parseTagValue(&pCxt->msgBuf, &tagStr, precision, pTagSchema, &token, tagName, pTagArray, ppTag); + code = parseTagValue(&pCxt->msgBuf, &tagStr, precision, pTagSchema, &token, tagName, pTagArray, ppTag, pCxt->pParseCxt->timezone); } if (TSDB_CODE_SUCCESS == code) { @@ -14265,7 +14265,7 @@ static int32_t fillVgroupInfo(SParseContext* pParseCxt, const SName* pName, SVgr return code; } -static int32_t parseOneStbRow(SMsgBuf* pMsgBuf, SParseFileContext* pParFileCxt) { +static int32_t parseOneStbRow(SMsgBuf* pMsgBuf, SParseFileContext* pParFileCxt, timezone_t tz) { int32_t code = TSDB_CODE_SUCCESS; int sz = taosArrayGetSize(pParFileCxt->aTagIndexs); int32_t numOfTags = getNumOfTags(pParFileCxt->pStbMeta); @@ -14297,7 +14297,7 @@ static int32_t parseOneStbRow(SMsgBuf* pMsgBuf, SParseFileContext* pParFileCxt) if (TSDB_CODE_SUCCESS == code) { SArray* aTagNames = pParFileCxt->tagNameFilled ? NULL : pParFileCxt->aTagNames; code = parseTagValue(pMsgBuf, &pParFileCxt->pSql, precision, (SSchema*)pTagSchema, &token, aTagNames, - pParFileCxt->aTagVals, &pParFileCxt->pTag); + pParFileCxt->aTagVals, &pParFileCxt->pTag, tz); } } else { // parse tbname @@ -14363,7 +14363,7 @@ static int32_t parseCsvFile(SMsgBuf* pMsgBuf, SParseContext* pParseCxt, SParseFi (void)strtolower(pLine, pLine); pParFileCxt->pSql = pLine; - code = parseOneStbRow(pMsgBuf, pParFileCxt); + code = parseOneStbRow(pMsgBuf, pParFileCxt, pParseCxt->timezone); if (TSDB_CODE_SUCCESS == code) { code = fillVgroupInfo(pParseCxt, &pParFileCxt->ctbName, &pParFileCxt->vg); @@ -15064,7 +15064,7 @@ static int32_t buildUpdateTagValReq(STranslateContext* pCxt, SAlterTableStmt* pS if (TSDB_CODE_SUCCESS == code) { code = parseTagValue(&pCxt->msgBuf, &tagStr, pTableMeta->tableInfo.precision, pSchema, &token, NULL, - pReq->pTagArray, &pTag); + pReq->pTagArray, &pTag, pCxt->pParseCxt->timezone); if (pSchema->type == TSDB_DATA_TYPE_JSON && token.type == TK_NULL && code == TSDB_CODE_SUCCESS) { pReq->tagFree = true; } diff --git a/source/libs/scalar/src/filter.c b/source/libs/scalar/src/filter.c index d8622d93ee..cb08851196 100644 --- a/source/libs/scalar/src/filter.c +++ b/source/libs/scalar/src/filter.c @@ -4694,33 +4694,6 @@ EDealRes fltReviseRewriter(SNode **pNode, void *pContext) { stat->scalarMode = true; return DEAL_RES_CONTINUE; } - - /* - if (!FILTER_GET_FLAG(stat->info->options, FLT_OPTION_TIMESTAMP)) { - return DEAL_RES_CONTINUE; - } - - if (TSDB_DATA_TYPE_BINARY != valueNode->node.resType.type && TSDB_DATA_TYPE_NCHAR != - valueNode->node.resType.type && - TSDB_DATA_TYPE_GEOMETRY != valueNode->node.resType.type) { return DEAL_RES_CONTINUE; - } - - if (stat->precision < 0) { - int32_t code = fltAddValueNodeToConverList(stat, valueNode); - if (code) { - stat->code = code; - return DEAL_RES_ERROR; - } - - return DEAL_RES_CONTINUE; - } - - int32_t code = sclConvertToTsValueNode(stat->precision, valueNode); - if (code) { - stat->code = code; - return DEAL_RES_ERROR; - } - */ return DEAL_RES_CONTINUE; } @@ -4887,15 +4860,6 @@ int32_t fltReviseNodes(SFilterInfo *pInfo, SNode **pNode, SFltTreeStat *pStat) { FLT_ERR_JRET(pStat->code); - /* - int32_t nodeNum = taosArrayGetSize(pStat->nodeList); - for (int32_t i = 0; i < nodeNum; ++i) { - SValueNode *valueNode = *(SValueNode **)taosArrayGet(pStat->nodeList, i); - - FLT_ERR_JRET(sclConvertToTsValueNode(pStat->precision, valueNode)); - } - */ - _return: taosArrayDestroy(pStat->nodeList); diff --git a/source/libs/scalar/src/scalar.c b/source/libs/scalar/src/scalar.c index 209110b014..d533eba74e 100644 --- a/source/libs/scalar/src/scalar.c +++ b/source/libs/scalar/src/scalar.c @@ -24,7 +24,7 @@ int32_t scalarGetOperatorParamNum(EOperatorType type) { int32_t sclConvertToTsValueNode(int8_t precision, SValueNode *valueNode) { char *timeStr = valueNode->datum.p; int64_t value = 0; - int32_t code = convertStringToTimestamp(valueNode->node.resType.type, valueNode->datum.p, precision, &value); + int32_t code = convertStringToTimestamp(valueNode->node.resType.type, valueNode->datum.p, precision, &value, valueNode->tz); //todo tz if (code != TSDB_CODE_SUCCESS) { return code; } @@ -81,6 +81,8 @@ int32_t sclConvertValueToSclParam(SValueNode *pValueNode, SScalarParam *out, int goto _exit; } + in.tz = pValueNode->tz; + out->tz = pValueNode->tz; code = vectorConvertSingleColImpl(&in, out, overflow, -1, -1); _exit: @@ -586,6 +588,7 @@ int32_t sclInitOperatorParams(SScalarParam **pParams, SOperatorNode *node, SScal SCL_ERR_JRET(sclSetOperatorValueType(node, ctx)); SCL_ERR_JRET(sclInitParam(node->pLeft, ¶mList[0], ctx, rowNum)); + paramList[0].tz = node->tz; if (paramNum > 1) { TSWAP(ctx->type.selfType, ctx->type.peerType); SCL_ERR_JRET(sclInitParam(node->pRight, ¶mList[1], ctx, rowNum)); @@ -756,6 +759,7 @@ int32_t sclExecFunction(SFunctionNode *node, SScalarCtx *ctx, SScalarParam *outp int32_t paramNum = 0; int32_t code = 0; SCL_ERR_RET(sclInitParamList(¶ms, node->pParameterList, ctx, ¶mNum, &rowNum)); + params->tz = node->tz; if (fmIsUserDefinedFunc(node->funcId)) { code = callUdfScalarFunc(node->functionName, params, paramNum, output); @@ -942,7 +946,7 @@ int32_t sclExecCaseWhen(SCaseWhenNode *node, SScalarCtx *ctx, SScalarParam *outp SCL_ERR_JRET(sclGetNodeRes(node->pCase, ctx, &pCase)); SCL_ERR_JRET(sclGetNodeRes(node->pElse, ctx, &pElse)); - + pCase->tz = node->tz; SDataType compType = {0}; compType.type = TSDB_DATA_TYPE_BOOL; compType.bytes = tDataTypes[compType.type].bytes; diff --git a/source/libs/scalar/src/sclfunc.c b/source/libs/scalar/src/sclfunc.c index 9e75abcfe8..1ddf74eb2d 100644 --- a/source/libs/scalar/src/sclfunc.c +++ b/source/libs/scalar/src/sclfunc.c @@ -2053,7 +2053,7 @@ int32_t castFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutp if (inputType == TSDB_DATA_TYPE_BINARY || inputType == TSDB_DATA_TYPE_NCHAR) { int64_t timePrec; GET_TYPED_DATA(timePrec, int64_t, GET_PARAM_TYPE(&pInput[1]), pInput[1].columnData->pData); - int32_t ret = convertStringToTimestamp(inputType, input, timePrec, &timeVal); + int32_t ret = convertStringToTimestamp(inputType, input, timePrec, &timeVal, pInput->tz); if (ret != TSDB_CODE_SUCCESS) { *(int64_t *)output = 0; } else { @@ -2240,17 +2240,14 @@ int32_t toISO8601Function(SScalarParam *pInput, int32_t inputNum, SScalarParam * if (0 != offsetOfTimezone(tz, &offset)) { goto _end; } - quot -= offset + 3600 * ((int64_t)tsTimezone); + quot -= offset; struct tm tmInfo; - int32_t len = 0; - - if (taosLocalTime((const time_t *)", &tmInfo, buf, sizeof(buf)) == NULL) { - len = (int32_t)strlen(buf); + if (taosGmTimeR((const time_t *)", &tmInfo) == NULL) { goto _end; } - len = (int32_t)strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S", &tmInfo); + int32_t len = (int32_t)strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S", &tmInfo); len += tsnprintf(buf + len, fractionLen, format, mod); @@ -2286,7 +2283,7 @@ int32_t toUnixtimestampFunction(SScalarParam *pInput, int32_t inputNum, SScalarP char *input = colDataGetData(pInput[0].columnData, i); int64_t timeVal = 0; - int32_t ret = convertStringToTimestamp(type, input, timePrec, &timeVal); + int32_t ret = convertStringToTimestamp(type, input, timePrec, &timeVal, pInput->tz); if (ret != TSDB_CODE_SUCCESS) { colDataSetNULL(pOutput->columnData, i); } else { @@ -2381,7 +2378,7 @@ int32_t toTimestampFunction(SScalarParam* pInput, int32_t inputNum, SScalarParam } int32_t precision = pOutput->columnData->info.precision; char errMsg[128] = {0}; - code = taosChar2Ts(format, &formats, tsStr, &ts, precision, errMsg, 128); + code = taosChar2Ts(format, &formats, tsStr, &ts, precision, errMsg, 128, pInput->tz); if (code) { qError("func to_timestamp failed %s", errMsg); SCL_ERR_JRET(code); @@ -2424,7 +2421,7 @@ int32_t toCharFunction(SScalarParam* pInput, int32_t inputNum, SScalarParam* pOu } } int32_t precision = pInput[0].columnData->info.precision; - SCL_ERR_JRET(taosTs2Char(format, &formats, *(int64_t *)ts, precision, varDataVal(out), TS_FORMAT_MAX_LEN)); + SCL_ERR_JRET(taosTs2Char(format, &formats, *(int64_t *)ts, precision, varDataVal(out), TS_FORMAT_MAX_LEN, pInput->tz)); varDataSetLen(out, strlen(varDataVal(out))); SCL_ERR_JRET(colDataSetVal(pOutput->columnData, i, out, false)); } @@ -2437,11 +2434,11 @@ _return: } /** Time functions **/ -int64_t offsetFromTz(char *timezone, int64_t factor) { - char *minStr = &timezone[3]; +int64_t offsetFromTz(char *timezoneStr, int64_t factor) { + char *minStr = &timezoneStr[3]; int64_t minutes = taosStr2Int64(minStr, NULL, 10); (void)memset(minStr, 0, strlen(minStr)); - int64_t hours = taosStr2Int64(timezone, NULL, 10); + int64_t hours = taosStr2Int64(timezoneStr, NULL, 10); int64_t seconds = hours * 3600 + minutes * 60; return seconds * factor; @@ -2453,7 +2450,7 @@ int32_t timeTruncateFunction(SScalarParam *pInput, int32_t inputNum, SScalarPara int64_t timeUnit, timePrec, timeVal = 0; bool ignoreTz = true; - char timezone[20] = {0}; + char timezoneStr[20] = {0}; GET_TYPED_DATA(timeUnit, int64_t, GET_PARAM_TYPE(&pInput[1]), pInput[1].columnData->pData); @@ -2465,7 +2462,7 @@ int32_t timeTruncateFunction(SScalarParam *pInput, int32_t inputNum, SScalarPara } GET_TYPED_DATA(timePrec, int64_t, GET_PARAM_TYPE(&pInput[timePrecIdx]), pInput[timePrecIdx].columnData->pData); - (void)memcpy(timezone, varDataVal(pInput[timeZoneIdx].columnData->pData), varDataLen(pInput[timeZoneIdx].columnData->pData)); + (void)memcpy(timezoneStr, varDataVal(pInput[timeZoneIdx].columnData->pData), varDataLen(pInput[timeZoneIdx].columnData->pData)); for (int32_t i = 0; i < pInput[0].numOfRows; ++i) { if (colDataIsNull_s(pInput[0].columnData, i)) { @@ -2476,7 +2473,7 @@ int32_t timeTruncateFunction(SScalarParam *pInput, int32_t inputNum, SScalarPara char *input = colDataGetData(pInput[0].columnData, i); if (IS_VAR_DATA_TYPE(type)) { /* datetime format strings */ - int32_t ret = convertStringToTimestamp(type, input, timePrec, &timeVal); + int32_t ret = convertStringToTimestamp(type, input, timePrec, &timeVal, pInput->tz); if (ret != TSDB_CODE_SUCCESS) { colDataSetNULL(pOutput->columnData, i); continue; @@ -2493,7 +2490,7 @@ int32_t timeTruncateFunction(SScalarParam *pInput, int32_t inputNum, SScalarPara // truncate the timestamp to time_unit precision int64_t seconds = timeUnit / TSDB_TICK_PER_SECOND(timePrec); if (ignoreTz && (seconds == 604800 || seconds == 86400)) { - timeVal = timeVal - (timeVal + offsetFromTz(timezone, TSDB_TICK_PER_SECOND(timePrec))) % timeUnit; + timeVal = timeVal - (timeVal + offsetFromTz(timezoneStr, TSDB_TICK_PER_SECOND(timePrec))) % timeUnit; } else { timeVal = timeVal / timeUnit * timeUnit; } @@ -2536,7 +2533,7 @@ int32_t timeDiffFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *p int32_t type = GET_PARAM_TYPE(&pInput[k]); if (IS_VAR_DATA_TYPE(type)) { /* datetime format strings */ - int32_t ret = convertStringToTimestamp(type, input[k], TSDB_TIME_PRECISION_NANO, &timeVal[k]); + int32_t ret = convertStringToTimestamp(type, input[k], TSDB_TIME_PRECISION_NANO, &timeVal[k], pInput->tz); if (ret != TSDB_CODE_SUCCESS) { hasNull = true; break; @@ -2655,7 +2652,7 @@ int32_t todayFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOut int64_t timePrec; GET_TYPED_DATA(timePrec, int64_t, GET_PARAM_TYPE(&pInput[0]), pInput[0].columnData->pData); - int64_t ts = taosGetTimestampToday(timePrec); + int64_t ts = taosGetTimestampToday(timePrec, pInput->tz); for (int32_t i = 0; i < pInput->numOfRows; ++i) { colDataSetInt64(pOutput->columnData, i, &ts); } @@ -2665,8 +2662,16 @@ int32_t todayFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOut int32_t timezoneFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput) { char output[TD_TIMEZONE_LEN + VARSTR_HEADER_SIZE] = {0}; - (void)memcpy(varDataVal(output), tsTimezoneStr, TD_TIMEZONE_LEN); - varDataSetLen(output, strlen(tsTimezoneStr)); + // pInput->tz todo tz + char* tmp = NULL; + if (pInput->tz == NULL) { + (void)memcpy(varDataVal(output), tsTimezoneStr, TD_TIMEZONE_LEN); + } else{ + time_t tx1 = taosGetTimestampSec(); + SCL_ERR_RET(taosFormatTimezoneStr(tx1, "UTC-test", pInput->tz, varDataVal(output))); + } + + varDataSetLen(output, strlen(varDataVal(output))); for (int32_t i = 0; i < pInput->numOfRows; ++i) { SCL_ERR_RET(colDataSetVal(pOutput->columnData, i, output, false)); } @@ -2690,7 +2695,7 @@ int32_t weekdayFunctionImpl(SScalarParam *pInput, int32_t inputNum, SScalarParam char *input = colDataGetData(pInput[0].columnData, i); if (IS_VAR_DATA_TYPE(type)) { /* datetime format strings */ - int32_t ret = convertStringToTimestamp(type, input, timePrec, &timeVal); + int32_t ret = convertStringToTimestamp(type, input, timePrec, &timeVal, pInput->tz); if (ret != TSDB_CODE_SUCCESS) { colDataSetNULL(pOutput->columnData, i); continue; @@ -2701,7 +2706,7 @@ int32_t weekdayFunctionImpl(SScalarParam *pInput, int32_t inputNum, SScalarParam GET_TYPED_DATA(timeVal, int64_t, type, input); } struct STm tm; - TAOS_CHECK_RETURN(taosTs2Tm(timeVal, timePrec, &tm)); + TAOS_CHECK_RETURN(taosTs2Tm(timeVal, timePrec, &tm, pInput->tz)); int64_t ret = startFromZero ? (tm.tm.tm_wday + 6) % 7 : tm.tm.tm_wday + 1; colDataSetInt64(pOutput->columnData, i, &ret); } @@ -2800,7 +2805,7 @@ int32_t weekFunctionImpl(SScalarParam *pInput, int32_t inputNum, SScalarParam *p char *input = colDataGetData(pInput[0].columnData, i); if (IS_VAR_DATA_TYPE(type)) { /* datetime format strings */ - int32_t ret = convertStringToTimestamp(type, input, prec, &timeVal); + int32_t ret = convertStringToTimestamp(type, input, prec, &timeVal, pInput->tz); if (ret != TSDB_CODE_SUCCESS) { colDataSetNULL(pOutput->columnData, i); continue; @@ -2811,7 +2816,7 @@ int32_t weekFunctionImpl(SScalarParam *pInput, int32_t inputNum, SScalarParam *p GET_TYPED_DATA(timeVal, int64_t, type, input); } struct STm tm; - SCL_ERR_RET(taosTs2Tm(timeVal, prec, &tm)); + SCL_ERR_RET(taosTs2Tm(timeVal, prec, &tm, pInput->tz)); int64_t ret = calculateWeekNum(tm.tm, weekMode(mode)); colDataSetInt64(pOutput->columnData, i, &ret); } diff --git a/source/libs/scalar/src/sclvector.c b/source/libs/scalar/src/sclvector.c index 7e37d00bfe..d9becf199a 100644 --- a/source/libs/scalar/src/sclvector.c +++ b/source/libs/scalar/src/sclvector.c @@ -242,7 +242,7 @@ int32_t getVectorBigintValueFn(int32_t srcType, _getBigintValue_fn_t *p) { static FORCE_INLINE int32_t varToTimestamp(char *buf, SScalarParam *pOut, int32_t rowIndex, int32_t *overflow) { int64_t value = 0; int32_t code = TSDB_CODE_SUCCESS; - if (taosParseTime(buf, &value, strlen(buf), pOut->columnData->info.precision) != TSDB_CODE_SUCCESS) { + if (taosParseTime(buf, &value, strlen(buf), pOut->columnData->info.precision, pOut->tz) != TSDB_CODE_SUCCESS) { value = 0; code = TSDB_CODE_SCALAR_CONVERT_ERROR; } @@ -2042,6 +2042,9 @@ int32_t vectorCompareImpl(SScalarParam *pLeft, SScalarParam *pRight, SScalarPara SScalarParam *param1 = NULL; SScalarParam *param2 = NULL; int32_t code = TSDB_CODE_SUCCESS; + pRight->tz = pLeft->tz; + pLeftOut.tz = pLeft->tz; + pRightOut.tz = pRight->tz; if (noConvertBeforeCompare(GET_PARAM_TYPE(pLeft), GET_PARAM_TYPE(pRight), optr)) { param1 = pLeft; param2 = pRight; @@ -2125,6 +2128,7 @@ int32_t vectorIsNull(SScalarParam *pLeft, SScalarParam *pRight, SScalarParam *pO } int32_t vectorNotNull(SScalarParam *pLeft, SScalarParam *pRight, SScalarParam *pOut, int32_t _ord) { + pRight->tz = pLeft->tz; for (int32_t i = 0; i < pLeft->numOfRows; ++i) { int8_t v = IS_HELPER_NULL(pLeft->columnData, i) ? 0 : 1; if (v) { @@ -2138,6 +2142,7 @@ int32_t vectorNotNull(SScalarParam *pLeft, SScalarParam *pRight, SScalarParam *p } int32_t vectorIsTrue(SScalarParam *pLeft, SScalarParam *pRight, SScalarParam *pOut, int32_t _ord) { + pRight->tz = pLeft->tz; SCL_ERR_RET(vectorConvertSingleColImpl(pLeft, pOut, NULL, -1, -1)); for (int32_t i = 0; i < pOut->numOfRows; ++i) { if (colDataIsNull_s(pOut->columnData, i)) { diff --git a/source/os/src/osTime.c b/source/os/src/osTime.c index 3798a96e9d..3d026b3ccc 100644 --- a/source/os/src/osTime.c +++ b/source/os/src/osTime.c @@ -413,7 +413,7 @@ int64_t user_mktime64(const uint32_t year, const uint32_t mon, const uint32_t da return _res + time_zone; } -time_t taosMktime(struct tm *timep) { +time_t taosMktime(struct tm *timep, timezone_t tz) { #ifdef WINDOWS #if 0 struct tm tm1 = {0}; @@ -466,7 +466,7 @@ time_t taosMktime(struct tm *timep) { timep->tm_sec, tz); #endif #else - time_t r = mktime(timep); + time_t r = tz != NULL ? mktime_z(tz, timep) : mktime(timep); if (r == (time_t)-1) { terrno = TAOS_SYSTEM_ERROR(errno); } @@ -474,7 +474,74 @@ time_t taosMktime(struct tm *timep) { #endif } -struct tm *taosLocalTime(const time_t *timep, struct tm *result, char *buf, int32_t bufSize) { +struct tm *taosGmTimeR(const time_t *timep, struct tm *result){ + if (timep == NULL || result == NULL) { + return NULL; + } +#ifdef WINDOWS + if (*timep < -2208988800LL) { + if (buf != NULL) { + snprintf(buf, bufSize, "NaN"); + } + return NULL; + } else if (*timep < 0) { + SYSTEMTIME ss, s; + FILETIME ff, f; + + LARGE_INTEGER offset; + struct tm tm1; + time_t tt = 0; + if (localtime_s(&tm1, &tt) != 0) { + if (buf != NULL) { + snprintf(buf, bufSize, "NaN"); + } + return NULL; + } + ss.wYear = tm1.tm_year + 1900; + ss.wMonth = tm1.tm_mon + 1; + ss.wDay = tm1.tm_mday; + ss.wHour = tm1.tm_hour; + ss.wMinute = tm1.tm_min; + ss.wSecond = tm1.tm_sec; + ss.wMilliseconds = 0; + SystemTimeToFileTime(&ss, &ff); + offset.QuadPart = ff.dwHighDateTime; + offset.QuadPart <<= 32; + offset.QuadPart |= ff.dwLowDateTime; + offset.QuadPart += *timep * 10000000; + f.dwLowDateTime = offset.QuadPart & 0xffffffff; + f.dwHighDateTime = (offset.QuadPart >> 32) & 0xffffffff; + FileTimeToSystemTime(&f, &s); + result->tm_sec = s.wSecond; + result->tm_min = s.wMinute; + result->tm_hour = s.wHour; + result->tm_mday = s.wDay; + result->tm_mon = s.wMonth - 1; + result->tm_year = s.wYear - 1900; + result->tm_wday = s.wDayOfWeek; + result->tm_yday = 0; + result->tm_isdst = 0; + } else { + if (localtime_s(result, timep) != 0) { + if (buf != NULL) { + snprintf(buf, bufSize, "NaN"); + } + return NULL; + } + } +#else + return gmtime_r(timep, result); +#endif +} + +time_t taosTimeGm(struct tm *tmp){ + if (tmp == NULL) { + return -1; + } + return timegm(tmp); +} + +struct tm *taosLocalTime(const time_t *timep, struct tm *result, char *buf, int32_t bufSize, timezone_t tz) { struct tm *res = NULL; if (timep == NULL || result == NULL) { return NULL; @@ -531,7 +598,7 @@ struct tm *taosLocalTime(const time_t *timep, struct tm *result, char *buf, int3 } } #else - res = localtime_r(timep, result); + res = tz != NULL ? localtime_rz(tz, timep, result): localtime_r(timep, result); if (res == NULL && buf != NULL) { (void)snprintf(buf, bufSize, "NaN"); } @@ -539,233 +606,6 @@ struct tm *taosLocalTime(const time_t *timep, struct tm *result, char *buf, int3 return result; } -time_t taosMktimeRz(timezone_t state, struct tm *timep) { -#ifdef WINDOWS - #if 0 - struct tm tm1 = {0}; - LARGE_INTEGER t; - FILETIME f; - SYSTEMTIME s; - FILETIME ff; - SYSTEMTIME ss; - LARGE_INTEGER offset; - - time_t tt = 0; - localtime_s(&tm1, &tt); - ss.wYear = tm1.tm_year + 1900; - ss.wMonth = tm1.tm_mon + 1; - ss.wDay = tm1.tm_mday; - ss.wHour = tm1.tm_hour; - ss.wMinute = tm1.tm_min; - ss.wSecond = tm1.tm_sec; - ss.wMilliseconds = 0; - SystemTimeToFileTime(&ss, &ff); - offset.QuadPart = ff.dwHighDateTime; - offset.QuadPart <<= 32; - offset.QuadPart |= ff.dwLowDateTime; - - s.wYear = timep->tm_year + 1900; - s.wMonth = timep->tm_mon + 1; - s.wDay = timep->tm_mday; - s.wHour = timep->tm_hour; - s.wMinute = timep->tm_min; - s.wSecond = timep->tm_sec; - s.wMilliseconds = 0; - SystemTimeToFileTime(&s, &f); - t.QuadPart = f.dwHighDateTime; - t.QuadPart <<= 32; - t.QuadPart |= f.dwLowDateTime; - - t.QuadPart -= offset.QuadPart; - return (time_t)(t.QuadPart / 10000000); -#else - time_t result = mktime(timep); - if (result != -1) { - return result; - } -#ifdef _MSC_VER -#if _MSC_VER >= 1900 - int64_t tz = _timezone; -#endif -#endif - return user_mktime64(timep->tm_year + 1900, timep->tm_mon + 1, timep->tm_mday, timep->tm_hour, timep->tm_min, - timep->tm_sec, tz); -#endif -#else - time_t r = mktime_z(state, timep); - if (r == (time_t)-1) { - terrno = TAOS_SYSTEM_ERROR(errno); - } - return r; -#endif -} - -struct tm *taosLocalTimeRz(timezone_t state, const time_t *timep, struct tm *result) { - struct tm *res = NULL; - if (timep == NULL || result == NULL) { - return NULL; - } -#ifdef WINDOWS - if (*timep < -2208988800LL) { - if (buf != NULL) { - snprintf(buf, bufSize, "NaN"); - } - return NULL; - } else if (*timep < 0) { - SYSTEMTIME ss, s; - FILETIME ff, f; - - LARGE_INTEGER offset; - struct tm tm1; - time_t tt = 0; - if (localtime_s(&tm1, &tt) != 0) { - if (buf != NULL) { - snprintf(buf, bufSize, "NaN"); - } - return NULL; - } - ss.wYear = tm1.tm_year + 1900; - ss.wMonth = tm1.tm_mon + 1; - ss.wDay = tm1.tm_mday; - ss.wHour = tm1.tm_hour; - ss.wMinute = tm1.tm_min; - ss.wSecond = tm1.tm_sec; - ss.wMilliseconds = 0; - SystemTimeToFileTime(&ss, &ff); - offset.QuadPart = ff.dwHighDateTime; - offset.QuadPart <<= 32; - offset.QuadPart |= ff.dwLowDateTime; - offset.QuadPart += *timep * 10000000; - f.dwLowDateTime = offset.QuadPart & 0xffffffff; - f.dwHighDateTime = (offset.QuadPart >> 32) & 0xffffffff; - FileTimeToSystemTime(&f, &s); - result->tm_sec = s.wSecond; - result->tm_min = s.wMinute; - result->tm_hour = s.wHour; - result->tm_mday = s.wDay; - result->tm_mon = s.wMonth - 1; - result->tm_year = s.wYear - 1900; - result->tm_wday = s.wDayOfWeek; - result->tm_yday = 0; - result->tm_isdst = 0; - } else { - if (localtime_s(result, timep) != 0) { - if (buf != NULL) { - snprintf(buf, bufSize, "NaN"); - } - return NULL; - } - } -#else - return localtime_rz(state, timep, result); -#endif -} - -static int isLeapYear(time_t year) { - if (year % 4) - return 0; - else if (year % 100) - return 1; - else if (year % 400) - return 0; - else - return 1; -} - -struct tm *taosLocalTimeNolock(struct tm *result, const time_t *timep, int dst) { - if (result == NULL) { - return NULL; - } -#ifdef WINDOWS - if (*timep < 0) { - return NULL; - // TODO: bugs in following code - SYSTEMTIME ss, s; - FILETIME ff, f; - LARGE_INTEGER offset; - struct tm tm1; - time_t tt = 0; - if (localtime_s(&tm1, &tt) != 0) { - return NULL; - } - ss.wYear = tm1.tm_year + 1900; - ss.wMonth = tm1.tm_mon + 1; - ss.wDay = tm1.tm_mday; - ss.wHour = tm1.tm_hour; - ss.wMinute = tm1.tm_min; - ss.wSecond = tm1.tm_sec; - ss.wMilliseconds = 0; - SystemTimeToFileTime(&ss, &ff); - offset.QuadPart = ff.dwHighDateTime; - offset.QuadPart <<= 32; - offset.QuadPart |= ff.dwLowDateTime; - offset.QuadPart += *timep * 10000000; - f.dwLowDateTime = offset.QuadPart & 0xffffffff; - f.dwHighDateTime = (offset.QuadPart >> 32) & 0xffffffff; - FileTimeToSystemTime(&f, &s); - result->tm_sec = s.wSecond; - result->tm_min = s.wMinute; - result->tm_hour = s.wHour; - result->tm_mday = s.wDay; - result->tm_mon = s.wMonth - 1; - result->tm_year = s.wYear - 1900; - result->tm_wday = s.wDayOfWeek; - result->tm_yday = 0; - result->tm_isdst = 0; - } else { - if (localtime_s(result, timep) != 0) { - return NULL; - } - } -#elif defined(LINUX) - time_t secsMin = 60, secsHour = 3600, secsDay = 3600 * 24; - long tz = timezone; - - time_t t = *timep; - t -= tz; /* Adjust for timezone. */ - t += 3600 * dst; /* Adjust for daylight time. */ - time_t days = t / secsDay; /* Days passed since epoch. */ - time_t seconds = t % secsDay; /* Remaining seconds. */ - - result->tm_isdst = dst; - result->tm_hour = seconds / secsHour; - result->tm_min = (seconds % secsHour) / secsMin; - result->tm_sec = (seconds % secsHour) % secsMin; - - /* 1/1/1970 was a Thursday, that is, day 4 from the POV of the tm structure - * where sunday = 0, so to calculate the day of the week we have to add 4 - * and take the modulo by 7. */ - result->tm_wday = (days + 4) % 7; - - /* Calculate the current year. */ - result->tm_year = 1970; - while (1) { - /* Leap years have one day more. */ - time_t daysOfYear = 365 + isLeapYear(result->tm_year); - if (daysOfYear > days) break; - days -= daysOfYear; - result->tm_year++; - } - result->tm_yday = days; /* Number of day of the current year. */ - /* We need to calculate in which month and day of the month we are. To do - * so we need to skip days according to how many days there are in each - * month, and adjust for the leap year that has one more day in February. */ - int mdays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - mdays[1] += isLeapYear(result->tm_year); - result->tm_mon = 0; - while (days >= mdays[result->tm_mon]) { - days -= mdays[result->tm_mon]; - result->tm_mon++; - } - - result->tm_mday = days + 1; /* Add 1 since our 'days' is zero-based. */ - result->tm_year -= 1900; /* Surprisingly tm_year is year-1900. */ -#else - localtime_r(timep, result); -#endif - return result; -} - int32_t taosGetTimestampSec() { return (int32_t)time(NULL); } int32_t taosClockGetTime(int clock_id, struct timespec *pTS) { diff --git a/source/os/src/osTimezone.c b/source/os/src/osTimezone.c index 1b54f3a42c..83100d4327 100644 --- a/source/os/src/osTimezone.c +++ b/source/os/src/osTimezone.c @@ -764,7 +764,7 @@ int32_t taosSetGlobalTimezone(const char *tz) { return terrno; } int32_t code = TSDB_CODE_SUCCESS; - + uDebug("[tz]set timezone to %s", tz) #ifdef WINDOWS char winStr[TD_LOCALE_LEN * 2]; memset(winStr, 0, sizeof(winStr)); @@ -830,16 +830,9 @@ int32_t taosSetGlobalTimezone(const char *tz) { int32_t taosFormatTimezoneStr(time_t t, const char* tz, timezone_t sp, char *outTimezoneStr){ struct tm tm1; - if (sp == NULL) { - if (taosLocalTime(&t, &tm1, NULL, 0) == NULL) { - uError("failed to get local time"); - return TSDB_CODE_TIME_ERROR; - } - } else { - if (taosLocalTimeRz(sp, &t, &tm1) == NULL) { - uError("failed to get rz time"); - return TSDB_CODE_TIME_ERROR; - } + if (taosLocalTime(&t, &tm1, NULL, 0, sp) == NULL) { + uError("%s failed to get local time: code:%d", __FUNCTION__, errno); + return TSDB_CODE_TIME_ERROR; } /* @@ -862,12 +855,13 @@ int32_t taosFormatTimezoneStr(time_t t, const char* tz, timezone_t sp, char *out return TSDB_CODE_TIME_ERROR; } (void)snprintf(outTimezoneStr, TD_TIMEZONE_LEN, "%s (%s, %s)", tz, str1, str2); + uDebug("[tz] system timezone:%s", outTimezoneStr); return 0; } void getTimezoneStr(char *tz) { do { - int n = readlink("/r/localtime", tz, TD_TIMEZONE_LEN - 1); + int n = readlink("/etc/localtime", tz, TD_TIMEZONE_LEN - 1); if (n < 0) { uWarn("[tz] failed to readlink /etc/localtime, reason:%s", strerror(errno)); break; @@ -903,6 +897,7 @@ END: if (tz[0] == '\0') { memcpy(tz, "n/a", sizeof("n/a")); } + uDebug("[tz] system timezone:%s", tz); } int32_t taosGetSystemTimezone(char *outTimezoneStr) { @@ -3211,3 +3206,627 @@ leapcorr(struct state const *sp, time_t t) } return 0; } + + +/* + * ************************************************************************** + * The code from here to the end of the file is the code for the + * strftime() function. + * ***************** + */ + + +#ifndef DEPRECATE_TWO_DIGIT_YEARS +# define DEPRECATE_TWO_DIGIT_YEARS false +#endif + +struct lc_time_T { + const char * mon[MONSPERYEAR]; + const char * month[MONSPERYEAR]; + const char * wday[DAYSPERWEEK]; + const char * weekday[DAYSPERWEEK]; + const char * X_fmt; + const char * x_fmt; + const char * c_fmt; + const char * am; + const char * pm; + const char * date_fmt; +}; + +static const struct lc_time_T C_time_locale = { + { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }, { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" + }, { + "Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat" + }, { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday" + }, + + /* X_fmt */ + "%H:%M:%S", + + /* + ** x_fmt + ** C99 and later require this format. + ** Using just numbers (as here) makes Quakers happier; + ** it's also compatible with SVR4. + */ + "%m/%d/%y", + + /* + ** c_fmt + ** C99 and later require this format. + ** Previously this code used "%D %X", but we now conform to C99. + ** Note that + ** "%a %b %d %H:%M:%S %Y" + ** is used by Solaris 2.3. + */ + "%a %b %e %T %Y", + + /* am */ + "AM", + + /* pm */ + "PM", + + /* date_fmt */ + "%a %b %e %H:%M:%S %Z %Y" +}; + +enum warn { IN_NONE, IN_SOME, IN_THIS, IN_ALL }; + +static char * _add(const char *, char *, const char *); +static char * _conv(int, const char *, char *, const char *); +static char * _fmt(const char *, const struct tm *, char *, const char *, + enum warn *); +static char * _yconv(int, int, bool, bool, char *, char const *); + +#ifndef YEAR_2000_NAME +# define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS" +#endif /* !defined YEAR_2000_NAME */ + +#if HAVE_STRFTIME_L +size_t +strftime_l(char *restrict s, size_t maxsize, char const *restrict format, + struct tm const *restrict t, + ATTRIBUTE_MAYBE_UNUSED locale_t locale) +{ + /* Just call strftime, as only the C locale is supported. */ + return strftime(s, maxsize, format, t); +} +#endif + +size_t +strftime(char *restrict s, size_t maxsize, char const *restrict format, + struct tm const *restrict t) +{ + char * p; + int saved_errno = errno; + enum warn warn = IN_NONE; + + tzset(); + p = _fmt(format, t, s, s + maxsize, &warn); + if (DEPRECATE_TWO_DIGIT_YEARS + && warn != IN_NONE && getenv(YEAR_2000_NAME)) { + fprintf(stderr, "\n"); + fprintf(stderr, "strftime format \"%s\" ", format); + fprintf(stderr, "yields only two digits of years in "); + if (warn == IN_SOME) + fprintf(stderr, "some locales"); + else if (warn == IN_THIS) + fprintf(stderr, "the current locale"); + else fprintf(stderr, "all locales"); + fprintf(stderr, "\n"); + } + if (p == s + maxsize) { + errno = ERANGE; + return 0; + } + *p = '\0'; + errno = saved_errno; + return p - s; +} + +static char * +_fmt(const char *format, const struct tm *t, char *pt, + const char *ptlim, enum warn *warnp) +{ + struct lc_time_T const *Locale = &C_time_locale; + + for ( ; *format; ++format) { + if (*format == '%') { + label: + switch (*++format) { + default: + /* Output unknown conversion specifiers as-is, + to aid debugging. This includes '%' at + format end. This conforms to C23 section + 7.29.3.5 paragraph 6, which says behavior + is undefined here. */ + --format; + break; + case 'A': + pt = _add((t->tm_wday < 0 || + t->tm_wday >= DAYSPERWEEK) ? + "?" : Locale->weekday[t->tm_wday], + pt, ptlim); + continue; + case 'a': + pt = _add((t->tm_wday < 0 || + t->tm_wday >= DAYSPERWEEK) ? + "?" : Locale->wday[t->tm_wday], + pt, ptlim); + continue; + case 'B': + pt = _add((t->tm_mon < 0 || + t->tm_mon >= MONSPERYEAR) ? + "?" : Locale->month[t->tm_mon], + pt, ptlim); + continue; + case 'b': + case 'h': + pt = _add((t->tm_mon < 0 || + t->tm_mon >= MONSPERYEAR) ? + "?" : Locale->mon[t->tm_mon], + pt, ptlim); + continue; + case 'C': + /* + ** %C used to do a... + ** _fmt("%a %b %e %X %Y", t); + ** ...whereas now POSIX 1003.2 calls for + ** something completely different. + ** (ado, 1993-05-24) + */ + pt = _yconv(t->tm_year, TM_YEAR_BASE, + true, false, pt, ptlim); + continue; + case 'c': + { + enum warn warn2 = IN_SOME; + + pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2); + if (warn2 == IN_ALL) + warn2 = IN_THIS; + if (warn2 > *warnp) + *warnp = warn2; + } + continue; + case 'D': + pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp); + continue; + case 'd': + pt = _conv(t->tm_mday, "%02d", pt, ptlim); + continue; + case 'E': + case 'O': + /* + ** Locale modifiers of C99 and later. + ** The sequences + ** %Ec %EC %Ex %EX %Ey %EY + ** %Od %oe %OH %OI %Om %OM + ** %OS %Ou %OU %OV %Ow %OW %Oy + ** are supposed to provide alternative + ** representations. + */ + goto label; + case 'e': + pt = _conv(t->tm_mday, "%2d", pt, ptlim); + continue; + case 'F': + pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp); + continue; + case 'H': + pt = _conv(t->tm_hour, "%02d", pt, ptlim); + continue; + case 'I': + pt = _conv((t->tm_hour % 12) ? + (t->tm_hour % 12) : 12, + "%02d", pt, ptlim); + continue; + case 'j': + pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim); + continue; + case 'k': + /* + ** This used to be... + ** _conv(t->tm_hour % 12 ? + ** t->tm_hour % 12 : 12, 2, ' '); + ** ...and has been changed to the below to + ** match SunOS 4.1.1 and Arnold Robbins' + ** strftime version 3.0. That is, "%k" and + ** "%l" have been swapped. + ** (ado, 1993-05-24) + */ + pt = _conv(t->tm_hour, "%2d", pt, ptlim); + continue; +#ifdef KITCHEN_SINK + case 'K': + /* + ** After all this time, still unclaimed! + */ + pt = _add("kitchen sink", pt, ptlim); + continue; +#endif /* defined KITCHEN_SINK */ + case 'l': + /* + ** This used to be... + ** _conv(t->tm_hour, 2, ' '); + ** ...and has been changed to the below to + ** match SunOS 4.1.1 and Arnold Robbin's + ** strftime version 3.0. That is, "%k" and + ** "%l" have been swapped. + ** (ado, 1993-05-24) + */ + pt = _conv((t->tm_hour % 12) ? + (t->tm_hour % 12) : 12, + "%2d", pt, ptlim); + continue; + case 'M': + pt = _conv(t->tm_min, "%02d", pt, ptlim); + continue; + case 'm': + pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim); + continue; + case 'n': + pt = _add("\n", pt, ptlim); + continue; + case 'p': + pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ? + Locale->pm : + Locale->am, + pt, ptlim); + continue; + case 'R': + pt = _fmt("%H:%M", t, pt, ptlim, warnp); + continue; + case 'r': + pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp); + continue; + case 'S': + pt = _conv(t->tm_sec, "%02d", pt, ptlim); + continue; + case 's': + { + struct tm tm; + char buf[INT_STRLEN_MAXIMUM( + time_t) + 1]; + time_t mkt; + + tm.tm_sec = t->tm_sec; + tm.tm_min = t->tm_min; + tm.tm_hour = t->tm_hour; + tm.tm_mday = t->tm_mday; + tm.tm_mon = t->tm_mon; + tm.tm_year = t->tm_year; +#ifdef TM_GMTOFF + mkt = timeoff(&tm, t->TM_GMTOFF); +#else + tm.tm_isdst = t->tm_isdst; + mkt = mktime(&tm); +#endif + /* If mktime fails, %s expands to the + value of (time_t) -1 as a failure + marker; this is better in practice + than strftime failing. */ + if (TYPE_SIGNED(time_t)) { + intmax_t n = mkt; + sprintf(buf, "%"PRIdMAX, n); + } else { + uintmax_t n = mkt; + sprintf(buf, "%"PRIuMAX, n); + } + pt = _add(buf, pt, ptlim); + } + continue; + case 'T': + pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp); + continue; + case 't': + pt = _add("\t", pt, ptlim); + continue; + case 'U': + pt = _conv((t->tm_yday + DAYSPERWEEK - + t->tm_wday) / DAYSPERWEEK, + "%02d", pt, ptlim); + continue; + case 'u': + /* + ** From Arnold Robbins' strftime version 3.0: + ** "ISO 8601: Weekday as a decimal number + ** [1 (Monday) - 7]" + ** (ado, 1993-05-24) + */ + pt = _conv((t->tm_wday == 0) ? + DAYSPERWEEK : t->tm_wday, + "%d", pt, ptlim); + continue; + case 'V': /* ISO 8601 week number */ + case 'G': /* ISO 8601 year (four digits) */ + case 'g': /* ISO 8601 year (two digits) */ +/* +** From Arnold Robbins' strftime version 3.0: "the week number of the +** year (the first Monday as the first day of week 1) as a decimal number +** (01-53)." +** (ado, 1993-05-24) +** +** From by Markus Kuhn: +** "Week 01 of a year is per definition the first week which has the +** Thursday in this year, which is equivalent to the week which contains +** the fourth day of January. In other words, the first week of a new year +** is the week which has the majority of its days in the new year. Week 01 +** might also contain days from the previous year and the week before week +** 01 of a year is the last week (52 or 53) of the previous year even if +** it contains days from the new year. A week starts with Monday (day 1) +** and ends with Sunday (day 7). For example, the first week of the year +** 1997 lasts from 1996-12-30 to 1997-01-05..." +** (ado, 1996-01-02) +*/ + { + int year; + int base; + int yday; + int wday; + int w; + + year = t->tm_year; + base = TM_YEAR_BASE; + yday = t->tm_yday; + wday = t->tm_wday; + for ( ; ; ) { + int len; + int bot; + int top; + + len = isleap_sum(year, base) ? + DAYSPERLYEAR : + DAYSPERNYEAR; + /* + ** What yday (-3 ... 3) does + ** the ISO year begin on? + */ + bot = ((yday + 11 - wday) % + DAYSPERWEEK) - 3; + /* + ** What yday does the NEXT + ** ISO year begin on? + */ + top = bot - + (len % DAYSPERWEEK); + if (top < -3) + top += DAYSPERWEEK; + top += len; + if (yday >= top) { + ++base; + w = 1; + break; + } + if (yday >= bot) { + w = 1 + ((yday - bot) / + DAYSPERWEEK); + break; + } + --base; + yday += isleap_sum(year, base) ? + DAYSPERLYEAR : + DAYSPERNYEAR; + } +#ifdef XPG4_1994_04_09 + if ((w == 52 && + t->tm_mon == TM_JANUARY) || + (w == 1 && + t->tm_mon == TM_DECEMBER)) + w = 53; +#endif /* defined XPG4_1994_04_09 */ + if (*format == 'V') + pt = _conv(w, "%02d", + pt, ptlim); + else if (*format == 'g') { + *warnp = IN_ALL; + pt = _yconv(year, base, + false, true, + pt, ptlim); + } else pt = _yconv(year, base, + true, true, + pt, ptlim); + } + continue; + case 'v': + /* + ** From Arnold Robbins' strftime version 3.0: + ** "date as dd-bbb-YYYY" + ** (ado, 1993-05-24) + */ + pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp); + continue; + case 'W': + pt = _conv((t->tm_yday + DAYSPERWEEK - + (t->tm_wday ? + (t->tm_wday - 1) : + (DAYSPERWEEK - 1))) / DAYSPERWEEK, + "%02d", pt, ptlim); + continue; + case 'w': + pt = _conv(t->tm_wday, "%d", pt, ptlim); + continue; + case 'X': + pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp); + continue; + case 'x': + { + enum warn warn2 = IN_SOME; + + pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2); + if (warn2 == IN_ALL) + warn2 = IN_THIS; + if (warn2 > *warnp) + *warnp = warn2; + } + continue; + case 'y': + *warnp = IN_ALL; + pt = _yconv(t->tm_year, TM_YEAR_BASE, + false, true, + pt, ptlim); + continue; + case 'Y': + pt = _yconv(t->tm_year, TM_YEAR_BASE, + true, true, + pt, ptlim); + continue; + case 'Z': +#ifdef TM_ZONE + pt = _add(t->TM_ZONE, pt, ptlim); +#elif HAVE_TZNAME + if (t->tm_isdst >= 0) + pt = _add(tzname[t->tm_isdst != 0], + pt, ptlim); +#endif + /* + ** C99 and later say that %Z must be + ** replaced by the empty string if the + ** time zone abbreviation is not + ** determinable. + */ + continue; + case 'z': +#if defined TM_GMTOFF || USG_COMPAT || ALTZONE + { + long diff; + char const * sign; + bool negative; + +# ifdef TM_GMTOFF + diff = t->TM_GMTOFF; +# else + /* + ** C99 and later say that the UT offset must + ** be computed by looking only at + ** tm_isdst. This requirement is + ** incorrect, since it means the code + ** must rely on magic (in this case + ** altzone and timezone), and the + ** magic might not have the correct + ** offset. Doing things correctly is + ** tricky and requires disobeying the standard; + ** see GNU C strftime for details. + ** For now, punt and conform to the + ** standard, even though it's incorrect. + ** + ** C99 and later say that %z must be replaced by + ** the empty string if the time zone is not + ** determinable, so output nothing if the + ** appropriate variables are not available. + */ + if (t->tm_isdst < 0) + continue; + if (t->tm_isdst == 0) +# if USG_COMPAT + diff = -timezone; +# else + continue; +# endif + else +# if ALTZONE + diff = -altzone; +# else + continue; +# endif +# endif + negative = diff < 0; + if (diff == 0) { +# ifdef TM_ZONE + negative = t->TM_ZONE[0] == '-'; +# else + negative = t->tm_isdst < 0; +# if HAVE_TZNAME + if (tzname[t->tm_isdst != 0][0] == '-') + negative = true; +# endif +# endif + } + if (negative) { + sign = "-"; + diff = -diff; + } else sign = "+"; + pt = _add(sign, pt, ptlim); + diff /= SECSPERMIN; + diff = (diff / MINSPERHOUR) * 100 + + (diff % MINSPERHOUR); + pt = _conv(diff, "%04d", pt, ptlim); + } +#endif + continue; + case '+': + pt = _fmt(Locale->date_fmt, t, pt, ptlim, + warnp); + continue; + case '%': + break; + } + } + if (pt == ptlim) + break; + *pt++ = *format; + } + return pt; +} + +static char * +_conv(int n, const char *format, char *pt, const char *ptlim) +{ + char buf[INT_STRLEN_MAXIMUM(int) + 1]; + + sprintf(buf, format, n); + return _add(buf, pt, ptlim); +} + +static char * +_add(const char *str, char *pt, const char *ptlim) +{ + while (pt < ptlim && (*pt = *str++) != '\0') + ++pt; + return pt; +} + +/* +** POSIX and the C Standard are unclear or inconsistent about +** what %C and %y do if the year is negative or exceeds 9999. +** Use the convention that %C concatenated with %y yields the +** same output as %Y, and that %Y contains at least 4 bytes, +** with more only if necessary. +*/ + +static char * +_yconv(int a, int b, bool convert_top, bool convert_yy, + char *pt, const char *ptlim) +{ + register int lead; + register int trail; + + int DIVISOR = 100; + trail = a % DIVISOR + b % DIVISOR; + lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR; + trail %= DIVISOR; + if (trail < 0 && lead > 0) { + trail += DIVISOR; + --lead; + } else if (lead < 0 && trail > 0) { + trail -= DIVISOR; + ++lead; + } + if (convert_top) { + if (lead == 0 && trail < 0) + pt = _add("-0", pt, ptlim); + else pt = _conv(lead, "%02d", pt, ptlim); + } + if (convert_yy) + pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim); + return pt; +} \ No newline at end of file diff --git a/source/os/test/osTimeTests.cpp b/source/os/test/osTimeTests.cpp index 42837cc537..501502f7e6 100644 --- a/source/os/test/osTimeTests.cpp +++ b/source/os/test/osTimeTests.cpp @@ -29,30 +29,11 @@ #include "os.h" #include "tlog.h" -TEST(osTimeTests, taosLocalTimeNolock) { - time_t currentTime; - // Test when result is not NULL - struct tm expectedTime; - struct tm* result = taosLocalTimeNolock(&expectedTime, ¤tTime, 1); - if (result) { - EXPECT_EQ(expectedTime.tm_year, result->tm_year); - EXPECT_EQ(expectedTime.tm_mon, result->tm_mon); - EXPECT_EQ(expectedTime.tm_mday, result->tm_mday); - EXPECT_EQ(expectedTime.tm_hour, result->tm_hour); - EXPECT_EQ(expectedTime.tm_min, result->tm_min); - EXPECT_EQ(expectedTime.tm_sec, result->tm_sec); - EXPECT_EQ(expectedTime.tm_wday, result->tm_wday); - EXPECT_EQ(expectedTime.tm_yday, result->tm_yday); - EXPECT_EQ(expectedTime.tm_isdst, result->tm_isdst); - } -} - - TEST(osTimeTests, taosLocalTime) { // Test 1: Test when both timep and result are not NULL time_t timep = 1617531000; // 2021-04-04 18:10:00 struct tm result; - struct tm* local_time = taosLocalTime(&timep, &result, NULL, 0); + struct tm* local_time = taosLocalTime(&timep, &result, NULL, 0, NULL); ASSERT_NE(local_time, nullptr); ASSERT_EQ(local_time->tm_year, 121); ASSERT_EQ(local_time->tm_mon, 3); @@ -62,7 +43,7 @@ TEST(osTimeTests, taosLocalTime) { ASSERT_EQ(local_time->tm_sec, 00); // Test 2: Test when timep is NULL - local_time = taosLocalTime(NULL, &result, NULL, 0); + local_time = taosLocalTime(NULL, &result, NULL, 0, NULL); ASSERT_EQ(local_time, nullptr); // Test 4: Test when timep is negative on Windows diff --git a/source/util/src/tconfig.c b/source/util/src/tconfig.c index 96056e3678..a4fc223c90 100644 --- a/source/util/src/tconfig.c +++ b/source/util/src/tconfig.c @@ -254,14 +254,14 @@ static int32_t cfgSetTimezone(SConfigItem *pItem, const char *value, ECfgSrcType uError("invalid timezone:%s", value); TAOS_RETURN(TSDB_CODE_INVALID_TIMEZONE); } - TAOS_CHECK_RETURN(osSetTimezone(value)); - - TAOS_CHECK_RETURN(doSetConf(pItem, value, stype)); if (strlen(value) == 0) { uError("cfg:%s, type:%s src:%s, value:%s, skip to set timezone", pItem->name, cfgDtypeStr(pItem->dtype), cfgStypeStr(stype), value); TAOS_RETURN(TSDB_CODE_SUCCESS); } + TAOS_CHECK_RETURN(osSetTimezone(value)); + + TAOS_CHECK_RETURN(doSetConf(pItem, tsTimezoneStr, stype)); TAOS_RETURN(TSDB_CODE_SUCCESS); } @@ -698,11 +698,19 @@ int32_t cfgDumpItemValue(SConfigItem *pItem, char *buf, int32_t bufSize, int32_t case CFG_DTYPE_DOUBLE: len = tsnprintf(buf, bufSize, "%f", pItem->fval); break; + case CFG_DTYPE_TIMEZONE:{ +// char str1[TD_TIMEZONE_LEN] = {0}; +// time_t tx1 = taosGetTimestampSec(); +// if (taosFormatTimezoneStr(tx1, buf, NULL, str1) != 0) { +// tstrncpy(str1, "tz error", sizeof(str1)); +// } +// len = tsnprintf(buf, bufSize, "%s", str1); +// break; + } case CFG_DTYPE_STRING: case CFG_DTYPE_DIR: case CFG_DTYPE_LOCALE: case CFG_DTYPE_CHARSET: - case CFG_DTYPE_TIMEZONE: case CFG_DTYPE_NONE: len = tsnprintf(buf, bufSize, "%s", pItem->str); break; diff --git a/source/util/src/tlog.c b/source/util/src/tlog.c index 76d0139521..9e6d946637 100644 --- a/source/util/src/tlog.c +++ b/source/util/src/tlog.c @@ -20,6 +20,7 @@ #include "tglobal.h" #include "tjson.h" #include "tutil.h" +#include "ttime.h" #define LOG_MAX_LINE_SIZE (10024) #define LOG_MAX_LINE_BUFFER_SIZE (LOG_MAX_LINE_SIZE + 3) @@ -157,24 +158,11 @@ static int32_t taosStartLog() { static void getDay(char *buf, int32_t bufSize) { time_t t = taosTime(NULL); struct tm tmInfo; - if (taosLocalTime(&t, &tmInfo, buf, bufSize) != NULL) { + if (taosLocalTime(&t, &tmInfo, buf, bufSize, NULL) != NULL) { TAOS_UNUSED(strftime(buf, bufSize, "%Y-%m-%d", &tmInfo)); } } -static int64_t getTimestampToday() { - time_t t = taosTime(NULL); - struct tm tm; - if (taosLocalTime(&t, &tm, NULL, 0) == NULL) { - return 0; - } - tm.tm_hour = 0; - tm.tm_min = 0; - tm.tm_sec = 0; - - return (int64_t)taosMktime(&tm); -} - static void getFullPathName(char *fullName, const char *logName) { if (strlen(tsLogDir) != 0) { char lastC = tsLogDir[strlen(tsLogDir) - 1]; @@ -206,7 +194,7 @@ int32_t taosInitSlowLog() { getDay(day, sizeof(day)); (void)snprintf(name, PATH_MAX + TD_TIME_STR_LEN, "%s.%s", tsLogObj.slowLogName, day); - tsLogObj.timestampToday = getTimestampToday(); + tsLogObj.timestampToday = taosGetTimestampToday(TSDB_TIME_PRECISION_SECONDS, NULL); tsLogObj.slowHandle = taosLogBuffNew(LOG_SLOW_BUF_SIZE); if (tsLogObj.slowHandle == NULL) return terrno; @@ -448,7 +436,7 @@ static void taosOpenNewSlowLogFile() { TdFilePtr pOldFile = tsLogObj.slowHandle->pFile; tsLogObj.slowHandle->pFile = pFile; (void)taosCloseFile(&pOldFile); - tsLogObj.timestampToday = getTimestampToday(); + tsLogObj.timestampToday = taosGetTimestampToday(TSDB_TIME_PRECISION_SECONDS, NULL); (void)taosThreadMutexUnlock(&tsLogObj.logMutex); } @@ -634,8 +622,11 @@ static inline int32_t taosBuildLogHead(char *buffer, const char *flags) { TAOS_UNUSED(taosGetTimeOfDay(&timeSecs)); time_t curTime = timeSecs.tv_sec; - ptm = taosLocalTime(&curTime, &Tm, NULL, 0); - + ptm = taosLocalTime(&curTime, &Tm, NULL, 0, NULL); + if (ptm == NULL){ + uError("%s failed to get local time, code:%d", __FUNCTION__ , errno); + return 0; + } return sprintf(buffer, "%02d/%02d %02d:%02d:%02d.%06d %08" PRId64 " %s %s", ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec, (int32_t)timeSecs.tv_usec, taosGetSelfPthreadId(), LOG_EDITION_FLG, flags); diff --git a/tests/system-test/2-query/timezone.py b/tests/system-test/2-query/timezone.py index 33bcb16595..4b1509a2b5 100644 --- a/tests/system-test/2-query/timezone.py +++ b/tests/system-test/2-query/timezone.py @@ -159,9 +159,44 @@ class TDTestCase: tdSql.execute(f'drop database {self.dbname}') + # def timezone_check(self, cursor, timezone): + # cursor.execute("show local variables") + # res = cursor.fetchall() + # for i in range(cursor.rowcount): + # if res[i][0] == "timezone" : + # if res[i][1].find(timezone) == -1: + # tdLog.exit("show timezone:%s != %s"%(res[i][1],timezone)) + # + # def timezone_check_set_options(self): + # buildPath = tdCom.getBuildPath() + # cmdStr = '%s/build/bin/timezone_test'%(buildPath) + # print("cmdStr:", cmdStr) + # tdLog.info(cmdStr) + # ret = os.system(cmdStr) + # if ret != 0: + # tdLog.exit("timezone_test error") + # + # def timezone_check_conf(self, timezone): + # updateCfgDict = ({"clientCfg" : {'timezone': 'UTC'} },) + # tdDnodes.sim.deploy(updateCfgDict) + # conn = taos.connect(config=tdDnodes.getSimCfgPath()) + # cursor = conn.cursor() + # self.timezone_check(cursor, 'UTC') + # cursor.close() + def timezone_check(self, sql, timezone): + tdSql.query(sql) + rows = tdSql.getRows() + for i in range(rows): + if tdSql.getData(i, 0) == "timezone" : + if tdSql.getData(i, 1).find(timezone) == -1: + tdLog.exit("show timezone:%s != %s"%(tdSql.getData(i, 1),timezone)) def run(self): # sourcery skip: extract-duplicate-method - timezone = self.get_system_timezone() + # timezone = self.get_system_timezone() + timezone = "Asia/Shanghai" + self.timezone_check("show local variables", timezone) + self.timezone_check("show dnode 1 variables", timezone) + self.timezone_check_ntb(timezone) self.timezone_check_stb(timezone) self.timezone_format_test() diff --git a/tests/system-test/2-query/timezone_conf.py b/tests/system-test/2-query/timezone_conf.py new file mode 100644 index 0000000000..2a54114a6b --- /dev/null +++ b/tests/system-test/2-query/timezone_conf.py @@ -0,0 +1,34 @@ + +from util.log import * +from util.sql import * +from util.cases import * +from util.sqlset import * +from util.cluster import * + +class TDTestCase: + updateCfgDict = {"clientCfg" : {'timezone': 'UTC'} , 'timezone': 'UTC-8'} + + def init(self, conn, logSql, replicaVar=1): + self.replicaVar = int(replicaVar) + tdLog.debug(f"start to excute {__file__}") + tdSql.init(conn.cursor()) + + def timezone_check(self, timezone, sql): + tdSql.execute(sql) + rows = tdSql.getRows() + for i in range(rows): + if tdSql.getData(i, 0) == "timezone" : + if tdSql.getData(i, 1).find(timezone) == -1: + tdLog.exit("show timezone:%s != %s"%(tdSql.getData(i, 1),timezone)) + + def run(self): + self.timezone_check('UTC', "show local variables") + self.timezone_check('UTC-8', "show dnode 1 variables") + + def stop(self): + tdSql.close() + tdLog.success(f"{__file__} successfully executed") + + +tdCases.addLinux(__file__, TDTestCase()) +tdCases.addWindows(__file__, TDTestCase()) diff --git a/tools/shell/src/shellEngine.c b/tools/shell/src/shellEngine.c index 50a7fe8119..e8a1a38e30 100644 --- a/tools/shell/src/shellEngine.c +++ b/tools/shell/src/shellEngine.c @@ -337,7 +337,7 @@ char *shellFormatTimestamp(char *buf, int32_t bufSize, int64_t val, int32_t prec } struct tm ptm = {0}; - if (taosLocalTime(&tt, &ptm, buf, bufSize) == NULL) { + if (taosLocalTime(&tt, &ptm, buf, bufSize, NULL) == NULL) { return buf; } size_t pos = strftime(buf, 35, "%Y-%m-%d %H:%M:%S", &ptm); diff --git a/utils/test/c/CMakeLists.txt b/utils/test/c/CMakeLists.txt index 7589d11840..15a4424185 100644 --- a/utils/test/c/CMakeLists.txt +++ b/utils/test/c/CMakeLists.txt @@ -15,6 +15,7 @@ add_executable(tmq_multi_thread_test tmq_multi_thread_test.c) add_executable(tmq_offset_test tmq_offset_test.c) add_executable(varbinary_test varbinary_test.c) add_executable(replay_test replay_test.c) +add_executable(timezone_test timezone_test.c) if(${TD_LINUX}) add_executable(tsz_test tsz_test.c) @@ -142,6 +143,14 @@ target_link_libraries( PUBLIC os ) +target_link_libraries( + timezone_test + PUBLIC taos + PUBLIC util + PUBLIC common + PUBLIC os +) + if(${TD_LINUX}) target_link_libraries( tsz_test diff --git a/utils/test/c/timezone_test.c b/utils/test/c/timezone_test.c new file mode 100644 index 0000000000..f8bedc62cc --- /dev/null +++ b/utils/test/c/timezone_test.c @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2019 TAOS Data, Inc. + * + * This program is free software: you can use, redistribute, and/or modify + * it under the terms of the GNU Affero General Public License, version 3 + * or later ("AGPL"), as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include "taos.h" +#include "types.h" +#include "tlog.h" + +char* timezone_name = "America/New_York"; +void check_timezone(const char* tzName){ + TAOS* pConn = taos_connect("localhost", "root", "taosdata", NULL, 0); + ASSERT(pConn != NULL); + TAOS_RES *pRes = taos_query(pConn, "show local variables"); + ASSERT(taos_errno(pRes) == 0); + TAOS_ROW row = NULL; + while ((row = taos_fetch_row(pRes)) != NULL) { + if (strcmp(row[0], "timezone") == 0){ + ASSERT(strstr(row[1], tzName) != NULL); + } + } + taos_free_result(pRes); + taos_close(pConn); +} + +void timezone_set_options_test(){ + taos_options(TSDB_OPTION_TIMEZONE, timezone_name); + check_timezone(timezone_name); +} + +void timezone_insert_test(){ + TAOS *taos = taos_connect("localhost", "root", "taosdata", NULL, 0); + + TAOS_RES *pRes = taos_query(taos, "drop database if exists tz_db"); + taos_free_result(pRes); + + pRes = taos_query(taos, "create database if not exists tz_db"); + taos_free_result(pRes); + + pRes = taos_query(taos, "use tz_db"); + int code = taos_errno(pRes); + taos_free_result(pRes); + ASSERT(code == 0); + + pRes = taos_query(taos, "create stable stb (ts timestamp, c1 int) tags (t1 timestamp, t2 binary(32))"); + taos_free_result(pRes); + + pRes = taos_query(taos, "create table ntb (ts timestamp, c1 int)"); + taos_free_result(pRes); + + pRes = taos_query(taos, "insert into tb1 using stb tags (1, 'test') values (1, 2)"); + taos_free_result(pRes); + + pRes = taos_query(taos, "insert into tb1 using stb tags ('2013-04-12T10:52:01', 'test') values ('2013-04-12T10:52:01', 2)"); + taos_free_result(pRes); + + pRes = taos_query(taos, "insert into ntb values ('2013-04-12T10:52:01', 2)"); + taos_free_result(pRes); + + pRes = taos_query(taos, "insert into ntb values (1, 2)"); + taos_free_result(pRes); + + + /* + select today(); + select cast(1 as timestamp) + '2013-04-12T10:52:01'; + tsConver.sim + "COMPACT DATABASE test START WITH '2023-03-07 14:01:23' END WITH '2023-03-08 14:01:23'" + TK_TIMEZONE + + ts > c1('2013-04-12T10:52:01') + in ('2013-04-12T10:52:01') + + offsetFromTz hash join + */ +} + +int main(int argc, char *argv[]) { + int ret = 0; + timezone_set_options_test(); + return ret; +} diff --git a/utils/test/c/tmqDemo.c b/utils/test/c/tmqDemo.c index 40ed98132c..550613ee9a 100644 --- a/utils/test/c/tmqDemo.c +++ b/utils/test/c/tmqDemo.c @@ -597,7 +597,7 @@ void printParaIntoFile() { time_t tTime = taosGetTimestampSec(); struct tm tm; - taosLocalTime(&tTime, &tm, NULL, 0); + taosLocalTime(&tTime, &tm, NULL, 0, NULL); taosFprintfFile(pFile, "###################################################################\n"); taosFprintfFile(pFile, "# configDir: %s\n", configDir); diff --git a/utils/test/c/tmqSim.c b/utils/test/c/tmqSim.c index c045629d1f..bb0d813ace 100644 --- a/utils/test/c/tmqSim.c +++ b/utils/test/c/tmqSim.c @@ -166,7 +166,7 @@ static void printHelp() { char* getCurrentTimeString(char* timeString) { time_t tTime = taosGetTimestampSec(); struct tm tm; - taosLocalTime(&tTime, &tm, NULL, 0); + taosLocalTime(&tTime, &tm, NULL, 0, NULL); sprintf(timeString, "%d-%02d-%02d %02d:%02d:%02d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); @@ -472,7 +472,7 @@ static char* shellFormatTimestamp(char* buf, int32_t bufSize, int64_t val, int32 } struct tm ptm; - if (taosLocalTime(&tt, &ptm, buf, bufSize) == NULL) { + if (taosLocalTime(&tt, &ptm, buf, bufSize, NULL) == NULL) { return buf; } size_t pos = strftime(buf, 35, "%Y-%m-%d %H:%M:%S", &ptm); diff --git a/utils/tsim/src/simExe.c b/utils/tsim/src/simExe.c index c7cec64dec..2d269bdea2 100644 --- a/utils/tsim/src/simExe.c +++ b/utils/tsim/src/simExe.c @@ -797,7 +797,7 @@ bool simExecuteNativeSqlCommand(SScript *script, char *rest, bool isSlow) { tt = (*(int64_t *)row[i]) / 1000000000; } - if (taosLocalTime(&tt, &tp, timeStr, sizeof(timeStr)) == NULL) { + if (taosLocalTime(&tt, &tp, timeStr, sizeof(timeStr), NULL) == NULL) { break; } strftime(timeStr, 64, "%y-%m-%d %H:%M:%S", &tp); From c7d0f69ca48691f3073130431c6feca19c36df02 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Mon, 25 Nov 2024 23:43:37 +0800 Subject: [PATCH 05/45] feat:[TD-32642] add timezone logic --- cmake/cmake.define | 8 +- contrib/CMakeLists.txt | 7 +- include/os/osTimezone.h | 1151 +------- source/client/test/clientTests.cpp | 32 +- source/libs/executor/src/hashjoinoperator.c | 2 +- source/libs/parser/src/parAstCreater.c | 1 + source/os/CMakeLists.txt | 3 +- source/os/src/osTimezone.c | 2890 ------------------- source/os/src/timezone/localtime.c | 2498 ++++++++++++++++ source/os/src/timezone/private.h | 1129 ++++++++ source/os/src/timezone/strftime.c | 662 +++++ source/os/src/timezone/tzdir.h | 6 + source/os/src/timezone/tzfile.h | 121 + utils/test/c/timezone_test.c | 1 - 14 files changed, 4466 insertions(+), 4045 deletions(-) create mode 100644 source/os/src/timezone/localtime.c create mode 100644 source/os/src/timezone/private.h create mode 100644 source/os/src/timezone/strftime.c create mode 100644 source/os/src/timezone/tzdir.h create mode 100644 source/os/src/timezone/tzfile.h diff --git a/cmake/cmake.define b/cmake/cmake.define index 1b277a1f3a..09a64526a4 100644 --- a/cmake/cmake.define +++ b/cmake/cmake.define @@ -6,14 +6,12 @@ set(TD_BUILD_KEEPER_INTERNAL FALSE) # set output directory SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/build/lib) SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/build/bin) -SET(SHARE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/build/share) SET(TD_TESTS_OUTPUT_DIR ${PROJECT_BINARY_DIR}/test) MESSAGE(STATUS "Project source directory: " ${PROJECT_SOURCE_DIR}) MESSAGE(STATUS "Project binary files output path: " ${PROJECT_BINARY_DIR}) MESSAGE(STATUS "Project executable files output path: " ${EXECUTABLE_OUTPUT_PATH}) MESSAGE(STATUS "Project library files output path: " ${LIBRARY_OUTPUT_PATH}) -MESSAGE(STATUS "Project share files output path: " ${SHARE_OUTPUT_PATH}) IF(NOT DEFINED TD_GRANT) SET(TD_GRANT FALSE) @@ -105,6 +103,12 @@ ELSE() SET(TAOS_LIB taos) ENDIF() +IF(NOT DEFINED TZ_OUTPUT_PATH) + SET(TZ_OUTPUT_PATH ${PROJECT_BINARY_DIR}/build/share/timezone) +ENDIF() +MESSAGE(STATUS "timezone output path: " ${TZ_OUTPUT_PATH}) + + # build TSZ by default IF("${TSZ_ENABLED}" MATCHES "false") set(VAR_TSZ "" CACHE INTERNAL "global variant empty") diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index 1380f036da..174077b634 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -635,10 +635,15 @@ if(${TD_LINUX} AND ${BUILD_WITH_S3}) endif() execute_process( - COMMAND make TZDIR=${SHARE_OUTPUT_PATH}/timezone clean all posix_only + COMMAND make TZDIR=${TZ_OUTPUT_PATH}/ clean all posix_only WORKING_DIRECTORY "${TD_CONTRIB_DIR}/tz" ) +set(TZ_SRC_DIR "${TD_SOURCE_DIR}/source/os/src/timezone") +file(MAKE_DIRECTORY ${TZ_SRC_DIR}) +file(COPY ${TD_CONTRIB_DIR}/tz/private.h ${TD_CONTRIB_DIR}/tz/tzdir.h ${TD_CONTRIB_DIR}/tz/tzfile.h + ${TD_CONTRIB_DIR}/tz/localtime.c ${TD_CONTRIB_DIR}/tz/strftime.c + DESTINATION ${TZ_SRC_DIR}) # ================================================================================================ # Build test # ================================================================================================ diff --git a/include/os/osTimezone.h b/include/os/osTimezone.h index 52f7f661af..2e3d27212f 100644 --- a/include/os/osTimezone.h +++ b/include/os/osTimezone.h @@ -20,1149 +20,6 @@ extern "C" { #endif -/* PORT_TO_C89 means the code should work even if the underlying - compiler and library support only C89 plus C99's 'long long' - and perhaps a few other extensions to C89. - - This macro is obsolescent, and the plan is to remove it along with - associated code. A good time to do that might be in the year 2029 - because RHEL 7 (whose GCC defaults to C89) extended life cycle - support (ELS) is scheduled to end on 2028-06-30. */ -#ifndef PORT_TO_C89 -# define PORT_TO_C89 0 -#endif - -/* SUPPORT_C89 means the tzcode library should support C89 callers - in addition to the usual support for C99-and-later callers. - This defaults to 1 as POSIX requires, even though that can trigger - latent bugs in callers. */ -#ifndef SUPPORT_C89 -# define SUPPORT_C89 1 -#endif - -#ifndef __STDC_VERSION__ -# define __STDC_VERSION__ 0 -#endif - -/* Define true, false and bool if they don't work out of the box. */ -#if PORT_TO_C89 && __STDC_VERSION__ < 199901 -# define true 1 -# define false 0 -# define bool int -#elif __STDC_VERSION__ < 202311 -# include -#endif - -#if __STDC_VERSION__ < 202311 -# undef static_assert_tz -# define static_assert_tz(cond) extern int static_assert_check[(cond) ? 1 : -1] -#endif - -/* -** zdump has been made independent of the rest of the time -** conversion package to increase confidence in the verification it provides. -** You can use zdump to help in verifying other implementations. -** To do this, compile with -DUSE_LTZ=0 and link without the tz library. -*/ -#ifndef USE_LTZ -# define USE_LTZ 1 -#endif - -/* This string was in the Factory zone through version 2016f. */ -#define GRANDPARENTED "Local time zone must be set--see zic manual page" - -/* -** Defaults for preprocessor symbols. -** You can override these in your C compiler options, e.g. '-DHAVE_GETTEXT=1'. -*/ - -#if !defined HAVE__GENERIC && defined __has_extension -# if !__has_extension(c_generic_selections) -# define HAVE__GENERIC 0 -# endif -#endif -/* _Generic is buggy in pre-4.9 GCC. */ -#if !defined HAVE__GENERIC && defined __GNUC__ && !defined __STRICT_ANSI__ -# define HAVE__GENERIC (4 < __GNUC__ + (9 <= __GNUC_MINOR__)) -#endif -#ifndef HAVE__GENERIC -# define HAVE__GENERIC (201112 <= __STDC_VERSION__) -#endif - -#if !defined HAVE_GETTEXT && defined __has_include -# if __has_include() -# define HAVE_GETTEXT true -# endif -#endif -#ifndef HAVE_GETTEXT -# define HAVE_GETTEXT false -#endif - -#ifndef HAVE_INCOMPATIBLE_CTIME_R -# define HAVE_INCOMPATIBLE_CTIME_R 0 -#endif - -#ifndef HAVE_LINK -# define HAVE_LINK 1 -#endif /* !defined HAVE_LINK */ - -#ifndef HAVE_MALLOC_ERRNO -# define HAVE_MALLOC_ERRNO 1 -#endif - -#ifndef HAVE_POSIX_DECLS -# define HAVE_POSIX_DECLS 1 -#endif - -#ifndef HAVE_SETENV -# define HAVE_SETENV 1 -#endif - -#ifndef HAVE_STRDUP -# define HAVE_STRDUP 1 -#endif - -#ifndef HAVE_SYMLINK -# define HAVE_SYMLINK 1 -#endif /* !defined HAVE_SYMLINK */ - -#if !defined HAVE_SYS_STAT_H && defined __has_include -# if !__has_include() -# define HAVE_SYS_STAT_H false -# endif -#endif -#ifndef HAVE_SYS_STAT_H -# define HAVE_SYS_STAT_H true -#endif - -#if !defined HAVE_UNISTD_H && defined __has_include -# if !__has_include() -# define HAVE_UNISTD_H false -# endif -#endif -#ifndef HAVE_UNISTD_H -# define HAVE_UNISTD_H true -#endif - -#ifndef NETBSD_INSPIRED -# define NETBSD_INSPIRED 1 -#endif - -#if HAVE_INCOMPATIBLE_CTIME_R -# define asctime_r _incompatible_asctime_r -# define ctime_r _incompatible_ctime_r -#endif /* HAVE_INCOMPATIBLE_CTIME_R */ - -/* Enable tm_gmtoff, tm_zone, and environ on GNUish systems. */ -//#define _GNU_SOURCE 1 -/* Fix asctime_r on Solaris 11. */ -#define _POSIX_PTHREAD_SEMANTICS 1 -/* Enable strtoimax on pre-C99 Solaris 11. */ -#define __EXTENSIONS__ 1 - -/* On GNUish systems where time_t might be 32 or 64 bits, use 64. - On these platforms _FILE_OFFSET_BITS must also be 64; otherwise - setting _TIME_BITS to 64 does not work. The code does not - otherwise rely on _FILE_OFFSET_BITS being 64, since it does not - use off_t or functions like 'stat' that depend on off_t. */ -#ifndef _FILE_OFFSET_BITS -# define _FILE_OFFSET_BITS 64 -#endif -#if !defined _TIME_BITS && _FILE_OFFSET_BITS == 64 -# define _TIME_BITS 64 -#endif - -/* -** Nested includes -*/ - -/* Avoid clashes with NetBSD by renaming NetBSD's declarations. - If defining the 'timezone' variable, avoid a clash with FreeBSD's - 'timezone' function by renaming its declaration. */ -#define localtime_rz sys_localtime_rz -#define mktime_z sys_mktime_z -#define posix2time_z sys_posix2time_z -#define time2posix_z sys_time2posix_z -#if defined USG_COMPAT && USG_COMPAT == 2 -# define timezone sys_timezone -#endif -#define timezone_t sys_timezone_t -#define tzalloc sys_tzalloc -#define tzfree sys_tzfree -#include -#undef localtime_rz -#undef mktime_z -#undef posix2time_z -#undef time2posix_z -#if defined USG_COMPAT && USG_COMPAT == 2 -# undef timezone -#endif -#undef timezone_t -#undef tzalloc -#undef tzfree - -#include -#include -#if !PORT_TO_C89 -# include -#endif -#include /* for CHAR_BIT et al. */ -#include - -#include - -#ifndef EINVAL -# define EINVAL ERANGE -#endif - -#ifndef ELOOP -# define ELOOP EINVAL -#endif -#ifndef ENAMETOOLONG -# define ENAMETOOLONG EINVAL -#endif -#ifndef ENOMEM -# define ENOMEM EINVAL -#endif -#ifndef ENOTSUP -# define ENOTSUP EINVAL -#endif -#ifndef EOVERFLOW -# define EOVERFLOW EINVAL -#endif - -#if HAVE_GETTEXT -# include -#endif /* HAVE_GETTEXT */ - -#if HAVE_UNISTD_H -# include /* for R_OK, and other POSIX goodness */ -#endif /* HAVE_UNISTD_H */ - -/* SUPPORT_POSIX2008 means the tzcode library should support - POSIX.1-2017-and-earlier callers in addition to the usual support for - POSIX.1-2024-and-later callers; however, this can be - incompatible with POSIX.1-2024-and-later callers. - This macro is obsolescent, and the plan is to remove it - along with any code needed only when it is nonzero. - A good time to do that might be in the year 2034. - This macro's name is SUPPORT_POSIX2008 because _POSIX_VERSION == 200809 - in POSIX.1-2017, a minor revision of POSIX.1-2008. */ -#ifndef SUPPORT_POSIX2008 -# if defined _POSIX_VERSION && _POSIX_VERSION <= 200809 -# define SUPPORT_POSIX2008 1 -# else -# define SUPPORT_POSIX2008 0 -# endif -#endif - -#ifndef HAVE_DECL_ASCTIME_R -# if SUPPORT_POSIX2008 -# define HAVE_DECL_ASCTIME_R 1 -# else -# define HAVE_DECL_ASCTIME_R 0 -# endif -#endif - -#ifndef HAVE_STRFTIME_L -# if _POSIX_VERSION < 200809 -# define HAVE_STRFTIME_L 0 -# else -# define HAVE_STRFTIME_L 1 -# endif -#endif - -#ifndef USG_COMPAT -# ifndef _XOPEN_VERSION -# define USG_COMPAT 0 -# else -# define USG_COMPAT 1 -# endif -#endif - -#ifndef HAVE_TZNAME -# if _POSIX_VERSION < 198808 && !USG_COMPAT -# define HAVE_TZNAME 0 -# else -# define HAVE_TZNAME 1 -# endif -#endif - -#ifndef ALTZONE -# if defined __sun || defined _M_XENIX -# define ALTZONE 1 -# else -# define ALTZONE 0 -# endif -#endif - -#ifndef R_OK -# define R_OK 4 -#endif /* !defined R_OK */ - -#if PORT_TO_C89 - -/* -** Define HAVE_STDINT_H's default value here, rather than at the -** start, since __GLIBC__ and INTMAX_MAX's values depend on -** previously included files. glibc 2.1 and Solaris 10 and later have -** stdint.h, even with pre-C99 compilers. -*/ -#if !defined HAVE_STDINT_H && defined __has_include -# define HAVE_STDINT_H true /* C23 __has_include implies C99 stdint.h. */ -#endif -#ifndef HAVE_STDINT_H -# define HAVE_STDINT_H \ - (199901 <= __STDC_VERSION__ \ - || 2 < __GLIBC__ + (1 <= __GLIBC_MINOR__) \ - || __CYGWIN__ || INTMAX_MAX) -#endif /* !defined HAVE_STDINT_H */ - -#if HAVE_STDINT_H -# include -#endif /* !HAVE_STDINT_H */ - -#ifndef HAVE_INTTYPES_H -# define HAVE_INTTYPES_H HAVE_STDINT_H -#endif -#if HAVE_INTTYPES_H -# include -#endif - -/* Pre-C99 GCC compilers define __LONG_LONG_MAX__ instead of LLONG_MAX. */ -#if defined __LONG_LONG_MAX__ && !defined __STRICT_ANSI__ -# ifndef LLONG_MAX -# define LLONG_MAX __LONG_LONG_MAX__ -# endif -# ifndef LLONG_MIN -# define LLONG_MIN (-1 - LLONG_MAX) -# endif -# ifndef ULLONG_MAX -# define ULLONG_MAX (LLONG_MAX * 2ull + 1) -# endif -#endif - -#ifndef INT_FAST64_MAX -# if 1 <= LONG_MAX >> 31 >> 31 -typedef long int_fast64_t; -# define INT_FAST64_MIN LONG_MIN -# define INT_FAST64_MAX LONG_MAX -# else -/* If this fails, compile with -DHAVE_STDINT_H or with a better compiler. */ -typedef long long int_fast64_t; -# define INT_FAST64_MIN LLONG_MIN -# define INT_FAST64_MAX LLONG_MAX -# endif -#endif - -#ifndef PRIdFAST64 -# if INT_FAST64_MAX == LONG_MAX -# define PRIdFAST64 "ld" -# else -# define PRIdFAST64 "lld" -# endif -#endif - -#ifndef SCNdFAST64 -# define SCNdFAST64 PRIdFAST64 -#endif - -#ifndef INT_FAST32_MAX -# if INT_MAX >> 31 == 0 -typedef long int_fast32_t; -# define INT_FAST32_MAX LONG_MAX -# define INT_FAST32_MIN LONG_MIN -# else -typedef int int_fast32_t; -# define INT_FAST32_MAX INT_MAX -# define INT_FAST32_MIN INT_MIN -# endif -#endif - -#ifndef INTMAX_MAX -# ifdef LLONG_MAX -typedef long long intmax_t; -# ifndef HAVE_STRTOLL -# define HAVE_STRTOLL true -# endif -# if HAVE_STRTOLL -# define strtoimax strtoll -# endif -# define INTMAX_MAX LLONG_MAX -# define INTMAX_MIN LLONG_MIN -# else -typedef long intmax_t; -# define INTMAX_MAX LONG_MAX -# define INTMAX_MIN LONG_MIN -# endif -# ifndef strtoimax -# define strtoimax strtol -# endif -#endif - -#ifndef PRIdMAX -# if INTMAX_MAX == LLONG_MAX -# define PRIdMAX "lld" -# else -# define PRIdMAX "ld" -# endif -#endif - -#ifndef PTRDIFF_MAX -# define PTRDIFF_MAX MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t)) -#endif - -#ifndef UINT_FAST32_MAX -typedef unsigned long uint_fast32_t; -#endif - -#ifndef UINT_FAST64_MAX -# if 3 <= ULONG_MAX >> 31 >> 31 -typedef unsigned long uint_fast64_t; -# define UINT_FAST64_MAX ULONG_MAX -# else -/* If this fails, compile with -DHAVE_STDINT_H or with a better compiler. */ -typedef unsigned long long uint_fast64_t; -# define UINT_FAST64_MAX ULLONG_MAX -# endif -#endif - -#ifndef UINTMAX_MAX -# ifdef ULLONG_MAX -typedef unsigned long long uintmax_t; -# define UINTMAX_MAX ULLONG_MAX -# else -typedef unsigned long uintmax_t; -# define UINTMAX_MAX ULONG_MAX -# endif -#endif - -#ifndef PRIuMAX -# ifdef ULLONG_MAX -# define PRIuMAX "llu" -# else -# define PRIuMAX "lu" -# endif -#endif - -#ifndef SIZE_MAX -# define SIZE_MAX ((size_t) -1) -#endif - -#endif /* PORT_TO_C89 */ - -/* The maximum size of any created object, as a signed integer. - Although the C standard does not outright prohibit larger objects, - behavior is undefined if the result of pointer subtraction does not - fit into ptrdiff_t, and the code assumes in several places that - pointer subtraction works. As a practical matter it's OK to not - support objects larger than this. */ -#define INDEX_MAX ((ptrdiff_t) min_tz(PTRDIFF_MAX, SIZE_MAX)) - -/* Support ckd_add, ckd_sub, ckd_mul on C23 or recent-enough GCC-like - hosts, unless compiled with -DHAVE_STDCKDINT_H=0 or with pre-C23 EDG. */ -#if !defined HAVE_STDCKDINT_H && defined __has_include -# if __has_include() -# define HAVE_STDCKDINT_H true -# endif -#endif -#ifdef HAVE_STDCKDINT_H -# if HAVE_STDCKDINT_H -# include -# endif -#elif defined __EDG__ -/* Do nothing, to work around EDG bug . */ -#elif defined __has_builtin -# if __has_builtin(__builtin_add_overflow) -# define ckd_add(r, a, b) __builtin_add_overflow(a, b, r) -# endif -# if __has_builtin(__builtin_sub_overflow) -# define ckd_sub(r, a, b) __builtin_sub_overflow(a, b, r) -# endif -# if __has_builtin(__builtin_mul_overflow) -# define ckd_mul(r, a, b) __builtin_mul_overflow(a, b, r) -# endif -#elif 7 <= __GNUC__ -# define ckd_add(r, a, b) __builtin_add_overflow(a, b, r) -# define ckd_sub(r, a, b) __builtin_sub_overflow(a, b, r) -# define ckd_mul(r, a, b) __builtin_mul_overflow(a, b, r) -#endif - -#if (defined __has_c_attribute \ - && (202311 <= __STDC_VERSION__ || !defined __STRICT_ANSI__)) -# define HAVE___HAS_C_ATTRIBUTE true -#else -# define HAVE___HAS_C_ATTRIBUTE false -#endif - -#if HAVE___HAS_C_ATTRIBUTE -# if __has_c_attribute(deprecated) -# define ATTRIBUTE_DEPRECATED [[deprecated]] -# endif -#endif -#ifndef ATTRIBUTE_DEPRECATED -# if 3 < __GNUC__ + (2 <= __GNUC_MINOR__) -# define ATTRIBUTE_DEPRECATED __attribute__((deprecated)) -# else -# define ATTRIBUTE_DEPRECATED /* empty */ -# endif -#endif - -#if HAVE___HAS_C_ATTRIBUTE -# if __has_c_attribute(fallthrough) -# define ATTRIBUTE_FALLTHROUGH [[fallthrough]] -# endif -#endif -#ifndef ATTRIBUTE_FALLTHROUGH -# if 7 <= __GNUC__ -# define ATTRIBUTE_FALLTHROUGH __attribute__((fallthrough)) -# else -# define ATTRIBUTE_FALLTHROUGH ((void) 0) -# endif -#endif - -#if HAVE___HAS_C_ATTRIBUTE -# if __has_c_attribute(maybe_unused) -# define ATTRIBUTE_MAYBE_UNUSED [[maybe_unused]] -# endif -#endif -#ifndef ATTRIBUTE_MAYBE_UNUSED -# if 2 < __GNUC__ + (7 <= __GNUC_MINOR__) -# define ATTRIBUTE_MAYBE_UNUSED __attribute__((unused)) -# else -# define ATTRIBUTE_MAYBE_UNUSED /* empty */ -# endif -#endif - -#if HAVE___HAS_C_ATTRIBUTE -# if __has_c_attribute(noreturn) -# define ATTRIBUTE_NORETURN [[noreturn]] -# endif -#endif -#ifndef ATTRIBUTE_NORETURN -# if 201112 <= __STDC_VERSION__ -# define ATTRIBUTE_NORETURN _Noreturn -# elif 2 < __GNUC__ + (8 <= __GNUC_MINOR__) -# define ATTRIBUTE_NORETURN __attribute__((noreturn)) -# else -# define ATTRIBUTE_NORETURN /* empty */ -# endif -#endif - -#if HAVE___HAS_C_ATTRIBUTE -# if __has_c_attribute(reproducible) -# define ATTRIBUTE_REPRODUCIBLE [[reproducible]] -# endif -#endif -#ifndef ATTRIBUTE_REPRODUCIBLE -# define ATTRIBUTE_REPRODUCIBLE /* empty */ -#endif - -/* GCC attributes that are useful in tzcode. - __attribute__((pure)) is stricter than [[reproducible]], - so the latter is an adequate substitute in non-GCC C23 platforms. */ -#if __GNUC__ < 3 -# define ATTRIBUTE_FORMAT(spec) /* empty */ -# define ATTRIBUTE_PURE ATTRIBUTE_REPRODUCIBLE -#else -# define ATTRIBUTE_FORMAT(spec) __attribute__((format spec)) -# define ATTRIBUTE_PURE __attribute__((pure)) -#endif - -/* Avoid GCC bug 114833 . - Remove this macro and its uses when the bug is fixed in a GCC release, - because only the latest GCC matters for $(GCC_DEBUG_FLAGS). */ -#ifdef GCC_LINT -# define ATTRIBUTE_PURE_114833 ATTRIBUTE_PURE -#else -# define ATTRIBUTE_PURE_114833 /* empty */ -#endif - -#if (__STDC_VERSION__ < 199901 && !defined restrict \ - && (PORT_TO_C89 || defined _MSC_VER)) -# define restrict /* empty */ -#endif - -/* -** Workarounds for compilers/systems. -*/ - -#ifndef EPOCH_LOCAL -# define EPOCH_LOCAL 0 -#endif -#ifndef EPOCH_OFFSET -# define EPOCH_OFFSET 0 -#endif -#ifndef RESERVE_STD_EXT_IDS -# define RESERVE_STD_EXT_IDS 0 -#endif - -/* If standard C identifiers with external linkage (e.g., localtime) - are reserved and are not already being renamed anyway, rename them - as if compiling with '-Dtime_tz=time_t'. */ -#if !defined time_tz && RESERVE_STD_EXT_IDS && USE_LTZ -# define time_tz time_t -#endif - -/* -** Compile with -Dtime_tz=T to build the tz package with a private -** time_t type equivalent to T rather than the system-supplied time_t. -** This debugging feature can test unusual design decisions -** (e.g., time_t wider than 'long', or unsigned time_t) even on -** typical platforms. -*/ -#if defined time_tz || EPOCH_LOCAL || EPOCH_OFFSET != 0 -# define TZ_TIME_T 1 -#else -# define TZ_TIME_T 0 -#endif - -#if defined LOCALTIME_IMPLEMENTATION && TZ_TIME_T -static time_t sys_time(time_t *x) { return time(x); } -#endif - -#if TZ_TIME_T - -typedef time_tz tz_time_t; - -# undef asctime -# define asctime tz_asctime -# undef ctime -# define ctime tz_ctime -# undef difftime -# define difftime tz_difftime -# undef gmtime -# define gmtime tz_gmtime -# undef gmtime_r -# define gmtime_r tz_gmtime_r -# undef localtime -# define localtime tz_localtime -# undef localtime_r -# define localtime_r tz_localtime_r -# undef localtime_rz -# define localtime_rz tz_localtime_rz -# undef mktime -# define mktime tz_mktime -# undef mktime_z -# define mktime_z tz_mktime_z -# undef offtime -# define offtime tz_offtime -# undef posix2time -# define posix2time tz_posix2time -# undef posix2time_z -# define posix2time_z tz_posix2time_z -# undef strftime -# define strftime tz_strftime -# undef time -# define time tz_time -# undef time2posix -# define time2posix tz_time2posix -# undef time2posix_z -# define time2posix_z tz_time2posix_z -# undef time_t -# define time_t tz_time_t -# undef timegm -# define timegm tz_timegm -# undef timelocal -# define timelocal tz_timelocal -# undef timeoff -# define timeoff tz_timeoff -# undef tzalloc -# define tzalloc tz_tzalloc -# undef tzfree -# define tzfree tz_tzfree -# undef tzset -# define tzset tz_tzset -# if SUPPORT_POSIX2008 -# undef asctime_r -# define asctime_r tz_asctime_r -# undef ctime_r -# define ctime_r tz_ctime_r -# endif -# if HAVE_STRFTIME_L -# undef strftime_l -# define strftime_l tz_strftime_l -# endif -# if HAVE_TZNAME -# undef tzname -# define tzname tz_tzname -# endif -# if USG_COMPAT -# undef daylight -# define daylight tz_daylight -# undef timezone -# define timezone tz_timezone -# endif -# if ALTZONE -# undef altzone -# define altzone tz_altzone -# endif - -# if __STDC_VERSION__ < 202311 -# define DEPRECATED_IN_C23 /* empty */ -# else -# define DEPRECATED_IN_C23 ATTRIBUTE_DEPRECATED -# endif -DEPRECATED_IN_C23 char *asctime(struct tm const *); -DEPRECATED_IN_C23 char *ctime(time_t const *); -#if SUPPORT_POSIX2008 -char *asctime_r(struct tm const *restrict, char *restrict); -char *ctime_r(time_t const *, char *); -#endif -double difftime(time_t, time_t); -size_t strftime(char *restrict, size_t, char const *restrict, - struct tm const *restrict); -# if HAVE_STRFTIME_L -size_t strftime_l(char *restrict, size_t, char const *restrict, - struct tm const *restrict, locale_t); -# endif -struct tm *gmtime(time_t const *); -struct tm *gmtime_r(time_t const *restrict, struct tm *restrict); -struct tm *localtime(time_t const *); -struct tm *localtime_r(time_t const *restrict, struct tm *restrict); -time_t mktime(struct tm *); -time_t time(time_t *); -time_t timegm(struct tm *); -void tzset(void); -#endif - -#ifndef HAVE_DECL_TIMEGM -# if (202311 <= __STDC_VERSION__ \ - || defined __GLIBC__ || defined __tm_zone /* musl */ \ - || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \ - || (defined __APPLE__ && defined __MACH__)) -# define HAVE_DECL_TIMEGM true -# else -# define HAVE_DECL_TIMEGM false -# endif -#endif -#if !HAVE_DECL_TIMEGM && !defined timegm -time_t timegm(struct tm *); -#endif - -#if !HAVE_DECL_ASCTIME_R && !defined asctime_r && SUPPORT_POSIX2008 -extern char *asctime_r(struct tm const *restrict, char *restrict); -#endif - -#ifndef HAVE_DECL_ENVIRON -# if defined environ || defined __USE_GNU -# define HAVE_DECL_ENVIRON 1 -# else -# define HAVE_DECL_ENVIRON 0 -# endif -#endif - -#if !HAVE_DECL_ENVIRON -extern char **environ; -#endif - -#if 2 <= HAVE_TZNAME + (TZ_TIME_T || !HAVE_POSIX_DECLS) -extern char *tzname[]; -#endif -#if 2 <= USG_COMPAT + (TZ_TIME_T || !HAVE_POSIX_DECLS) -extern long timezone; -extern int daylight; -#endif -#if 2 <= ALTZONE + (TZ_TIME_T || !HAVE_POSIX_DECLS) -extern long altzone; -#endif - -/* -** The STD_INSPIRED functions are similar, but most also need -** declarations if time_tz is defined. -*/ - -#ifndef STD_INSPIRED -# define STD_INSPIRED 0 -#endif -#if STD_INSPIRED -# if TZ_TIME_T || !defined offtime -struct tm *offtime(time_t const *, long); -# endif -# if TZ_TIME_T || !defined timelocal -time_t timelocal(struct tm *); -# endif -# if TZ_TIME_T || !defined timeoff -# define EXTERN_TIMEOFF -# endif -# if TZ_TIME_T || !defined time2posix -time_t time2posix(time_t); -# endif -# if TZ_TIME_T || !defined posix2time -time_t posix2time(time_t); -# endif -#endif - -/* Infer TM_ZONE on systems where this information is known, but suppress - guessing if NO_TM_ZONE is defined. Similarly for TM_GMTOFF. */ -#if (200809 < _POSIX_VERSION \ - || defined __GLIBC__ \ - || defined __tm_zone /* musl */ \ - || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \ - || (defined __APPLE__ && defined __MACH__)) -# if !defined TM_GMTOFF && !defined NO_TM_GMTOFF -# define TM_GMTOFF tm_gmtoff -# endif -# if !defined TM_ZONE && !defined NO_TM_ZONE -# define TM_ZONE tm_zone -# endif -#endif - -/* -** Define functions that are ABI compatible with NetBSD but have -** better prototypes. NetBSD 6.1.4 defines a pointer type timezone_t -** and labors under the misconception that 'const timezone_t' is a -** pointer to a constant. This use of 'const' is ineffective, so it -** is not done here. What we call 'struct state' NetBSD calls -** 'struct __state', but this is a private name so it doesn't matter. -*/ -#if NETBSD_INSPIRED -typedef struct state *timezone_t; -struct tm *localtime_rz(timezone_t, time_t const *, - struct tm *); -time_t mktime_z(timezone_t, struct tm *); -timezone_t tzalloc(char const *); -void tzfree(timezone_t); -# if STD_INSPIRED -# if TZ_TIME_T || !defined posix2time_z -ATTRIBUTE_PURE time_t posix2time_z(timezone_t, time_t); -# endif -# if TZ_TIME_T || !defined time2posix_z -ATTRIBUTE_PURE time_t time2posix_z(timezone_t, time_t); -# endif -# endif -#endif - -/* -** Finally, some convenience items. -*/ - -#define TYPE_BIT(type) (CHAR_BIT * (ptrdiff_t) sizeof(type)) -#define TYPE_SIGNED(type) (((type) -1) < 0) -#define TWOS_COMPLEMENT(t) ((t) ~ (t) 0 < 0) - -/* Minimum and maximum of two values. Use lower case to avoid - naming clashes with standard include files. */ -#define max_tz(a, b) ((a) > (b) ? (a) : (b)) -#define min_tz(a, b) ((a) < (b) ? (a) : (b)) - -/* Max and min values of the integer type T, of which only the bottom - B bits are used, and where the highest-order used bit is considered - to be a sign bit if T is signed. */ -#define MAXVAL(t, b) \ - ((t) (((t) 1 << ((b) - 1 - TYPE_SIGNED(t))) \ - - 1 + ((t) 1 << ((b) - 1 - TYPE_SIGNED(t))))) -#define MINVAL(t, b) \ - ((t) (TYPE_SIGNED(t) ? - TWOS_COMPLEMENT(t) - MAXVAL(t, b) : 0)) - -/* The extreme time values, assuming no padding. */ -#define TIME_T_MIN_NO_PADDING MINVAL(time_t, TYPE_BIT(time_t)) -#define TIME_T_MAX_NO_PADDING MAXVAL(time_t, TYPE_BIT(time_t)) - -/* The extreme time values. These are macros, not constants, so that - any portability problems occur only when compiling .c files that use - the macros, which is safer for applications that need only zdump and zic. - This implementation assumes no padding if time_t is signed and - either the compiler lacks support for _Generic or time_t is not one - of the standard signed integer types. */ -#if HAVE__GENERIC -# define TIME_T_MIN \ - _Generic((time_t) 0, \ - signed char: SCHAR_MIN, short: SHRT_MIN, \ - int: INT_MIN, long: LONG_MIN, long long: LLONG_MIN, \ - default: TIME_T_MIN_NO_PADDING) -# define TIME_T_MAX \ - (TYPE_SIGNED(time_t) \ - ? _Generic((time_t) 0, \ - signed char: SCHAR_MAX, short: SHRT_MAX, \ - int: INT_MAX, long: LONG_MAX, long long: LLONG_MAX, \ - default: TIME_T_MAX_NO_PADDING) \ - : (time_t) -1) -enum { SIGNED_PADDING_CHECK_NEEDED - = _Generic((time_t) 0, - signed char: false, short: false, - int: false, long: false, long long: false, - default: true) }; -#else -# define TIME_T_MIN TIME_T_MIN_NO_PADDING -# define TIME_T_MAX TIME_T_MAX_NO_PADDING -enum { SIGNED_PADDING_CHECK_NEEDED = true }; -#endif -/* Try to check the padding assumptions. Although TIME_T_MAX and the - following check can both have undefined behavior on oddball - platforms due to shifts exceeding widths of signed integers, these - platforms' compilers are likely to diagnose these issues in integer - constant expressions, so it shouldn't hurt to check statically. */ -static_assert_tz(! TYPE_SIGNED(time_t) || ! SIGNED_PADDING_CHECK_NEEDED - || TIME_T_MAX >> (TYPE_BIT(time_t) - 2) == 1); - -/* -** 302 / 1000 is log10(2.0) rounded up. -** Subtract one for the sign bit if the type is signed; -** add one for integer division truncation; -** add one more for a minus sign if the type is signed. -*/ -#define INT_STRLEN_MAXIMUM(type) \ - ((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + \ - 1 + TYPE_SIGNED(type)) - -/* -** INITIALIZE(x) -*/ - -#ifdef GCC_LINT -# define INITIALIZE(x) ((x) = 0) -#else -# define INITIALIZE(x) -#endif - -/* Whether memory access must strictly follow the C standard. - If 0, it's OK to read uninitialized storage so long as the value is - not relied upon. Defining it to 0 lets mktime access parts of - struct tm that might be uninitialized, as a heuristic when the - standard doesn't say what to return and when tm_gmtoff can help - mktime likely infer a better value. */ -#ifndef UNINIT_TRAP -# define UNINIT_TRAP 0 -#endif - -/* localtime.c sometimes needs access to timeoff if it is not already public. - tz_private_timeoff should be used only by localtime.c. */ -#if (!defined EXTERN_TIMEOFF \ - && defined TM_GMTOFF && (200809 < _POSIX_VERSION || ! UNINIT_TRAP)) -# ifndef timeoff -# define timeoff tz_private_timeoff -# endif -# define EXTERN_TIMEOFF -#endif -#ifdef EXTERN_TIMEOFF -time_t timeoff(struct tm *, long); -#endif - -#ifdef DEBUG -# undef unreachable -# define unreachable() abort() -#elif !defined unreachable -# ifdef __has_builtin -# if __has_builtin(__builtin_unreachable) -# define unreachable() __builtin_unreachable() -# endif -# elif 4 < __GNUC__ + (5 <= __GNUC_MINOR__) -# define unreachable() __builtin_unreachable() -# endif -# ifndef unreachable -# define unreachable() ((void) 0) -# endif -#endif - -/* -** For the benefit of GNU folk... -** '_(MSGID)' uses the current locale's message library string for MSGID. -** The default is to use gettext if available, and use MSGID otherwise. -*/ - -#if HAVE_GETTEXT -#define _(msgid) gettext(msgid) -#else /* !HAVE_GETTEXT */ -#define _(msgid) msgid -#endif /* !HAVE_GETTEXT */ - -#if !defined TZ_DOMAIN && defined HAVE_GETTEXT -# define TZ_DOMAIN "tz" -#endif - -#if HAVE_INCOMPATIBLE_CTIME_R -#undef asctime_r -#undef ctime_r -char *asctime_r(struct tm const *restrict, char *restrict); -char *ctime_r(time_t const *, char *); -#endif /* HAVE_INCOMPATIBLE_CTIME_R */ - -/* Handy macros that are independent of tzfile implementation. */ - -enum { - SECSPERMIN = 60, - MINSPERHOUR = 60, - SECSPERHOUR = SECSPERMIN * MINSPERHOUR, - HOURSPERDAY = 24, - DAYSPERWEEK = 7, - DAYSPERNYEAR = 365, - DAYSPERLYEAR = DAYSPERNYEAR + 1, - MONSPERYEAR = 12, - YEARSPERREPEAT = 400 /* years before a Gregorian repeat */ -}; - -#define SECSPERDAY ((int_fast32_t) SECSPERHOUR * HOURSPERDAY) - -#define DAYSPERREPEAT ((int_fast32_t) 400 * 365 + 100 - 4 + 1) -#define SECSPERREPEAT ((int_fast64_t) DAYSPERREPEAT * SECSPERDAY) -#define AVGSECSPERYEAR (SECSPERREPEAT / YEARSPERREPEAT) - -/* How many years to generate (in zic.c) or search through (in localtime.c). - This is two years larger than the obvious 400, to avoid edge cases. - E.g., suppose a rule applies from 2012 on with transitions - in March and September, plus one-off transitions in November 2013, - and suppose the rule cannot be expressed as a proleptic TZ string. - If zic looked only at the last 400 years, it would set max_year=2413, - with the intent that the 400 years 2014 through 2413 will be repeated. - The last transition listed in the tzfile would be in 2413-09, - less than 400 years after the last one-off transition in 2013-11. - Two years is not overkill for localtime.c, as a one-year bump - would mishandle 2023d's America/Ciudad_Juarez for November 2422. */ -enum { years_of_observations = YEARSPERREPEAT + 2 }; - -enum { - TM_SUNDAY, - TM_MONDAY, - TM_TUESDAY, - TM_WEDNESDAY, - TM_THURSDAY, - TM_FRIDAY, - TM_SATURDAY -}; - -enum { - TM_JANUARY, - TM_FEBRUARY, - TM_MARCH, - TM_APRIL, - TM_MAY, - TM_JUNE, - TM_JULY, - TM_AUGUST, - TM_SEPTEMBER, - TM_OCTOBER, - TM_NOVEMBER, - TM_DECEMBER -}; - -enum { - TM_YEAR_BASE = 1900, - TM_WDAY_BASE = TM_MONDAY, - EPOCH_YEAR = 1970, - EPOCH_WDAY = TM_THURSDAY -}; - -#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) - -/* -** Since everything in isleap is modulo 400 (or a factor of 400), we know that -** isleap(y) == isleap(y % 400) -** and so -** isleap(a + b) == isleap((a + b) % 400) -** or -** isleap(a + b) == isleap(a % 400 + b % 400) -** This is true even if % means modulo rather than Fortran remainder -** (which is allowed by C89 but not by C99 or later). -** We use this to avoid addition overflow problems. -*/ - -#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400) - -#ifndef TZDEFRULES -# define TZDEFRULES "posixrules" -#endif /* !defined TZDEFRULES */ - - -/* See Internet RFC 8536 for more details about the following format. */ - -/* -** Each file begins with. . . -*/ - -#define TZ_MAGIC "TZif" - -struct tzhead { - char tzh_magic[4]; /* TZ_MAGIC */ - char tzh_version[1]; /* '\0' or '2'-'4' as of 2021 */ - char tzh_reserved[15]; /* reserved; must be zero */ - char tzh_ttisutcnt[4]; /* coded number of trans. time flags */ - char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */ - char tzh_leapcnt[4]; /* coded number of leap seconds */ - char tzh_timecnt[4]; /* coded number of transition times */ - char tzh_typecnt[4]; /* coded number of local time types */ - char tzh_charcnt[4]; /* coded number of abbr. chars */ -}; - -/* -** . . .followed by. . . -** -** tzh_timecnt (char [4])s coded transition times a la time(2) -** tzh_timecnt (unsigned char)s types of local time starting at above -** tzh_typecnt repetitions of -** one (char [4]) coded UT offset in seconds -** one (unsigned char) used to set tm_isdst -** one (unsigned char) that's an abbreviation list index -** tzh_charcnt (char)s '\0'-terminated zone abbreviations -** tzh_leapcnt repetitions of -** one (char [4]) coded leap second transition times -** one (char [4]) total correction after above -** tzh_ttisstdcnt (char)s indexed by type; if 1, transition -** time is standard time, if 0, -** transition time is local (wall clock) -** time; if absent, transition times are -** assumed to be local time -** tzh_ttisutcnt (char)s indexed by type; if 1, transition -** time is UT, if 0, transition time is -** local time; if absent, transition -** times are assumed to be local time. -** When this is 1, the corresponding -** std/wall indicator must also be 1. -*/ - -/* -** If tzh_version is '2' or greater, the above is followed by a second instance -** of tzhead and a second instance of the data in which each coded transition -** time uses 8 rather than 4 chars, -** then a POSIX.1-2017 proleptic TZ string for use in handling -** instants after the last transition time stored in the file -** (with nothing between the newlines if there is no POSIX.1-2017 -** representation for such instants). -** -** If tz_version is '3' or greater, the TZ string can be any POSIX.1-2024 -** proleptic TZ string, which means the above is extended as follows. -** First, the TZ string's hour offset may range from -167 -** through 167 as compared to the range 0 through 24 required -** by POSIX.1-2017 and earlier. -** Second, its DST start time may be January 1 at 00:00 and its stop -** time December 31 at 24:00 plus the difference between DST and -** standard time, indicating DST all year. -*/ - -/* -** In the current implementation, "tzset()" refuses to deal with files that -** exceed any of the limits below. -*/ - -#ifndef TZ_MAX_TIMES -/* This must be at least 242 for Europe/London with 'zic -b fat'. */ -# define TZ_MAX_TIMES 2000 -#endif /* !defined TZ_MAX_TIMES */ - -#ifndef TZ_MAX_TYPES -/* This must be at least 18 for Europe/Vilnius with 'zic -b fat'. */ -# define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */ -#endif /* !defined TZ_MAX_TYPES */ - -#ifndef TZ_MAX_CHARS -/* This must be at least 40 for America/Anchorage. */ -# define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */ -/* (limited by what unsigned chars can hold) */ -#endif /* !defined TZ_MAX_CHARS */ - -#ifndef TZ_MAX_LEAPS -/* This must be at least 27 for leap seconds from 1972 through mid-2023. - There's a plan to discontinue leap seconds by 2035. */ -# define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */ -#endif /* !defined TZ_MAX_LEAPS */ - -#ifndef TZDEFAULT -# define TZDEFAULT "/etc/localtime" /* default zone */ -#endif -#ifndef TZDIR -# define TZDIR "/Users/mingmingwanng/source_code/TDengine/debug/build/share/timezone" /* TZif directory */ -#endif - enum TdTimezone { TdWestZone12 = -12, TdWestZone11, @@ -1191,9 +48,13 @@ enum TdTimezone { TdEastZone12 }; -void getTimezoneStr(char *tz); - +typedef struct state *timezone_t; +struct tm *localtime_rz(timezone_t , time_t const *, struct tm *); +time_t mktime_z(timezone_t, struct tm *); +timezone_t tzalloc(char const *); +void tzfree(timezone_t); +void getTimezoneStr(char *tz); int32_t taosGetSystemTimezone(char *outTimezone); int32_t taosSetGlobalTimezone(const char *tz); int32_t taosFormatTimezoneStr(time_t t, const char* tzStr, timezone_t sp, char *outTimezoneStr); diff --git a/source/client/test/clientTests.cpp b/source/client/test/clientTests.cpp index 97d863b330..1e4faa3eae 100644 --- a/source/client/test/clientTests.cpp +++ b/source/client/test/clientTests.cpp @@ -1764,10 +1764,11 @@ TEST(clientCase, func_timezone_Test) { check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE(1704142800000, 1d, 0))", "2024-01-01T02:00:00.000+0200"); // 2024-01-01 23:00:00+0200 check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE(ts, 1w, 1)) from db1.ntb", "2023-12-28T00:00:00.000+0200"); // 2024-01-01 23:00:00+0200 -// int64_t now = get_sql_result(pConn, "select now(),now() + 2d"); -// int64_t locationNow = now + 2 * 3600; -// check_sql_result_partial(pConn, "select TO_ISO8601(today())", "T00:00:00.000+0200"); // 2024-01-01 23:00:00+0200 -// check_sql_result_partial(pConn, "select TO_ISO8601(now())", "+0200"); // 2024-01-01 23:00:00+0200 + // TODAY + check_sql_result_partial(pConn, "select TO_ISO8601(today())", "T00:00:00.000+0200"); + + // NOW + check_sql_result_partial(pConn, "select TO_ISO8601(now())", "+0200"); // WEEKDAY check_sql_result_integer(pConn, "select WEEKDAY('2024-01-01')", 0); @@ -1831,6 +1832,29 @@ TEST(clientCase, func_timezone_Test) { check_sql_result(pConn, "select TO_CHAR(cast(1704142800000 as timestamp), 'yyyy-mm-dd hh24:mi:ss tzh')", "2024-01-01 23:00:00 +02"); check_sql_result(pConn, "select TO_CHAR(cast(1704142800000 as timestamp), 'yyyy-mm-dd hh24:mi:ss')", "2024-01-01 23:00:00"); + // TIMEDIFF + check_sql_result_integer(pConn, "select TIMEDIFF(c1, '2024-01-01T23:00:00.001+02') from db1.ntb", -21600001); // use timezone in server UTC-8 + check_sql_result_integer(pConn, "select TIMEDIFF(c1, '2024-01-01T23:00:00.001') from db1.ntb", -1); // use timezone in server UTC-8 + check_sql_result_integer(pConn, "select TIMEDIFF('2024-01-01T23:00:00.001', '2024-01-01T13:00:00.000-08')", 1); + + // CAST + check_sql_result_integer(pConn, "select CAST(c1 as timestamp) from db1.ntb", 1704121200000); + check_sql_result_integer(pConn, "select CAST('2024-01-01T23:00:00.000+02' as timestamp)", 1704142800000); + check_sql_result_integer(pConn, "select CAST('2024-01-01T23:00:00.000' as timestamp)", 1704142800000); + + taos_close(pConn); + + // hash join + pConn = getConnWithOption("UTC+1"); + + execQuery(pConn, "drop database if exists db1"); + execQuery(pConn, "create database db1"); + execQuery(pConn, "create table db1.ntb (ts timestamp, c1 binary(32), c2 int)"); + execQuery(pConn, "create table db1.ntb1 (ts timestamp, c1 binary(32), c2 int)"); + execQuery(pConn, "insert into db1.ntb values(1703987400000, '2023-12-31 00:50:00', 1)"); // 2023-12-31 00:50:00-0100 + execQuery(pConn, "insert into db1.ntb1 values(1704070200000, '2023-12-31 23:50:00', 11)"); // 2023-12-31 23:50:00-0100 + checkRows(pConn, "select a.ts,b.ts from db1.ntb a join db1.ntb1 b on timetruncate(a.ts, 1d) = timetruncate(b.ts, 1d)", 1); + taos_close(pConn); } diff --git a/source/libs/executor/src/hashjoinoperator.c b/source/libs/executor/src/hashjoinoperator.c index 1f43a429b3..6fe3eb43dc 100644 --- a/source/libs/executor/src/hashjoinoperator.c +++ b/source/libs/executor/src/hashjoinoperator.c @@ -114,7 +114,7 @@ int32_t hJoinLaunchPrimExpr(SSDataBlock* pBlock, SHJoinTableCtx* pTable, int32_t SColumnInfoData* pPrimOut = taosArrayGet(pBlock->pDataBlock, pTable->primCtx.targetSlotId); if (0 != pCtx->timezoneUnit) { for (int32_t i = startIdx; i <= endIdx; ++i) { - ((int64_t*)pPrimOut->pData)[i] = ((int64_t*)pPrimIn->pData)[i] - (((int64_t*)pPrimIn->pData)[i] - pCtx->timezoneUnit) % pCtx->truncateUnit; + ((int64_t*)pPrimOut->pData)[i] = ((int64_t*)pPrimIn->pData)[i] - (((int64_t*)pPrimIn->pData)[i] + pCtx->timezoneUnit) % pCtx->truncateUnit; } } else { for (int32_t i = startIdx; i <= endIdx; ++i) { diff --git a/source/libs/parser/src/parAstCreater.c b/source/libs/parser/src/parAstCreater.c index cceebd627e..1cfdf1e636 100644 --- a/source/libs/parser/src/parAstCreater.c +++ b/source/libs/parser/src/parAstCreater.c @@ -1063,6 +1063,7 @@ SNode* createCastFunctionNode(SAstCreateContext* pCxt, SNode* pExpr, SDataType d } pCxt->errCode = nodesListMakeAppend(&func->pParameterList, pExpr); CHECK_PARSER_STATUS(pCxt); + func->tz = pCxt->pQueryCxt->timezone; return (SNode*)func; _err: nodesDestroyNode((SNode*)func); diff --git a/source/os/CMakeLists.txt b/source/os/CMakeLists.txt index 32609301a9..dbcf0f9080 100644 --- a/source/os/CMakeLists.txt +++ b/source/os/CMakeLists.txt @@ -1,5 +1,6 @@ aux_source_directory(src OS_SRC) -add_library(os STATIC ${OS_SRC}) +aux_source_directory(src/timezone OS_TZ) +add_library(os STATIC ${OS_SRC} ${OS_TZ}) target_include_directories( os PUBLIC "${TD_SOURCE_DIR}/include/os" diff --git a/source/os/src/osTimezone.c b/source/os/src/osTimezone.c index 83100d4327..5511043cd2 100644 --- a/source/os/src/osTimezone.c +++ b/source/os/src/osTimezone.c @@ -939,2894 +939,4 @@ int32_t taosGetSystemTimezone(char *outTimezoneStr) { time_t tx1 = taosGetTimestampSec(); return taosFormatTimezoneStr(tx1, tz, NULL, outTimezoneStr); #endif -} - -#if defined THREAD_SAFE && THREAD_SAFE -# include -static pthread_mutex_t locallock = PTHREAD_MUTEX_INITIALIZER; -static int lock(void) { return pthread_mutex_lock(&locallock); } -static void unlock(void) { pthread_mutex_unlock(&locallock); } -#else -static int lock(void) { return 0; } -static void unlock(void) { } -#endif - -#ifndef TZ_ABBR_CHAR_SET -# define TZ_ABBR_CHAR_SET \ - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 :+-._" -#endif /* !defined TZ_ABBR_CHAR_SET */ - -#ifndef TZ_ABBR_ERR_CHAR -# define TZ_ABBR_ERR_CHAR '_' -#endif /* !defined TZ_ABBR_ERR_CHAR */ - -/* -** Support non-POSIX platforms that distinguish between text and binary files. -*/ - -#ifndef O_BINARY -# define O_BINARY 0 -#endif - -#ifndef WILDABBR -/* -** Someone might make incorrect use of a time zone abbreviation: -** 1. They might reference tzname[0] before calling tzset (explicitly -** or implicitly). -** 2. They might reference tzname[1] before calling tzset (explicitly -** or implicitly). -** 3. They might reference tzname[1] after setting to a time zone -** in which Daylight Saving Time is never observed. -** 4. They might reference tzname[0] after setting to a time zone -** in which Standard Time is never observed. -** 5. They might reference tm.TM_ZONE after calling offtime. -** What's best to do in the above cases is open to debate; -** for now, we just set things up so that in any of the five cases -** WILDABBR is used. Another possibility: initialize tzname[0] to the -** string "tzname[0] used before set", and similarly for the other cases. -** And another: initialize tzname[0] to "ERA", with an explanation in the -** manual page of what this "time zone abbreviation" means (doing this so -** that tzname[0] has the "normal" length of three characters). -*/ -# define WILDABBR " " -#endif /* !defined WILDABBR */ - -static const char wildabbr[] = WILDABBR; - -static char const etc_utc[] = "Etc/UTC"; -static char const *utc = etc_utc + sizeof "Etc/" - 1; - -/* -** The DST rules to use if TZ has no rules and we can't load TZDEFRULES. -** Default to US rules as of 2017-05-07. -** POSIX does not specify the default DST rules; -** for historical reasons, US rules are a common default. -*/ -#ifndef TZDEFRULESTRING -# define TZDEFRULESTRING ",M3.2.0,M11.1.0" -#endif - -struct ttinfo { /* time type information */ - int_fast32_t tt_utoff; /* UT offset in seconds */ - bool tt_isdst; /* used to set tm_isdst */ - int tt_desigidx; /* abbreviation list index */ - bool tt_ttisstd; /* transition is std time */ - bool tt_ttisut; /* transition is UT */ -}; - -struct lsinfo { /* leap second information */ - time_t ls_trans; /* transition time */ - int_fast32_t ls_corr; /* correction to apply */ -}; - -/* This abbreviation means local time is unspecified. */ -static char const UNSPEC[] = "-00"; - -/* How many extra bytes are needed at the end of struct state's chars array. - This needs to be at least 1 for null termination in case the input - data isn't properly terminated, and it also needs to be big enough - for ttunspecified to work without crashing. */ -enum { CHARS_EXTRA = max_tz(sizeof UNSPEC, 2) - 1 }; - -/* Limit to time zone abbreviation length in proleptic TZ strings. - This is distinct from TZ_MAX_CHARS, which limits TZif file contents. */ -#ifndef TZNAME_MAXIMUM -# define TZNAME_MAXIMUM 255 -#endif - -/* A representation of the contents of a TZif file. Ideally this - would have no size limits; the following sizes should suffice for - practical use. This struct should not be too large, as instances - are put on the stack and stacks are relatively small on some platforms. - See tzfile.h for more about the sizes. */ -struct state { - int leapcnt; - int timecnt; - int typecnt; - int charcnt; - bool goback; - bool goahead; - time_t ats[TZ_MAX_TIMES]; - unsigned char types[TZ_MAX_TIMES]; - struct ttinfo ttis[TZ_MAX_TYPES]; - char chars[max_tz(max_tz(TZ_MAX_CHARS + CHARS_EXTRA, sizeof "UTC"), - 2 * (TZNAME_MAXIMUM + 1))]; - struct lsinfo lsis[TZ_MAX_LEAPS]; -}; - -enum r_type { - JULIAN_DAY, /* Jn = Julian day */ - DAY_OF_YEAR, /* n = day of year */ - MONTH_NTH_DAY_OF_WEEK /* Mm.n.d = month, week, day of week */ -}; - -struct rule { - enum r_type r_type; /* type of rule */ - int r_day; /* day number of rule */ - int r_week; /* week number of rule */ - int r_mon; /* month number of rule */ - int_fast32_t r_time; /* transition time of rule */ -}; - -static struct tm *gmtsub(struct state const *, time_t const *, int_fast32_t, - struct tm *); -static bool increment_overflow(int *, int); -static bool increment_overflow_time(time_t *, int_fast32_t); -static int_fast32_t leapcorr(struct state const *, time_t); -static bool normalize_overflow32(int_fast32_t *, int *, int); -static struct tm *timesub(time_t const *, int_fast32_t, struct state const *, - struct tm *); -static bool tzparse(char const *, struct state *, struct state const *); - -#ifdef ALL_STATE -static struct state * lclptr; -static struct state * gmtptr; -#endif /* defined ALL_STATE */ - -#ifndef ALL_STATE -static struct state lclmem; -static struct state gmtmem; -static struct state *const lclptr = &lclmem; -static struct state *const gmtptr = &gmtmem; -#endif /* State Farm */ - -#ifndef TZ_STRLEN_MAX -# define TZ_STRLEN_MAX 255 -#endif /* !defined TZ_STRLEN_MAX */ - -static char lcl_TZname[TZ_STRLEN_MAX + 1]; -static int lcl_is_set; - -/* -** Section 4.12.3 of X3.159-1989 requires that -** Except for the strftime function, these functions [asctime, -** ctime, gmtime, localtime] return values in one of two static -** objects: a broken-down time structure and an array of char. -** Thanks to Paul Eggert for noting this. -** -** Although this requirement was removed in C99 it is still present in POSIX. -** Follow the requirement if SUPPORT_C89, even though this is more likely to -** trigger latent bugs in programs. -*/ - -#if SUPPORT_C89 -static struct tm tm; -#endif - -#if 2 <= HAVE_TZNAME + TZ_TIME_T -char * tzname[2] = { - (char *) wildabbr, - (char *) wildabbr -}; -#endif -#if 2 <= USG_COMPAT + TZ_TIME_T -long timezone; -int daylight; -#endif -#if 2 <= ALTZONE + TZ_TIME_T -long altzone; -#endif - -/* Initialize *S to a value based on UTOFF, ISDST, and DESIGIDX. */ -static void -init_ttinfo(struct ttinfo *s, int_fast32_t utoff, bool isdst, int desigidx) -{ - s->tt_utoff = utoff; - s->tt_isdst = isdst; - s->tt_desigidx = desigidx; - s->tt_ttisstd = false; - s->tt_ttisut = false; -} - -/* Return true if SP's time type I does not specify local time. */ -static bool -ttunspecified(struct state const *sp, int i) -{ - char const *abbr = &sp->chars[sp->ttis[i].tt_desigidx]; - /* memcmp is likely faster than strcmp, and is safe due to CHARS_EXTRA. */ - return memcmp(abbr, UNSPEC, sizeof UNSPEC) == 0; -} - -static int_fast32_t -detzcode(const char *const codep) -{ - register int_fast32_t result; - register int i; - int_fast32_t one = 1; - int_fast32_t halfmaxval = one << (32 - 2); - int_fast32_t maxval = halfmaxval - 1 + halfmaxval; - int_fast32_t minval = -1 - maxval; - - result = codep[0] & 0x7f; - for (i = 1; i < 4; ++i) - result = (result << 8) | (codep[i] & 0xff); - - if (codep[0] & 0x80) { - /* Do two's-complement negation even on non-two's-complement machines. - If the result would be minval - 1, return minval. */ - result -= !TWOS_COMPLEMENT(int_fast32_t) && result != 0; - result += minval; - } - return result; -} - -static int_fast64_t -detzcode64(const char *const codep) -{ - register int_fast64_t result; - register int i; - int_fast64_t one = 1; - int_fast64_t halfmaxval = one << (64 - 2); - int_fast64_t maxval = halfmaxval - 1 + halfmaxval; - int_fast64_t minval = -TWOS_COMPLEMENT(int_fast64_t) - maxval; - - result = codep[0] & 0x7f; - for (i = 1; i < 8; ++i) - result = (result << 8) | (codep[i] & 0xff); - - if (codep[0] & 0x80) { - /* Do two's-complement negation even on non-two's-complement machines. - If the result would be minval - 1, return minval. */ - result -= !TWOS_COMPLEMENT(int_fast64_t) && result != 0; - result += minval; - } - return result; -} - -static void -update_tzname_etc(struct state const *sp, struct ttinfo const *ttisp) -{ -#if HAVE_TZNAME - tzname[ttisp->tt_isdst] = (char *) &sp->chars[ttisp->tt_desigidx]; -#endif -#if USG_COMPAT - if (!ttisp->tt_isdst) - timezone = - ttisp->tt_utoff; -#endif -#if ALTZONE - if (ttisp->tt_isdst) - altzone = - ttisp->tt_utoff; -#endif -} - -/* If STDDST_MASK indicates that SP's TYPE provides useful info, - update tzname, timezone, and/or altzone and return STDDST_MASK, - diminished by the provided info if it is a specified local time. - Otherwise, return STDDST_MASK. See settzname for STDDST_MASK. */ -static int -may_update_tzname_etc(int stddst_mask, struct state *sp, int type) -{ - struct ttinfo *ttisp = &sp->ttis[type]; - int this_bit = 1 << ttisp->tt_isdst; - if (stddst_mask & this_bit) { - update_tzname_etc(sp, ttisp); - if (!ttunspecified(sp, type)) - return stddst_mask & ~this_bit; - } - return stddst_mask; -} - -static void -settzname(void) -{ - register struct state * const sp = lclptr; - register int i; - - /* If STDDST_MASK & 1 we need info about a standard time. - If STDDST_MASK & 2 we need info about a daylight saving time. - When STDDST_MASK becomes zero we can stop looking. */ - int stddst_mask = 0; - -#if HAVE_TZNAME - tzname[0] = tzname[1] = (char *) (sp ? wildabbr : utc); - stddst_mask = 3; -#endif -#if USG_COMPAT - timezone = 0; - stddst_mask = 3; -#endif -#if ALTZONE - altzone = 0; - stddst_mask |= 2; -#endif - /* - ** And to get the latest time zone abbreviations into tzname. . . - */ - if (sp) { - for (i = sp->timecnt - 1; stddst_mask && 0 <= i; i--) - stddst_mask = may_update_tzname_etc(stddst_mask, sp, sp->types[i]); - for (i = sp->typecnt - 1; stddst_mask && 0 <= i; i--) - stddst_mask = may_update_tzname_etc(stddst_mask, sp, i); - } -#if USG_COMPAT - daylight = stddst_mask >> 1 ^ 1; -#endif -} - -/* Replace bogus characters in time zone abbreviations. - Return 0 on success, an errno value if a time zone abbreviation is - too long. */ -static int -scrub_abbrs(struct state *sp) -{ - int i; - - /* Reject overlong abbreviations. */ - for (i = 0; i < sp->charcnt - (TZNAME_MAXIMUM + 1); ) { - int len = strlen(&sp->chars[i]); - if (TZNAME_MAXIMUM < len) - return EOVERFLOW; - i += len + 1; - } - - /* Replace bogus characters. */ - for (i = 0; i < sp->charcnt; ++i) - if (strchr(TZ_ABBR_CHAR_SET, sp->chars[i]) == NULL) - sp->chars[i] = TZ_ABBR_ERR_CHAR; - - return 0; -} - -/* Input buffer for data read from a compiled tz file. */ -union input_buffer { - /* The first part of the buffer, interpreted as a header. */ - struct tzhead tzhead; - - /* The entire buffer. Ideally this would have no size limits; - the following should suffice for practical use. */ - char buf[2 * sizeof(struct tzhead) + 2 * sizeof(struct state) - + 4 * TZ_MAX_TIMES]; -}; - -/* TZDIR with a trailing '/' rather than a trailing '\0'. */ -static char const tzdirslash[sizeof TZDIR] = TZDIR "/"; - -/* Local storage needed for 'tzloadbody'. */ -union local_storage { - /* The results of analyzing the file's contents after it is opened. */ - struct file_analysis { - /* The input buffer. */ - union input_buffer u; - - /* A temporary state used for parsing a TZ string in the file. */ - struct state st; - } u; - - /* The name of the file to be opened. Ideally this would have no - size limits, to support arbitrarily long Zone names. - Limiting Zone names to 1024 bytes should suffice for practical use. - However, there is no need for this to be smaller than struct - file_analysis as that struct is allocated anyway, as the other - union member. */ - char fullname[max_tz(sizeof(struct file_analysis), sizeof tzdirslash + 1024)]; -}; - -/* Load tz data from the file named NAME into *SP. Read extended - format if DOEXTEND. Use *LSP for temporary storage. Return 0 on - success, an errno value on failure. */ -static int -tzloadbody(char const *name, struct state *sp, bool doextend, - union local_storage *lsp) -{ - register int i; - register int fid; - register int stored; - register ssize_t nread; - register bool doaccess; - register union input_buffer *up = &lsp->u.u; - register int tzheadsize = sizeof(struct tzhead); - - sp->goback = sp->goahead = false; - - if (! name) { - name = TZDEFAULT; - if (! name) - return EINVAL; - } - - if (name[0] == ':') - ++name; -#ifdef SUPPRESS_TZDIR - /* Do not prepend TZDIR. This is intended for specialized - applications only, due to its security implications. */ - doaccess = true; -#else - doaccess = name[0] == '/'; -#endif - if (!doaccess) { - char const *dot; - if (sizeof lsp->fullname - sizeof tzdirslash <= strlen(name)) - return ENAMETOOLONG; - - /* Create a string "TZDIR/NAME". Using sprintf here - would pull in stdio (and would fail if the - resulting string length exceeded INT_MAX!). */ - memcpy(lsp->fullname, tzdirslash, sizeof tzdirslash); - strcpy(lsp->fullname + sizeof tzdirslash, name); - - /* Set doaccess if NAME contains a ".." file name - component, as such a name could read a file outside - the TZDIR virtual subtree. */ - for (dot = name; (dot = strchr(dot, '.')); dot++) - if ((dot == name || dot[-1] == '/') && dot[1] == '.' - && (dot[2] == '/' || !dot[2])) { - doaccess = true; - break; - } - - name = lsp->fullname; - } - if (doaccess && access(name, R_OK) != 0) - return errno; - fid = open(name, O_RDONLY | O_BINARY); - if (fid < 0) - return errno; - - nread = read(fid, up->buf, sizeof up->buf); - if (nread < tzheadsize) { - int err = nread < 0 ? errno : EINVAL; - close(fid); - return err; - } - if (close(fid) < 0) - return errno; - for (stored = 4; stored <= 8; stored *= 2) { - char version = up->tzhead.tzh_version[0]; - bool skip_datablock = stored == 4 && version; - int_fast32_t datablock_size; - int_fast32_t ttisstdcnt = detzcode(up->tzhead.tzh_ttisstdcnt); - int_fast32_t ttisutcnt = detzcode(up->tzhead.tzh_ttisutcnt); - int_fast64_t prevtr = -1; - int_fast32_t prevcorr; - int_fast32_t leapcnt = detzcode(up->tzhead.tzh_leapcnt); - int_fast32_t timecnt = detzcode(up->tzhead.tzh_timecnt); - int_fast32_t typecnt = detzcode(up->tzhead.tzh_typecnt); - int_fast32_t charcnt = detzcode(up->tzhead.tzh_charcnt); - char const *p = up->buf + tzheadsize; - /* Although tzfile(5) currently requires typecnt to be nonzero, - support future formats that may allow zero typecnt - in files that have a TZ string and no transitions. */ - if (! (0 <= leapcnt && leapcnt < TZ_MAX_LEAPS - && 0 <= typecnt && typecnt < TZ_MAX_TYPES - && 0 <= timecnt && timecnt < TZ_MAX_TIMES - && 0 <= charcnt && charcnt < TZ_MAX_CHARS - && 0 <= ttisstdcnt && ttisstdcnt < TZ_MAX_TYPES - && 0 <= ttisutcnt && ttisutcnt < TZ_MAX_TYPES)) - return EINVAL; - datablock_size - = (timecnt * stored /* ats */ - + timecnt /* types */ - + typecnt * 6 /* ttinfos */ - + charcnt /* chars */ - + leapcnt * (stored + 4) /* lsinfos */ - + ttisstdcnt /* ttisstds */ - + ttisutcnt); /* ttisuts */ - if (nread < tzheadsize + datablock_size) - return EINVAL; - if (skip_datablock) - p += datablock_size; - else { - if (! ((ttisstdcnt == typecnt || ttisstdcnt == 0) - && (ttisutcnt == typecnt || ttisutcnt == 0))) - return EINVAL; - - sp->leapcnt = leapcnt; - sp->timecnt = timecnt; - sp->typecnt = typecnt; - sp->charcnt = charcnt; - - /* Read transitions, discarding those out of time_t range. - But pretend the last transition before TIME_T_MIN - occurred at TIME_T_MIN. */ - timecnt = 0; - for (i = 0; i < sp->timecnt; ++i) { - int_fast64_t at - = stored == 4 ? detzcode(p) : detzcode64(p); - sp->types[i] = at <= TIME_T_MAX; - if (sp->types[i]) { - time_t attime - = ((TYPE_SIGNED(time_t) ? at < TIME_T_MIN : at < 0) - ? TIME_T_MIN : at); - if (timecnt && attime <= sp->ats[timecnt - 1]) { - if (attime < sp->ats[timecnt - 1]) - return EINVAL; - sp->types[i - 1] = 0; - timecnt--; - } - sp->ats[timecnt++] = attime; - } - p += stored; - } - - timecnt = 0; - for (i = 0; i < sp->timecnt; ++i) { - unsigned char typ = *p++; - if (sp->typecnt <= typ) - return EINVAL; - if (sp->types[i]) - sp->types[timecnt++] = typ; - } - sp->timecnt = timecnt; - for (i = 0; i < sp->typecnt; ++i) { - register struct ttinfo * ttisp; - unsigned char isdst, desigidx; - - ttisp = &sp->ttis[i]; - ttisp->tt_utoff = detzcode(p); - p += 4; - isdst = *p++; - if (! (isdst < 2)) - return EINVAL; - ttisp->tt_isdst = isdst; - desigidx = *p++; - if (! (desigidx < sp->charcnt)) - return EINVAL; - ttisp->tt_desigidx = desigidx; - } - for (i = 0; i < sp->charcnt; ++i) - sp->chars[i] = *p++; - /* Ensure '\0'-terminated, and make it safe to call - ttunspecified later. */ - memset(&sp->chars[i], 0, CHARS_EXTRA); - - /* Read leap seconds, discarding those out of time_t range. */ - leapcnt = 0; - for (i = 0; i < sp->leapcnt; ++i) { - int_fast64_t tr = stored == 4 ? detzcode(p) : detzcode64(p); - int_fast32_t corr = detzcode(p + stored); - p += stored + 4; - - /* Leap seconds cannot occur before the Epoch, - or out of order. */ - if (tr <= prevtr) - return EINVAL; - - /* To avoid other botches in this code, each leap second's - correction must differ from the previous one's by 1 - second or less, except that the first correction can be - any value; these requirements are more generous than - RFC 8536, to allow future RFC extensions. */ - if (! (i == 0 - || (prevcorr < corr - ? corr == prevcorr + 1 - : (corr == prevcorr - || corr == prevcorr - 1)))) - return EINVAL; - prevtr = tr; - prevcorr = corr; - - if (tr <= TIME_T_MAX) { - sp->lsis[leapcnt].ls_trans = tr; - sp->lsis[leapcnt].ls_corr = corr; - leapcnt++; - } - } - sp->leapcnt = leapcnt; - - for (i = 0; i < sp->typecnt; ++i) { - register struct ttinfo * ttisp; - - ttisp = &sp->ttis[i]; - if (ttisstdcnt == 0) - ttisp->tt_ttisstd = false; - else { - if (*p != true && *p != false) - return EINVAL; - ttisp->tt_ttisstd = *p++; - } - } - for (i = 0; i < sp->typecnt; ++i) { - register struct ttinfo * ttisp; - - ttisp = &sp->ttis[i]; - if (ttisutcnt == 0) - ttisp->tt_ttisut = false; - else { - if (*p != true && *p != false) - return EINVAL; - ttisp->tt_ttisut = *p++; - } - } - } - - nread -= p - up->buf; - memmove(up->buf, p, nread); - - /* If this is an old file, we're done. */ - if (!version) - break; - } - if (doextend && nread > 2 && - up->buf[0] == '\n' && up->buf[nread - 1] == '\n' && - sp->typecnt + 2 <= TZ_MAX_TYPES) { - struct state *ts = &lsp->u.st; - - up->buf[nread - 1] = '\0'; - if (tzparse(&up->buf[1], ts, sp)) { - - /* Attempt to reuse existing abbreviations. - Without this, America/Anchorage would be right on - the edge after 2037 when TZ_MAX_CHARS is 50, as - sp->charcnt equals 40 (for LMT AST AWT APT AHST - AHDT YST AKDT AKST) and ts->charcnt equals 10 - (for AKST AKDT). Reusing means sp->charcnt can - stay 40 in this example. */ - int gotabbr = 0; - int charcnt = sp->charcnt; - for (i = 0; i < ts->typecnt; i++) { - char *tsabbr = ts->chars + ts->ttis[i].tt_desigidx; - int j; - for (j = 0; j < charcnt; j++) - if (strcmp(sp->chars + j, tsabbr) == 0) { - ts->ttis[i].tt_desigidx = j; - gotabbr++; - break; - } - if (! (j < charcnt)) { - int tsabbrlen = strlen(tsabbr); - if (j + tsabbrlen < TZ_MAX_CHARS) { - strcpy(sp->chars + j, tsabbr); - charcnt = j + tsabbrlen + 1; - ts->ttis[i].tt_desigidx = j; - gotabbr++; - } - } - } - if (gotabbr == ts->typecnt) { - sp->charcnt = charcnt; - - /* Ignore any trailing, no-op transitions generated - by zic as they don't help here and can run afoul - of bugs in zic 2016j or earlier. */ - while (1 < sp->timecnt - && (sp->types[sp->timecnt - 1] - == sp->types[sp->timecnt - 2])) - sp->timecnt--; - - sp->goahead = ts->goahead; - - for (i = 0; i < ts->timecnt; i++) { - time_t t = ts->ats[i]; - if (increment_overflow_time(&t, leapcorr(sp, t)) - || (0 < sp->timecnt - && t <= sp->ats[sp->timecnt - 1])) - continue; - if (TZ_MAX_TIMES <= sp->timecnt) { - sp->goahead = false; - break; - } - sp->ats[sp->timecnt] = t; - sp->types[sp->timecnt] = (sp->typecnt - + ts->types[i]); - sp->timecnt++; - } - for (i = 0; i < ts->typecnt; i++) - sp->ttis[sp->typecnt++] = ts->ttis[i]; - } - } - } - if (sp->typecnt == 0) - return EINVAL; - - return 0; -} - -/* Load tz data from the file named NAME into *SP. Read extended - format if DOEXTEND. Return 0 on success, an errno value on failure. */ -static int -tzload(char const *name, struct state *sp, bool doextend) -{ -#ifdef ALL_STATE - union local_storage *lsp = malloc(sizeof *lsp); - if (!lsp) { - return HAVE_MALLOC_ERRNO ? errno : ENOMEM; - } else { - int err = tzloadbody(name, sp, doextend, lsp); - free(lsp); - return err; - } -#else - union local_storage ls; - return tzloadbody(name, sp, doextend, &ls); -#endif -} - -static const int mon_lengths[2][MONSPERYEAR] = { - { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, - { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } -}; - -static const int year_lengths[2] = { - DAYSPERNYEAR, DAYSPERLYEAR -}; - -/* Is C an ASCII digit? */ -static bool -is_digit(char c) -{ - return '0' <= c && c <= '9'; -} - -/* -** Given a pointer into a timezone string, scan until a character that is not -** a valid character in a time zone abbreviation is found. -** Return a pointer to that character. -*/ - -ATTRIBUTE_PURE_114833 static const char * -getzname(register const char *strp) -{ - register char c; - - while ((c = *strp) != '\0' && !is_digit(c) && c != ',' && c != '-' && - c != '+') - ++strp; - return strp; -} - -/* -** Given a pointer into an extended timezone string, scan until the ending -** delimiter of the time zone abbreviation is located. -** Return a pointer to the delimiter. -** -** As with getzname above, the legal character set is actually quite -** restricted, with other characters producing undefined results. -** We don't do any checking here; checking is done later in common-case code. -*/ - -ATTRIBUTE_PURE_114833 static const char * -getqzname(register const char *strp, const int delim) -{ - register int c; - - while ((c = *strp) != '\0' && c != delim) - ++strp; - return strp; -} - -/* -** Given a pointer into a timezone string, extract a number from that string. -** Check that the number is within a specified range; if it is not, return -** NULL. -** Otherwise, return a pointer to the first character not part of the number. -*/ - -static const char * -getnum(register const char *strp, int *const nump, const int min, const int max) -{ - register char c; - register int num; - - if (strp == NULL || !is_digit(c = *strp)) - return NULL; - num = 0; - do { - num = num * 10 + (c - '0'); - if (num > max) - return NULL; /* illegal value */ - c = *++strp; - } while (is_digit(c)); - if (num < min) - return NULL; /* illegal value */ - *nump = num; - return strp; -} - -/* -** Given a pointer into a timezone string, extract a number of seconds, -** in hh[:mm[:ss]] form, from the string. -** If any error occurs, return NULL. -** Otherwise, return a pointer to the first character not part of the number -** of seconds. -*/ - -static const char * -getsecs(register const char *strp, int_fast32_t *const secsp) -{ - int num; - int_fast32_t secsperhour = SECSPERHOUR; - - /* - ** 'HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-POSIX rules like - ** "M10.4.6/26", which does not conform to POSIX, - ** but which specifies the equivalent of - ** "02:00 on the first Sunday on or after 23 Oct". - */ - strp = getnum(strp, &num, 0, HOURSPERDAY * DAYSPERWEEK - 1); - if (strp == NULL) - return NULL; - *secsp = num * secsperhour; - if (*strp == ':') { - ++strp; - strp = getnum(strp, &num, 0, MINSPERHOUR - 1); - if (strp == NULL) - return NULL; - *secsp += num * SECSPERMIN; - if (*strp == ':') { - ++strp; - /* 'SECSPERMIN' allows for leap seconds. */ - strp = getnum(strp, &num, 0, SECSPERMIN); - if (strp == NULL) - return NULL; - *secsp += num; - } - } - return strp; -} - -/* -** Given a pointer into a timezone string, extract an offset, in -** [+-]hh[:mm[:ss]] form, from the string. -** If any error occurs, return NULL. -** Otherwise, return a pointer to the first character not part of the time. -*/ - -static const char * -getoffset(register const char *strp, int_fast32_t *const offsetp) -{ - register bool neg = false; - - if (*strp == '-') { - neg = true; - ++strp; - } else if (*strp == '+') - ++strp; - strp = getsecs(strp, offsetp); - if (strp == NULL) - return NULL; /* illegal time */ - if (neg) - *offsetp = -*offsetp; - return strp; -} - -/* -** Given a pointer into a timezone string, extract a rule in the form -** date[/time]. See POSIX Base Definitions section 8.3 variable TZ -** for the format of "date" and "time". -** If a valid rule is not found, return NULL. -** Otherwise, return a pointer to the first character not part of the rule. -*/ - -static const char * -getrule(const char *strp, register struct rule *const rulep) -{ - if (*strp == 'J') { - /* - ** Julian day. - */ - rulep->r_type = JULIAN_DAY; - ++strp; - strp = getnum(strp, &rulep->r_day, 1, DAYSPERNYEAR); - } else if (*strp == 'M') { - /* - ** Month, week, day. - */ - rulep->r_type = MONTH_NTH_DAY_OF_WEEK; - ++strp; - strp = getnum(strp, &rulep->r_mon, 1, MONSPERYEAR); - if (strp == NULL) - return NULL; - if (*strp++ != '.') - return NULL; - strp = getnum(strp, &rulep->r_week, 1, 5); - if (strp == NULL) - return NULL; - if (*strp++ != '.') - return NULL; - strp = getnum(strp, &rulep->r_day, 0, DAYSPERWEEK - 1); - } else if (is_digit(*strp)) { - /* - ** Day of year. - */ - rulep->r_type = DAY_OF_YEAR; - strp = getnum(strp, &rulep->r_day, 0, DAYSPERLYEAR - 1); - } else return NULL; /* invalid format */ - if (strp == NULL) - return NULL; - if (*strp == '/') { - /* - ** Time specified. - */ - ++strp; - strp = getoffset(strp, &rulep->r_time); - } else rulep->r_time = 2 * SECSPERHOUR; /* default = 2:00:00 */ - return strp; -} - -/* -** Given a year, a rule, and the offset from UT at the time that rule takes -** effect, calculate the year-relative time that rule takes effect. -*/ - -static int_fast32_t -transtime(const int year, register const struct rule *const rulep, - const int_fast32_t offset) -{ - register bool leapyear; - register int_fast32_t value; - register int i; - int d, m1, yy0, yy1, yy2, dow; - - leapyear = isleap(year); - switch (rulep->r_type) { - - case JULIAN_DAY: - /* - ** Jn - Julian day, 1 == January 1, 60 == March 1 even in leap - ** years. - ** In non-leap years, or if the day number is 59 or less, just - ** add SECSPERDAY times the day number-1 to the time of - ** January 1, midnight, to get the day. - */ - value = (rulep->r_day - 1) * SECSPERDAY; - if (leapyear && rulep->r_day >= 60) - value += SECSPERDAY; - break; - - case DAY_OF_YEAR: - /* - ** n - day of year. - ** Just add SECSPERDAY times the day number to the time of - ** January 1, midnight, to get the day. - */ - value = rulep->r_day * SECSPERDAY; - break; - - case MONTH_NTH_DAY_OF_WEEK: - /* - ** Mm.n.d - nth "dth day" of month m. - */ - - /* - ** Use Zeller's Congruence to get day-of-week of first day of - ** month. - */ - m1 = (rulep->r_mon + 9) % 12 + 1; - yy0 = (rulep->r_mon <= 2) ? (year - 1) : year; - yy1 = yy0 / 100; - yy2 = yy0 % 100; - dow = ((26 * m1 - 2) / 10 + - 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7; - if (dow < 0) - dow += DAYSPERWEEK; - - /* - ** "dow" is the day-of-week of the first day of the month. Get - ** the day-of-month (zero-origin) of the first "dow" day of the - ** month. - */ - d = rulep->r_day - dow; - if (d < 0) - d += DAYSPERWEEK; - for (i = 1; i < rulep->r_week; ++i) { - if (d + DAYSPERWEEK >= - mon_lengths[leapyear][rulep->r_mon - 1]) - break; - d += DAYSPERWEEK; - } - - /* - ** "d" is the day-of-month (zero-origin) of the day we want. - */ - value = d * SECSPERDAY; - for (i = 0; i < rulep->r_mon - 1; ++i) - value += mon_lengths[leapyear][i] * SECSPERDAY; - break; - - default: unreachable(); - } - - /* - ** "value" is the year-relative time of 00:00:00 UT on the day in - ** question. To get the year-relative time of the specified local - ** time on that day, add the transition time and the current offset - ** from UT. - */ - return value + rulep->r_time + offset; -} - -/* -** Given a POSIX.1 proleptic TZ string, fill in the rule tables as -** appropriate. -*/ - -static bool -tzparse(const char *name, struct state *sp, struct state const *basep) -{ - const char * stdname; - const char * dstname; - int_fast32_t stdoffset; - int_fast32_t dstoffset; - register char * cp; - register bool load_ok; - ptrdiff_t stdlen, dstlen, charcnt; - time_t atlo = TIME_T_MIN, leaplo = TIME_T_MIN; - - stdname = name; - if (*name == '<') { - name++; - stdname = name; - name = getqzname(name, '>'); - if (*name != '>') - return false; - stdlen = name - stdname; - name++; - } else { - name = getzname(name); - stdlen = name - stdname; - } - if (! (0 < stdlen && stdlen <= TZNAME_MAXIMUM)) - return false; - name = getoffset(name, &stdoffset); - if (name == NULL) - return false; - charcnt = stdlen + 1; - if (basep) { - if (0 < basep->timecnt) - atlo = basep->ats[basep->timecnt - 1]; - load_ok = false; - sp->leapcnt = basep->leapcnt; - memcpy(sp->lsis, basep->lsis, sp->leapcnt * sizeof *sp->lsis); - } else { - load_ok = tzload(TZDEFRULES, sp, false) == 0; - if (!load_ok) - sp->leapcnt = 0; /* So, we're off a little. */ - } - if (0 < sp->leapcnt) - leaplo = sp->lsis[sp->leapcnt - 1].ls_trans; - sp->goback = sp->goahead = false; - if (*name != '\0') { - if (*name == '<') { - dstname = ++name; - name = getqzname(name, '>'); - if (*name != '>') - return false; - dstlen = name - dstname; - name++; - } else { - dstname = name; - name = getzname(name); - dstlen = name - dstname; /* length of DST abbr. */ - } - if (! (0 < dstlen && dstlen <= TZNAME_MAXIMUM)) - return false; - charcnt += dstlen + 1; - if (*name != '\0' && *name != ',' && *name != ';') { - name = getoffset(name, &dstoffset); - if (name == NULL) - return false; - } else dstoffset = stdoffset - SECSPERHOUR; - if (*name == '\0' && !load_ok) - name = TZDEFRULESTRING; - if (*name == ',' || *name == ';') { - struct rule start; - struct rule end; - register int year; - register int timecnt; - time_t janfirst; - int_fast32_t janoffset = 0; - int yearbeg, yearlim; - - ++name; - if ((name = getrule(name, &start)) == NULL) - return false; - if (*name++ != ',') - return false; - if ((name = getrule(name, &end)) == NULL) - return false; - if (*name != '\0') - return false; - sp->typecnt = 2; /* standard time and DST */ - /* - ** Two transitions per year, from EPOCH_YEAR forward. - */ - init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); - init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1); - timecnt = 0; - janfirst = 0; - yearbeg = EPOCH_YEAR; - - do { - int_fast32_t yearsecs - = year_lengths[isleap(yearbeg - 1)] * SECSPERDAY; - time_t janfirst1 = janfirst; - yearbeg--; - if (increment_overflow_time(&janfirst1, -yearsecs)) { - janoffset = -yearsecs; - break; - } - janfirst = janfirst1; - } while (atlo < janfirst - && EPOCH_YEAR - YEARSPERREPEAT / 2 < yearbeg); - - while (true) { - int_fast32_t yearsecs - = year_lengths[isleap(yearbeg)] * SECSPERDAY; - int yearbeg1 = yearbeg; - time_t janfirst1 = janfirst; - if (increment_overflow_time(&janfirst1, yearsecs) - || increment_overflow(&yearbeg1, 1) - || atlo <= janfirst1) - break; - yearbeg = yearbeg1; - janfirst = janfirst1; - } - - yearlim = yearbeg; - if (increment_overflow(&yearlim, years_of_observations)) - yearlim = INT_MAX; - for (year = yearbeg; year < yearlim; year++) { - int_fast32_t - starttime = transtime(year, &start, stdoffset), - endtime = transtime(year, &end, dstoffset); - int_fast32_t - yearsecs = (year_lengths[isleap(year)] - * SECSPERDAY); - bool reversed = endtime < starttime; - if (reversed) { - int_fast32_t swap = starttime; - starttime = endtime; - endtime = swap; - } - if (reversed - || (starttime < endtime - && endtime - starttime < yearsecs)) { - if (TZ_MAX_TIMES - 2 < timecnt) - break; - sp->ats[timecnt] = janfirst; - if (! increment_overflow_time - (&sp->ats[timecnt], - janoffset + starttime) - && atlo <= sp->ats[timecnt]) - sp->types[timecnt++] = !reversed; - sp->ats[timecnt] = janfirst; - if (! increment_overflow_time - (&sp->ats[timecnt], - janoffset + endtime) - && atlo <= sp->ats[timecnt]) { - sp->types[timecnt++] = reversed; - } - } - if (endtime < leaplo) { - yearlim = year; - if (increment_overflow(&yearlim, - years_of_observations)) - yearlim = INT_MAX; - } - if (increment_overflow_time - (&janfirst, janoffset + yearsecs)) - break; - janoffset = 0; - } - sp->timecnt = timecnt; - if (! timecnt) { - sp->ttis[0] = sp->ttis[1]; - sp->typecnt = 1; /* Perpetual DST. */ - } else if (years_of_observations <= year - yearbeg) - sp->goback = sp->goahead = true; - } else { - register int_fast32_t theirstdoffset; - register int_fast32_t theirdstoffset; - register int_fast32_t theiroffset; - register bool isdst; - register int i; - register int j; - - if (*name != '\0') - return false; - /* - ** Initial values of theirstdoffset and theirdstoffset. - */ - theirstdoffset = 0; - for (i = 0; i < sp->timecnt; ++i) { - j = sp->types[i]; - if (!sp->ttis[j].tt_isdst) { - theirstdoffset = - - sp->ttis[j].tt_utoff; - break; - } - } - theirdstoffset = 0; - for (i = 0; i < sp->timecnt; ++i) { - j = sp->types[i]; - if (sp->ttis[j].tt_isdst) { - theirdstoffset = - - sp->ttis[j].tt_utoff; - break; - } - } - /* - ** Initially we're assumed to be in standard time. - */ - isdst = false; - /* - ** Now juggle transition times and types - ** tracking offsets as you do. - */ - for (i = 0; i < sp->timecnt; ++i) { - j = sp->types[i]; - sp->types[i] = sp->ttis[j].tt_isdst; - if (sp->ttis[j].tt_ttisut) { - /* No adjustment to transition time */ - } else { - /* - ** If daylight saving time is in - ** effect, and the transition time was - ** not specified as standard time, add - ** the daylight saving time offset to - ** the transition time; otherwise, add - ** the standard time offset to the - ** transition time. - */ - /* - ** Transitions from DST to DDST - ** will effectively disappear since - ** proleptic TZ strings have only one - ** DST offset. - */ - if (isdst && !sp->ttis[j].tt_ttisstd) { - sp->ats[i] += dstoffset - - theirdstoffset; - } else { - sp->ats[i] += stdoffset - - theirstdoffset; - } - } - theiroffset = -sp->ttis[j].tt_utoff; - if (sp->ttis[j].tt_isdst) - theirdstoffset = theiroffset; - else theirstdoffset = theiroffset; - } - /* - ** Finally, fill in ttis. - */ - init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); - init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1); - sp->typecnt = 2; - } - } else { - dstlen = 0; - sp->typecnt = 1; /* only standard time */ - sp->timecnt = 0; - init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); - } - sp->charcnt = charcnt; - cp = sp->chars; - memcpy(cp, stdname, stdlen); - cp += stdlen; - *cp++ = '\0'; - if (dstlen != 0) { - memcpy(cp, dstname, dstlen); - *(cp + dstlen) = '\0'; - } - return true; -} - -static void -gmtload(struct state *const sp) -{ - if (tzload(etc_utc, sp, true) != 0) - tzparse("UTC0", sp, NULL); -} - -/* Initialize *SP to a value appropriate for the TZ setting NAME. - Return 0 on success, an errno value on failure. */ -static int -zoneinit(struct state *sp, char const *name) -{ - if (name && ! name[0]) { - /* - ** User wants it fast rather than right. - */ - sp->leapcnt = 0; /* so, we're off a little */ - sp->timecnt = 0; - sp->typecnt = 0; - sp->charcnt = 0; - sp->goback = sp->goahead = false; - init_ttinfo(&sp->ttis[0], 0, false, 0); - strcpy(sp->chars, utc); - return 0; - } else { - int err = tzload(name, sp, true); - if (err != 0 && name && name[0] != ':' && tzparse(name, sp, NULL)) - err = 0; - if (err == 0) - err = scrub_abbrs(sp); - return err; - } -} - -static void -tzset_unlocked(void) -{ - char const *name = getenv("TZ"); - struct state *sp = lclptr; - int lcl = name ? strlen(name) < sizeof lcl_TZname : -1; - if (lcl < 0 - ? lcl_is_set < 0 - : 0 < lcl_is_set && strcmp(lcl_TZname, name) == 0) - return; -#ifdef ALL_STATE - if (! sp) - lclptr = sp = malloc(sizeof *lclptr); -#endif /* defined ALL_STATE */ - if (sp) { - if (zoneinit(sp, name) != 0) - zoneinit(sp, ""); - if (0 < lcl) - strcpy(lcl_TZname, name); - } - settzname(); - lcl_is_set = lcl; -} - -void -tzset(void) -{ - if (lock() != 0) - return; - tzset_unlocked(); - unlock(); -} - -static void -gmtcheck(void) -{ - static bool gmt_is_set; - if (lock() != 0) - return; - if (! gmt_is_set) { -#ifdef ALL_STATE - gmtptr = malloc(sizeof *gmtptr); -#endif - if (gmtptr) - gmtload(gmtptr); - gmt_is_set = true; - } - unlock(); -} - -#if NETBSD_INSPIRED - -timezone_t -tzalloc(char const *name) -{ - timezone_t sp = malloc(sizeof *sp); - if (sp) { - int err = zoneinit(sp, name); - if (err != 0) { - free(sp); - errno = err; - return NULL; - } - } else if (!HAVE_MALLOC_ERRNO) - errno = ENOMEM; - return sp; -} - -void -tzfree(timezone_t sp) -{ - free(sp); -} - -/* -** NetBSD 6.1.4 has ctime_rz, but omit it because C23 deprecates ctime and -** POSIX.1-2024 removes ctime_r. Both have potential security problems that -** ctime_rz would share. Callers can instead use localtime_rz + strftime. -** -** NetBSD 6.1.4 has tzgetname, but omit it because it doesn't work -** in zones with three or more time zone abbreviations. -** Callers can instead use localtime_rz + strftime. -*/ - -#endif - -/* -** The easy way to behave "as if no library function calls" localtime -** is to not call it, so we drop its guts into "localsub", which can be -** freely called. (And no, the PANS doesn't require the above behavior, -** but it *is* desirable.) -** -** If successful and SETNAME is nonzero, -** set the applicable parts of tzname, timezone and altzone; -** however, it's OK to omit this step for proleptic TZ strings -** since in that case tzset should have already done this step correctly. -** SETNAME's type is int_fast32_t for compatibility with gmtsub, -** but it is actually a boolean and its value should be 0 or 1. -*/ - -/*ARGSUSED*/ -static struct tm * -localsub(struct state const *sp, time_t const *timep, int_fast32_t setname, - struct tm *const tmp) -{ - register const struct ttinfo * ttisp; - register int i; - register struct tm * result; - const time_t t = *timep; - - if (sp == NULL) { - /* Don't bother to set tzname etc.; tzset has already done it. */ - return gmtsub(gmtptr, timep, 0, tmp); - } - if ((sp->goback && t < sp->ats[0]) || - (sp->goahead && t > sp->ats[sp->timecnt - 1])) { - time_t newt; - register time_t seconds; - register time_t years; - - if (t < sp->ats[0]) - seconds = sp->ats[0] - t; - else seconds = t - sp->ats[sp->timecnt - 1]; - --seconds; - - /* Beware integer overflow, as SECONDS might - be close to the maximum time_t. */ - years = seconds / SECSPERREPEAT * YEARSPERREPEAT; - seconds = years * AVGSECSPERYEAR; - years += YEARSPERREPEAT; - if (t < sp->ats[0]) - newt = t + seconds + SECSPERREPEAT; - else - newt = t - seconds - SECSPERREPEAT; - - if (newt < sp->ats[0] || - newt > sp->ats[sp->timecnt - 1]) - return NULL; /* "cannot happen" */ - result = localsub(sp, &newt, setname, tmp); - if (result) { -#if defined ckd_add && defined ckd_sub - if (t < sp->ats[0] - ? ckd_sub(&result->tm_year, - result->tm_year, years) - : ckd_add(&result->tm_year, - result->tm_year, years)) - return NULL; -#else - register int_fast64_t newy; - - newy = result->tm_year; - if (t < sp->ats[0]) - newy -= years; - else newy += years; - if (! (INT_MIN <= newy && newy <= INT_MAX)) - return NULL; - result->tm_year = newy; -#endif - } - return result; - } - if (sp->timecnt == 0 || t < sp->ats[0]) { - i = 0; - } else { - register int lo = 1; - register int hi = sp->timecnt; - - while (lo < hi) { - register int mid = (lo + hi) >> 1; - - if (t < sp->ats[mid]) - hi = mid; - else lo = mid + 1; - } - i = sp->types[lo - 1]; - } - ttisp = &sp->ttis[i]; - /* - ** To get (wrong) behavior that's compatible with System V Release 2.0 - ** you'd replace the statement below with - ** t += ttisp->tt_utoff; - ** timesub(&t, 0L, sp, tmp); - */ - result = timesub(&t, ttisp->tt_utoff, sp, tmp); - if (result) { - result->tm_isdst = ttisp->tt_isdst; -#ifdef TM_ZONE - result->TM_ZONE = (char *) &sp->chars[ttisp->tt_desigidx]; -#endif /* defined TM_ZONE */ - if (setname) - update_tzname_etc(sp, ttisp); - } - return result; -} - -#if NETBSD_INSPIRED - -struct tm * -localtime_rz(struct state *sp, time_t const *timep, - struct tm *tmp) -{ - return localsub(sp, timep, 0, tmp); -} - -#endif - -static struct tm * -localtime_tzset(time_t const *timep, struct tm *tmp, bool setname) -{ - int err = lock(); - if (err) { - errno = err; - return NULL; - } - if (setname || !lcl_is_set) - tzset_unlocked(); - tmp = localsub(lclptr, timep, setname, tmp); - unlock(); - return tmp; -} - -struct tm * -localtime(const time_t *timep) -{ -#if !SUPPORT_C89 - static struct tm tm; -#endif - return localtime_tzset(timep, &tm, true); -} - -struct tm * -localtime_r(const time_t *restrict timep, struct tm *restrict tmp) -{ - return localtime_tzset(timep, tmp, false); -} - -/* -** gmtsub is to gmtime as localsub is to localtime. -*/ - -static struct tm * -gmtsub(ATTRIBUTE_MAYBE_UNUSED struct state const *sp, time_t const *timep, - int_fast32_t offset, struct tm *tmp) -{ - register struct tm * result; - - result = timesub(timep, offset, gmtptr, tmp); -#ifdef TM_ZONE - /* - ** Could get fancy here and deliver something such as - ** "+xx" or "-xx" if offset is non-zero, - ** but this is no time for a treasure hunt. - */ - tmp->TM_ZONE = ((char *) - (offset ? wildabbr : gmtptr ? gmtptr->chars : utc)); -#endif /* defined TM_ZONE */ - return result; -} - -/* -* Re-entrant version of gmtime. -*/ - -struct tm * -gmtime_r(time_t const *restrict timep, struct tm *restrict tmp) -{ - gmtcheck(); - return gmtsub(gmtptr, timep, 0, tmp); -} - -struct tm * -gmtime(const time_t *timep) -{ -#if !SUPPORT_C89 - static struct tm tm; -#endif - return gmtime_r(timep, &tm); -} - -#if STD_INSPIRED - -/* This function is obsolescent and may disappear in future releases. - Callers can instead use localtime_rz with a fixed-offset zone. */ - -struct tm * -offtime(const time_t *timep, long offset) -{ - gmtcheck(); - -#if !SUPPORT_C89 - static struct tm tm; -#endif - return gmtsub(gmtptr, timep, offset, &tm); -} - -#endif - -/* -** Return the number of leap years through the end of the given year -** where, to make the math easy, the answer for year zero is defined as zero. -*/ - -static time_t -leaps_thru_end_of_nonneg(time_t y) -{ - return y / 4 - y / 100 + y / 400; -} - -static time_t -leaps_thru_end_of(time_t y) -{ - return (y < 0 - ? -1 - leaps_thru_end_of_nonneg(-1 - y) - : leaps_thru_end_of_nonneg(y)); -} - -static struct tm * -timesub(const time_t *timep, int_fast32_t offset, - const struct state *sp, struct tm *tmp) -{ - register const struct lsinfo * lp; - register time_t tdays; - register const int * ip; - register int_fast32_t corr; - register int i; - int_fast32_t idays, rem, dayoff, dayrem; - time_t y; - - /* If less than SECSPERMIN, the number of seconds since the - most recent positive leap second; otherwise, do not add 1 - to localtime tm_sec because of leap seconds. */ - time_t secs_since_posleap = SECSPERMIN; - - corr = 0; - i = (sp == NULL) ? 0 : sp->leapcnt; - while (--i >= 0) { - lp = &sp->lsis[i]; - if (*timep >= lp->ls_trans) { - corr = lp->ls_corr; - if ((i == 0 ? 0 : lp[-1].ls_corr) < corr) - secs_since_posleap = *timep - lp->ls_trans; - break; - } - } - - /* Calculate the year, avoiding integer overflow even if - time_t is unsigned. */ - tdays = *timep / SECSPERDAY; - rem = *timep % SECSPERDAY; - rem += offset % SECSPERDAY - corr % SECSPERDAY + 3 * SECSPERDAY; - dayoff = offset / SECSPERDAY - corr / SECSPERDAY + rem / SECSPERDAY - 3; - rem %= SECSPERDAY; - /* y = (EPOCH_YEAR - + floor((tdays + dayoff) / DAYSPERREPEAT) * YEARSPERREPEAT), - sans overflow. But calculate against 1570 (EPOCH_YEAR - - YEARSPERREPEAT) instead of against 1970 so that things work - for localtime values before 1970 when time_t is unsigned. */ - dayrem = tdays % DAYSPERREPEAT; - dayrem += dayoff % DAYSPERREPEAT; - y = (EPOCH_YEAR - YEARSPERREPEAT - + ((1 + dayoff / DAYSPERREPEAT + dayrem / DAYSPERREPEAT - - ((dayrem % DAYSPERREPEAT) < 0) - + tdays / DAYSPERREPEAT) - * YEARSPERREPEAT)); - /* idays = (tdays + dayoff) mod DAYSPERREPEAT, sans overflow. */ - idays = tdays % DAYSPERREPEAT; - idays += dayoff % DAYSPERREPEAT + 2 * DAYSPERREPEAT; - idays %= DAYSPERREPEAT; - /* Increase Y and decrease IDAYS until IDAYS is in range for Y. */ - while (year_lengths[isleap(y)] <= idays) { - int tdelta = idays / DAYSPERLYEAR; - int_fast32_t ydelta = tdelta + !tdelta; - time_t newy = y + ydelta; - register int leapdays; - leapdays = leaps_thru_end_of(newy - 1) - - leaps_thru_end_of(y - 1); - idays -= ydelta * DAYSPERNYEAR; - idays -= leapdays; - y = newy; - } - -#ifdef ckd_add - if (ckd_add(&tmp->tm_year, y, -TM_YEAR_BASE)) { - errno = EOVERFLOW; - return NULL; - } -#else - if (!TYPE_SIGNED(time_t) && y < TM_YEAR_BASE) { - int signed_y = y; - tmp->tm_year = signed_y - TM_YEAR_BASE; - } else if ((!TYPE_SIGNED(time_t) || INT_MIN + TM_YEAR_BASE <= y) - && y - TM_YEAR_BASE <= INT_MAX) - tmp->tm_year = y - TM_YEAR_BASE; - else { - errno = EOVERFLOW; - return NULL; - } -#endif - tmp->tm_yday = idays; - /* - ** The "extra" mods below avoid overflow problems. - */ - tmp->tm_wday = (TM_WDAY_BASE - + ((tmp->tm_year % DAYSPERWEEK) - * (DAYSPERNYEAR % DAYSPERWEEK)) - + leaps_thru_end_of(y - 1) - - leaps_thru_end_of(TM_YEAR_BASE - 1) - + idays); - tmp->tm_wday %= DAYSPERWEEK; - if (tmp->tm_wday < 0) - tmp->tm_wday += DAYSPERWEEK; - tmp->tm_hour = rem / SECSPERHOUR; - rem %= SECSPERHOUR; - tmp->tm_min = rem / SECSPERMIN; - tmp->tm_sec = rem % SECSPERMIN; - - /* Use "... ??:??:60" at the end of the localtime minute containing - the second just before the positive leap second. */ - tmp->tm_sec += secs_since_posleap <= tmp->tm_sec; - - ip = mon_lengths[isleap(y)]; - for (tmp->tm_mon = 0; idays >= ip[tmp->tm_mon]; ++(tmp->tm_mon)) - idays -= ip[tmp->tm_mon]; - tmp->tm_mday = idays + 1; - tmp->tm_isdst = 0; -#ifdef TM_GMTOFF - tmp->TM_GMTOFF = offset; -#endif /* defined TM_GMTOFF */ - return tmp; -} - -/* -** Adapted from code provided by Robert Elz, who writes: -** The "best" way to do mktime I think is based on an idea of Bob -** Kridle's (so its said...) from a long time ago. -** It does a binary search of the time_t space. Since time_t's are -** just 32 bits, its a max of 32 iterations (even at 64 bits it -** would still be very reasonable). -*/ - -#ifndef WRONG -# define WRONG (-1) -#endif /* !defined WRONG */ - -/* -** Normalize logic courtesy Paul Eggert. -*/ - -static bool -increment_overflow(int *ip, int j) -{ -#ifdef ckd_add - return ckd_add(ip, *ip, j); -#else - register int const i = *ip; - - /* - ** If i >= 0 there can only be overflow if i + j > INT_MAX - ** or if j > INT_MAX - i; given i >= 0, INT_MAX - i cannot overflow. - ** If i < 0 there can only be overflow if i + j < INT_MIN - ** or if j < INT_MIN - i; given i < 0, INT_MIN - i cannot overflow. - */ - if ((i >= 0) ? (j > INT_MAX - i) : (j < INT_MIN - i)) - return true; - *ip += j; - return false; -#endif -} - -static bool -increment_overflow32(int_fast32_t *const lp, int const m) -{ -#ifdef ckd_add - return ckd_add(lp, *lp, m); -#else - register int_fast32_t const l = *lp; - - if ((l >= 0) ? (m > INT_FAST32_MAX - l) : (m < INT_FAST32_MIN - l)) - return true; - *lp += m; - return false; -#endif -} - -static bool -increment_overflow_time(time_t *tp, int_fast32_t j) -{ -#ifdef ckd_add - return ckd_add(tp, *tp, j); -#else - /* - ** This is like - ** 'if (! (TIME_T_MIN <= *tp + j && *tp + j <= TIME_T_MAX)) ...', - ** except that it does the right thing even if *tp + j would overflow. - */ - if (! (j < 0 - ? (TYPE_SIGNED(time_t) ? TIME_T_MIN - j <= *tp : -1 - j < *tp) - : *tp <= TIME_T_MAX - j)) - return true; - *tp += j; - return false; -#endif -} - -static bool -normalize_overflow(int *const tensptr, int *const unitsptr, const int base) -{ - register int tensdelta; - - tensdelta = (*unitsptr >= 0) ? - (*unitsptr / base) : - (-1 - (-1 - *unitsptr) / base); - *unitsptr -= tensdelta * base; - return increment_overflow(tensptr, tensdelta); -} - -static bool -normalize_overflow32(int_fast32_t *tensptr, int *unitsptr, int base) -{ - register int tensdelta; - - tensdelta = (*unitsptr >= 0) ? - (*unitsptr / base) : - (-1 - (-1 - *unitsptr) / base); - *unitsptr -= tensdelta * base; - return increment_overflow32(tensptr, tensdelta); -} - -static int -tmcomp(register const struct tm *const atmp, - register const struct tm *const btmp) -{ - register int result; - - if (atmp->tm_year != btmp->tm_year) - return atmp->tm_year < btmp->tm_year ? -1 : 1; - if ((result = (atmp->tm_mon - btmp->tm_mon)) == 0 && - (result = (atmp->tm_mday - btmp->tm_mday)) == 0 && - (result = (atmp->tm_hour - btmp->tm_hour)) == 0 && - (result = (atmp->tm_min - btmp->tm_min)) == 0) - result = atmp->tm_sec - btmp->tm_sec; - return result; -} - -/* Copy to *DEST from *SRC. Copy only the members needed for mktime, - as other members might not be initialized. */ -static void -mktmcpy(struct tm *dest, struct tm const *src) -{ - dest->tm_sec = src->tm_sec; - dest->tm_min = src->tm_min; - dest->tm_hour = src->tm_hour; - dest->tm_mday = src->tm_mday; - dest->tm_mon = src->tm_mon; - dest->tm_year = src->tm_year; - dest->tm_isdst = src->tm_isdst; -#if defined TM_GMTOFF && ! UNINIT_TRAP - dest->TM_GMTOFF = src->TM_GMTOFF; -#endif -} - -static time_t -time2sub(struct tm *const tmp, - struct tm *(*funcp)(struct state const *, time_t const *, - int_fast32_t, struct tm *), - struct state const *sp, - const int_fast32_t offset, - bool *okayp, - bool do_norm_secs) -{ - register int dir; - register int i, j; - register int saved_seconds; - register int_fast32_t li; - register time_t lo; - register time_t hi; - int_fast32_t y; - time_t newt; - time_t t; - struct tm yourtm, mytm; - - *okayp = false; - mktmcpy(&yourtm, tmp); - - if (do_norm_secs) { - if (normalize_overflow(&yourtm.tm_min, &yourtm.tm_sec, - SECSPERMIN)) - return WRONG; - } - if (normalize_overflow(&yourtm.tm_hour, &yourtm.tm_min, MINSPERHOUR)) - return WRONG; - if (normalize_overflow(&yourtm.tm_mday, &yourtm.tm_hour, HOURSPERDAY)) - return WRONG; - y = yourtm.tm_year; - if (normalize_overflow32(&y, &yourtm.tm_mon, MONSPERYEAR)) - return WRONG; - /* - ** Turn y into an actual year number for now. - ** It is converted back to an offset from TM_YEAR_BASE later. - */ - if (increment_overflow32(&y, TM_YEAR_BASE)) - return WRONG; - while (yourtm.tm_mday <= 0) { - if (increment_overflow32(&y, -1)) - return WRONG; - li = y + (1 < yourtm.tm_mon); - yourtm.tm_mday += year_lengths[isleap(li)]; - } - while (yourtm.tm_mday > DAYSPERLYEAR) { - li = y + (1 < yourtm.tm_mon); - yourtm.tm_mday -= year_lengths[isleap(li)]; - if (increment_overflow32(&y, 1)) - return WRONG; - } - for ( ; ; ) { - i = mon_lengths[isleap(y)][yourtm.tm_mon]; - if (yourtm.tm_mday <= i) - break; - yourtm.tm_mday -= i; - if (++yourtm.tm_mon >= MONSPERYEAR) { - yourtm.tm_mon = 0; - if (increment_overflow32(&y, 1)) - return WRONG; - } - } -#ifdef ckd_add - if (ckd_add(&yourtm.tm_year, y, -TM_YEAR_BASE)) - return WRONG; -#else - if (increment_overflow32(&y, -TM_YEAR_BASE)) - return WRONG; - if (! (INT_MIN <= y && y <= INT_MAX)) - return WRONG; - yourtm.tm_year = y; -#endif - if (yourtm.tm_sec >= 0 && yourtm.tm_sec < SECSPERMIN) - saved_seconds = 0; - else if (yourtm.tm_year < EPOCH_YEAR - TM_YEAR_BASE) { - /* - ** We can't set tm_sec to 0, because that might push the - ** time below the minimum representable time. - ** Set tm_sec to 59 instead. - ** This assumes that the minimum representable time is - ** not in the same minute that a leap second was deleted from, - ** which is a safer assumption than using 58 would be. - */ - if (increment_overflow(&yourtm.tm_sec, 1 - SECSPERMIN)) - return WRONG; - saved_seconds = yourtm.tm_sec; - yourtm.tm_sec = SECSPERMIN - 1; - } else { - saved_seconds = yourtm.tm_sec; - yourtm.tm_sec = 0; - } - /* - ** Do a binary search (this works whatever time_t's type is). - */ - lo = TIME_T_MIN; - hi = TIME_T_MAX; - for ( ; ; ) { - t = lo / 2 + hi / 2; - if (t < lo) - t = lo; - else if (t > hi) - t = hi; - if (! funcp(sp, &t, offset, &mytm)) { - /* - ** Assume that t is too extreme to be represented in - ** a struct tm; arrange things so that it is less - ** extreme on the next pass. - */ - dir = (t > 0) ? 1 : -1; - } else dir = tmcomp(&mytm, &yourtm); - if (dir != 0) { - if (t == lo) { - if (t == TIME_T_MAX) - return WRONG; - ++t; - ++lo; - } else if (t == hi) { - if (t == TIME_T_MIN) - return WRONG; - --t; - --hi; - } - if (lo > hi) - return WRONG; - if (dir > 0) - hi = t; - else lo = t; - continue; - } -#if defined TM_GMTOFF && ! UNINIT_TRAP - if (mytm.TM_GMTOFF != yourtm.TM_GMTOFF - && (yourtm.TM_GMTOFF < 0 - ? (-SECSPERDAY <= yourtm.TM_GMTOFF - && (mytm.TM_GMTOFF <= - (min_tz(INT_FAST32_MAX, LONG_MAX) - + yourtm.TM_GMTOFF))) - : (yourtm.TM_GMTOFF <= SECSPERDAY - && ((max_tz(INT_FAST32_MIN, LONG_MIN) - + yourtm.TM_GMTOFF) - <= mytm.TM_GMTOFF)))) { - /* MYTM matches YOURTM except with the wrong UT offset. - YOURTM.TM_GMTOFF is plausible, so try it instead. - It's OK if YOURTM.TM_GMTOFF contains uninitialized data, - since the guess gets checked. */ - time_t altt = t; - int_fast32_t diff = mytm.TM_GMTOFF - yourtm.TM_GMTOFF; - if (!increment_overflow_time(&altt, diff)) { - struct tm alttm; - if (funcp(sp, &altt, offset, &alttm) - && alttm.tm_isdst == mytm.tm_isdst - && alttm.TM_GMTOFF == yourtm.TM_GMTOFF - && tmcomp(&alttm, &yourtm) == 0) { - t = altt; - mytm = alttm; - } - } - } -#endif - if (yourtm.tm_isdst < 0 || mytm.tm_isdst == yourtm.tm_isdst) - break; - /* - ** Right time, wrong type. - ** Hunt for right time, right type. - ** It's okay to guess wrong since the guess - ** gets checked. - */ - if (sp == NULL) - return WRONG; - for (i = sp->typecnt - 1; i >= 0; --i) { - if (sp->ttis[i].tt_isdst != yourtm.tm_isdst) - continue; - for (j = sp->typecnt - 1; j >= 0; --j) { - if (sp->ttis[j].tt_isdst == yourtm.tm_isdst) - continue; - if (ttunspecified(sp, j)) - continue; - newt = (t + sp->ttis[j].tt_utoff - - sp->ttis[i].tt_utoff); - if (! funcp(sp, &newt, offset, &mytm)) - continue; - if (tmcomp(&mytm, &yourtm) != 0) - continue; - if (mytm.tm_isdst != yourtm.tm_isdst) - continue; - /* - ** We have a match. - */ - t = newt; - goto label; - } - } - return WRONG; - } - label: - newt = t + saved_seconds; - if ((newt < t) != (saved_seconds < 0)) - return WRONG; - t = newt; - if (funcp(sp, &t, offset, tmp)) - *okayp = true; - return t; -} - -static time_t -time2(struct tm * const tmp, - struct tm *(*funcp)(struct state const *, time_t const *, - int_fast32_t, struct tm *), - struct state const *sp, - const int_fast32_t offset, - bool *okayp) -{ - time_t t; - - /* - ** First try without normalization of seconds - ** (in case tm_sec contains a value associated with a leap second). - ** If that fails, try with normalization of seconds. - */ - t = time2sub(tmp, funcp, sp, offset, okayp, false); - return *okayp ? t : time2sub(tmp, funcp, sp, offset, okayp, true); -} - -static time_t -time1(struct tm *const tmp, - struct tm *(*funcp)(struct state const *, time_t const *, - int_fast32_t, struct tm *), - struct state const *sp, - const int_fast32_t offset) -{ - register time_t t; - register int samei, otheri; - register int sameind, otherind; - register int i; - register int nseen; - char seen[TZ_MAX_TYPES]; - unsigned char types[TZ_MAX_TYPES]; - bool okay; - - if (tmp == NULL) { - errno = EINVAL; - return WRONG; - } - if (tmp->tm_isdst > 1) - tmp->tm_isdst = 1; - t = time2(tmp, funcp, sp, offset, &okay); - if (okay) - return t; - if (tmp->tm_isdst < 0) -#ifdef PCTS - /* - ** POSIX Conformance Test Suite code courtesy Grant Sullivan. - */ - tmp->tm_isdst = 0; /* reset to std and try again */ -#else - return t; -#endif /* !defined PCTS */ - /* - ** We're supposed to assume that somebody took a time of one type - ** and did some math on it that yielded a "struct tm" that's bad. - ** We try to divine the type they started from and adjust to the - ** type they need. - */ - if (sp == NULL) - return WRONG; - for (i = 0; i < sp->typecnt; ++i) - seen[i] = false; - nseen = 0; - for (i = sp->timecnt - 1; i >= 0; --i) - if (!seen[sp->types[i]] && !ttunspecified(sp, sp->types[i])) { - seen[sp->types[i]] = true; - types[nseen++] = sp->types[i]; - } - for (sameind = 0; sameind < nseen; ++sameind) { - samei = types[sameind]; - if (sp->ttis[samei].tt_isdst != tmp->tm_isdst) - continue; - for (otherind = 0; otherind < nseen; ++otherind) { - otheri = types[otherind]; - if (sp->ttis[otheri].tt_isdst == tmp->tm_isdst) - continue; - tmp->tm_sec += (sp->ttis[otheri].tt_utoff - - sp->ttis[samei].tt_utoff); - tmp->tm_isdst = !tmp->tm_isdst; - t = time2(tmp, funcp, sp, offset, &okay); - if (okay) - return t; - tmp->tm_sec -= (sp->ttis[otheri].tt_utoff - - sp->ttis[samei].tt_utoff); - tmp->tm_isdst = !tmp->tm_isdst; - } - } - return WRONG; -} - -static time_t -mktime_tzname(struct state *sp, struct tm *tmp, bool setname) -{ - if (sp) - return time1(tmp, localsub, sp, setname); - else { - gmtcheck(); - return time1(tmp, gmtsub, gmtptr, 0); - } -} - -#if NETBSD_INSPIRED - -time_t -mktime_z(struct state *sp, struct tm *tmp) -{ - return mktime_tzname(sp, tmp, false); -} - -#endif - -time_t -mktime(struct tm *tmp) -{ - time_t t; - int err = lock(); - if (err) { - errno = err; - return -1; - } - tzset_unlocked(); - t = mktime_tzname(lclptr, tmp, true); - unlock(); - return t; -} - -#if STD_INSPIRED -/* This function is obsolescent and may disappear in future releases. - Callers can instead use mktime. */ -time_t -timelocal(struct tm *tmp) -{ - if (tmp != NULL) - tmp->tm_isdst = -1; /* in case it wasn't initialized */ - return mktime(tmp); -} -#endif - -#ifndef EXTERN_TIMEOFF -# ifndef timeoff -# define timeoff my_timeoff /* Don't collide with OpenBSD 7.4 . */ -# endif -# define EXTERN_TIMEOFF static -#endif - -/* This function is obsolescent and may disappear in future releases. - Callers can instead use mktime_z with a fixed-offset zone. */ -EXTERN_TIMEOFF time_t -timeoff(struct tm *tmp, long offset) -{ - if (tmp) - tmp->tm_isdst = 0; - gmtcheck(); - return time1(tmp, gmtsub, gmtptr, offset); -} - -time_t -timegm(struct tm *tmp) -{ - time_t t; - struct tm tmcpy; - mktmcpy(&tmcpy, tmp); - tmcpy.tm_wday = -1; - t = timeoff(&tmcpy, 0); - if (0 <= tmcpy.tm_wday) - *tmp = tmcpy; - return t; -} - -static int_fast32_t -leapcorr(struct state const *sp, time_t t) -{ - register struct lsinfo const * lp; - register int i; - - i = sp->leapcnt; - while (--i >= 0) { - lp = &sp->lsis[i]; - if (t >= lp->ls_trans) - return lp->ls_corr; - } - return 0; -} - - -/* - * ************************************************************************** - * The code from here to the end of the file is the code for the - * strftime() function. - * ***************** - */ - - -#ifndef DEPRECATE_TWO_DIGIT_YEARS -# define DEPRECATE_TWO_DIGIT_YEARS false -#endif - -struct lc_time_T { - const char * mon[MONSPERYEAR]; - const char * month[MONSPERYEAR]; - const char * wday[DAYSPERWEEK]; - const char * weekday[DAYSPERWEEK]; - const char * X_fmt; - const char * x_fmt; - const char * c_fmt; - const char * am; - const char * pm; - const char * date_fmt; -}; - -static const struct lc_time_T C_time_locale = { - { - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" - }, { - "January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December" - }, { - "Sun", "Mon", "Tue", "Wed", - "Thu", "Fri", "Sat" - }, { - "Sunday", "Monday", "Tuesday", "Wednesday", - "Thursday", "Friday", "Saturday" - }, - - /* X_fmt */ - "%H:%M:%S", - - /* - ** x_fmt - ** C99 and later require this format. - ** Using just numbers (as here) makes Quakers happier; - ** it's also compatible with SVR4. - */ - "%m/%d/%y", - - /* - ** c_fmt - ** C99 and later require this format. - ** Previously this code used "%D %X", but we now conform to C99. - ** Note that - ** "%a %b %d %H:%M:%S %Y" - ** is used by Solaris 2.3. - */ - "%a %b %e %T %Y", - - /* am */ - "AM", - - /* pm */ - "PM", - - /* date_fmt */ - "%a %b %e %H:%M:%S %Z %Y" -}; - -enum warn { IN_NONE, IN_SOME, IN_THIS, IN_ALL }; - -static char * _add(const char *, char *, const char *); -static char * _conv(int, const char *, char *, const char *); -static char * _fmt(const char *, const struct tm *, char *, const char *, - enum warn *); -static char * _yconv(int, int, bool, bool, char *, char const *); - -#ifndef YEAR_2000_NAME -# define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS" -#endif /* !defined YEAR_2000_NAME */ - -#if HAVE_STRFTIME_L -size_t -strftime_l(char *restrict s, size_t maxsize, char const *restrict format, - struct tm const *restrict t, - ATTRIBUTE_MAYBE_UNUSED locale_t locale) -{ - /* Just call strftime, as only the C locale is supported. */ - return strftime(s, maxsize, format, t); -} -#endif - -size_t -strftime(char *restrict s, size_t maxsize, char const *restrict format, - struct tm const *restrict t) -{ - char * p; - int saved_errno = errno; - enum warn warn = IN_NONE; - - tzset(); - p = _fmt(format, t, s, s + maxsize, &warn); - if (DEPRECATE_TWO_DIGIT_YEARS - && warn != IN_NONE && getenv(YEAR_2000_NAME)) { - fprintf(stderr, "\n"); - fprintf(stderr, "strftime format \"%s\" ", format); - fprintf(stderr, "yields only two digits of years in "); - if (warn == IN_SOME) - fprintf(stderr, "some locales"); - else if (warn == IN_THIS) - fprintf(stderr, "the current locale"); - else fprintf(stderr, "all locales"); - fprintf(stderr, "\n"); - } - if (p == s + maxsize) { - errno = ERANGE; - return 0; - } - *p = '\0'; - errno = saved_errno; - return p - s; -} - -static char * -_fmt(const char *format, const struct tm *t, char *pt, - const char *ptlim, enum warn *warnp) -{ - struct lc_time_T const *Locale = &C_time_locale; - - for ( ; *format; ++format) { - if (*format == '%') { - label: - switch (*++format) { - default: - /* Output unknown conversion specifiers as-is, - to aid debugging. This includes '%' at - format end. This conforms to C23 section - 7.29.3.5 paragraph 6, which says behavior - is undefined here. */ - --format; - break; - case 'A': - pt = _add((t->tm_wday < 0 || - t->tm_wday >= DAYSPERWEEK) ? - "?" : Locale->weekday[t->tm_wday], - pt, ptlim); - continue; - case 'a': - pt = _add((t->tm_wday < 0 || - t->tm_wday >= DAYSPERWEEK) ? - "?" : Locale->wday[t->tm_wday], - pt, ptlim); - continue; - case 'B': - pt = _add((t->tm_mon < 0 || - t->tm_mon >= MONSPERYEAR) ? - "?" : Locale->month[t->tm_mon], - pt, ptlim); - continue; - case 'b': - case 'h': - pt = _add((t->tm_mon < 0 || - t->tm_mon >= MONSPERYEAR) ? - "?" : Locale->mon[t->tm_mon], - pt, ptlim); - continue; - case 'C': - /* - ** %C used to do a... - ** _fmt("%a %b %e %X %Y", t); - ** ...whereas now POSIX 1003.2 calls for - ** something completely different. - ** (ado, 1993-05-24) - */ - pt = _yconv(t->tm_year, TM_YEAR_BASE, - true, false, pt, ptlim); - continue; - case 'c': - { - enum warn warn2 = IN_SOME; - - pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2); - if (warn2 == IN_ALL) - warn2 = IN_THIS; - if (warn2 > *warnp) - *warnp = warn2; - } - continue; - case 'D': - pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp); - continue; - case 'd': - pt = _conv(t->tm_mday, "%02d", pt, ptlim); - continue; - case 'E': - case 'O': - /* - ** Locale modifiers of C99 and later. - ** The sequences - ** %Ec %EC %Ex %EX %Ey %EY - ** %Od %oe %OH %OI %Om %OM - ** %OS %Ou %OU %OV %Ow %OW %Oy - ** are supposed to provide alternative - ** representations. - */ - goto label; - case 'e': - pt = _conv(t->tm_mday, "%2d", pt, ptlim); - continue; - case 'F': - pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp); - continue; - case 'H': - pt = _conv(t->tm_hour, "%02d", pt, ptlim); - continue; - case 'I': - pt = _conv((t->tm_hour % 12) ? - (t->tm_hour % 12) : 12, - "%02d", pt, ptlim); - continue; - case 'j': - pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim); - continue; - case 'k': - /* - ** This used to be... - ** _conv(t->tm_hour % 12 ? - ** t->tm_hour % 12 : 12, 2, ' '); - ** ...and has been changed to the below to - ** match SunOS 4.1.1 and Arnold Robbins' - ** strftime version 3.0. That is, "%k" and - ** "%l" have been swapped. - ** (ado, 1993-05-24) - */ - pt = _conv(t->tm_hour, "%2d", pt, ptlim); - continue; -#ifdef KITCHEN_SINK - case 'K': - /* - ** After all this time, still unclaimed! - */ - pt = _add("kitchen sink", pt, ptlim); - continue; -#endif /* defined KITCHEN_SINK */ - case 'l': - /* - ** This used to be... - ** _conv(t->tm_hour, 2, ' '); - ** ...and has been changed to the below to - ** match SunOS 4.1.1 and Arnold Robbin's - ** strftime version 3.0. That is, "%k" and - ** "%l" have been swapped. - ** (ado, 1993-05-24) - */ - pt = _conv((t->tm_hour % 12) ? - (t->tm_hour % 12) : 12, - "%2d", pt, ptlim); - continue; - case 'M': - pt = _conv(t->tm_min, "%02d", pt, ptlim); - continue; - case 'm': - pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim); - continue; - case 'n': - pt = _add("\n", pt, ptlim); - continue; - case 'p': - pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ? - Locale->pm : - Locale->am, - pt, ptlim); - continue; - case 'R': - pt = _fmt("%H:%M", t, pt, ptlim, warnp); - continue; - case 'r': - pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp); - continue; - case 'S': - pt = _conv(t->tm_sec, "%02d", pt, ptlim); - continue; - case 's': - { - struct tm tm; - char buf[INT_STRLEN_MAXIMUM( - time_t) + 1]; - time_t mkt; - - tm.tm_sec = t->tm_sec; - tm.tm_min = t->tm_min; - tm.tm_hour = t->tm_hour; - tm.tm_mday = t->tm_mday; - tm.tm_mon = t->tm_mon; - tm.tm_year = t->tm_year; -#ifdef TM_GMTOFF - mkt = timeoff(&tm, t->TM_GMTOFF); -#else - tm.tm_isdst = t->tm_isdst; - mkt = mktime(&tm); -#endif - /* If mktime fails, %s expands to the - value of (time_t) -1 as a failure - marker; this is better in practice - than strftime failing. */ - if (TYPE_SIGNED(time_t)) { - intmax_t n = mkt; - sprintf(buf, "%"PRIdMAX, n); - } else { - uintmax_t n = mkt; - sprintf(buf, "%"PRIuMAX, n); - } - pt = _add(buf, pt, ptlim); - } - continue; - case 'T': - pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp); - continue; - case 't': - pt = _add("\t", pt, ptlim); - continue; - case 'U': - pt = _conv((t->tm_yday + DAYSPERWEEK - - t->tm_wday) / DAYSPERWEEK, - "%02d", pt, ptlim); - continue; - case 'u': - /* - ** From Arnold Robbins' strftime version 3.0: - ** "ISO 8601: Weekday as a decimal number - ** [1 (Monday) - 7]" - ** (ado, 1993-05-24) - */ - pt = _conv((t->tm_wday == 0) ? - DAYSPERWEEK : t->tm_wday, - "%d", pt, ptlim); - continue; - case 'V': /* ISO 8601 week number */ - case 'G': /* ISO 8601 year (four digits) */ - case 'g': /* ISO 8601 year (two digits) */ -/* -** From Arnold Robbins' strftime version 3.0: "the week number of the -** year (the first Monday as the first day of week 1) as a decimal number -** (01-53)." -** (ado, 1993-05-24) -** -** From by Markus Kuhn: -** "Week 01 of a year is per definition the first week which has the -** Thursday in this year, which is equivalent to the week which contains -** the fourth day of January. In other words, the first week of a new year -** is the week which has the majority of its days in the new year. Week 01 -** might also contain days from the previous year and the week before week -** 01 of a year is the last week (52 or 53) of the previous year even if -** it contains days from the new year. A week starts with Monday (day 1) -** and ends with Sunday (day 7). For example, the first week of the year -** 1997 lasts from 1996-12-30 to 1997-01-05..." -** (ado, 1996-01-02) -*/ - { - int year; - int base; - int yday; - int wday; - int w; - - year = t->tm_year; - base = TM_YEAR_BASE; - yday = t->tm_yday; - wday = t->tm_wday; - for ( ; ; ) { - int len; - int bot; - int top; - - len = isleap_sum(year, base) ? - DAYSPERLYEAR : - DAYSPERNYEAR; - /* - ** What yday (-3 ... 3) does - ** the ISO year begin on? - */ - bot = ((yday + 11 - wday) % - DAYSPERWEEK) - 3; - /* - ** What yday does the NEXT - ** ISO year begin on? - */ - top = bot - - (len % DAYSPERWEEK); - if (top < -3) - top += DAYSPERWEEK; - top += len; - if (yday >= top) { - ++base; - w = 1; - break; - } - if (yday >= bot) { - w = 1 + ((yday - bot) / - DAYSPERWEEK); - break; - } - --base; - yday += isleap_sum(year, base) ? - DAYSPERLYEAR : - DAYSPERNYEAR; - } -#ifdef XPG4_1994_04_09 - if ((w == 52 && - t->tm_mon == TM_JANUARY) || - (w == 1 && - t->tm_mon == TM_DECEMBER)) - w = 53; -#endif /* defined XPG4_1994_04_09 */ - if (*format == 'V') - pt = _conv(w, "%02d", - pt, ptlim); - else if (*format == 'g') { - *warnp = IN_ALL; - pt = _yconv(year, base, - false, true, - pt, ptlim); - } else pt = _yconv(year, base, - true, true, - pt, ptlim); - } - continue; - case 'v': - /* - ** From Arnold Robbins' strftime version 3.0: - ** "date as dd-bbb-YYYY" - ** (ado, 1993-05-24) - */ - pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp); - continue; - case 'W': - pt = _conv((t->tm_yday + DAYSPERWEEK - - (t->tm_wday ? - (t->tm_wday - 1) : - (DAYSPERWEEK - 1))) / DAYSPERWEEK, - "%02d", pt, ptlim); - continue; - case 'w': - pt = _conv(t->tm_wday, "%d", pt, ptlim); - continue; - case 'X': - pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp); - continue; - case 'x': - { - enum warn warn2 = IN_SOME; - - pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2); - if (warn2 == IN_ALL) - warn2 = IN_THIS; - if (warn2 > *warnp) - *warnp = warn2; - } - continue; - case 'y': - *warnp = IN_ALL; - pt = _yconv(t->tm_year, TM_YEAR_BASE, - false, true, - pt, ptlim); - continue; - case 'Y': - pt = _yconv(t->tm_year, TM_YEAR_BASE, - true, true, - pt, ptlim); - continue; - case 'Z': -#ifdef TM_ZONE - pt = _add(t->TM_ZONE, pt, ptlim); -#elif HAVE_TZNAME - if (t->tm_isdst >= 0) - pt = _add(tzname[t->tm_isdst != 0], - pt, ptlim); -#endif - /* - ** C99 and later say that %Z must be - ** replaced by the empty string if the - ** time zone abbreviation is not - ** determinable. - */ - continue; - case 'z': -#if defined TM_GMTOFF || USG_COMPAT || ALTZONE - { - long diff; - char const * sign; - bool negative; - -# ifdef TM_GMTOFF - diff = t->TM_GMTOFF; -# else - /* - ** C99 and later say that the UT offset must - ** be computed by looking only at - ** tm_isdst. This requirement is - ** incorrect, since it means the code - ** must rely on magic (in this case - ** altzone and timezone), and the - ** magic might not have the correct - ** offset. Doing things correctly is - ** tricky and requires disobeying the standard; - ** see GNU C strftime for details. - ** For now, punt and conform to the - ** standard, even though it's incorrect. - ** - ** C99 and later say that %z must be replaced by - ** the empty string if the time zone is not - ** determinable, so output nothing if the - ** appropriate variables are not available. - */ - if (t->tm_isdst < 0) - continue; - if (t->tm_isdst == 0) -# if USG_COMPAT - diff = -timezone; -# else - continue; -# endif - else -# if ALTZONE - diff = -altzone; -# else - continue; -# endif -# endif - negative = diff < 0; - if (diff == 0) { -# ifdef TM_ZONE - negative = t->TM_ZONE[0] == '-'; -# else - negative = t->tm_isdst < 0; -# if HAVE_TZNAME - if (tzname[t->tm_isdst != 0][0] == '-') - negative = true; -# endif -# endif - } - if (negative) { - sign = "-"; - diff = -diff; - } else sign = "+"; - pt = _add(sign, pt, ptlim); - diff /= SECSPERMIN; - diff = (diff / MINSPERHOUR) * 100 + - (diff % MINSPERHOUR); - pt = _conv(diff, "%04d", pt, ptlim); - } -#endif - continue; - case '+': - pt = _fmt(Locale->date_fmt, t, pt, ptlim, - warnp); - continue; - case '%': - break; - } - } - if (pt == ptlim) - break; - *pt++ = *format; - } - return pt; -} - -static char * -_conv(int n, const char *format, char *pt, const char *ptlim) -{ - char buf[INT_STRLEN_MAXIMUM(int) + 1]; - - sprintf(buf, format, n); - return _add(buf, pt, ptlim); -} - -static char * -_add(const char *str, char *pt, const char *ptlim) -{ - while (pt < ptlim && (*pt = *str++) != '\0') - ++pt; - return pt; -} - -/* -** POSIX and the C Standard are unclear or inconsistent about -** what %C and %y do if the year is negative or exceeds 9999. -** Use the convention that %C concatenated with %y yields the -** same output as %Y, and that %Y contains at least 4 bytes, -** with more only if necessary. -*/ - -static char * -_yconv(int a, int b, bool convert_top, bool convert_yy, - char *pt, const char *ptlim) -{ - register int lead; - register int trail; - - int DIVISOR = 100; - trail = a % DIVISOR + b % DIVISOR; - lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR; - trail %= DIVISOR; - if (trail < 0 && lead > 0) { - trail += DIVISOR; - --lead; - } else if (lead < 0 && trail > 0) { - trail -= DIVISOR; - ++lead; - } - if (convert_top) { - if (lead == 0 && trail < 0) - pt = _add("-0", pt, ptlim); - else pt = _conv(lead, "%02d", pt, ptlim); - } - if (convert_yy) - pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim); - return pt; } \ No newline at end of file diff --git a/source/os/src/timezone/localtime.c b/source/os/src/timezone/localtime.c new file mode 100644 index 0000000000..ea2a6fb8a9 --- /dev/null +++ b/source/os/src/timezone/localtime.c @@ -0,0 +1,2498 @@ +/* Convert timestamp from time_t to struct tm. */ + +/* +** This file is in the public domain, so clarified as of +** 1996-06-05 by Arthur David Olson. +*/ + +/* +** Leap second handling from Bradley White. +** POSIX.1-1988 style TZ environment variable handling from Guy Harris. +*/ + +/*LINTLIBRARY*/ + +#define LOCALTIME_IMPLEMENTATION +#include "private.h" + +#include "tzdir.h" +#include "tzfile.h" +#include + +#if defined THREAD_SAFE && THREAD_SAFE +# include +static pthread_mutex_t locallock = PTHREAD_MUTEX_INITIALIZER; +static int lock(void) { return pthread_mutex_lock(&locallock); } +static void unlock(void) { pthread_mutex_unlock(&locallock); } +#else +static int lock(void) { return 0; } +static void unlock(void) { } +#endif + +/* A signed type wider than int, so that we can add 1900 + tm_mon/12 to tm_year + without overflow. The static_assert checks that it is indeed wider + than int; if this fails on your platform please let us know. */ +#if INT_MAX < LONG_MAX +typedef long iinntt; +# define IINNTT_MIN LONG_MIN +# define IINNTT_MAX LONG_MAX +#elif INT_MAX < LLONG_MAX +typedef long long iinntt; +# define IINNTT_MIN LLONG_MIN +# define IINNTT_MAX LLONG_MAX +#else +typedef intmax_t iinntt; +# define IINNTT_MIN INTMAX_MIN +# define IINNTT_MAX INTMAX_MAX +#endif +static_assert(IINNTT_MIN < INT_MIN && INT_MAX < IINNTT_MAX); + +/* On platforms where offtime or mktime might overflow, + strftime.c defines USE_TIMEX_T to be true and includes us. + This tells us to #define time_t to an internal type timex_t that is + wide enough so that strftime %s never suffers from integer overflow, + and to #define offtime (if TM_GMTOFF is defined) or mktime (otherwise) + to a static function that returns the redefined time_t. + It also tells us to define only data and code needed + to support the offtime or mktime variant. */ +#ifndef USE_TIMEX_T +# define USE_TIMEX_T false +#endif +#if USE_TIMEX_T +# undef TIME_T_MIN +# undef TIME_T_MAX +# undef time_t +# define time_t timex_t +# if MKTIME_FITS_IN(LONG_MIN, LONG_MAX) +typedef long timex_t; +# define TIME_T_MIN LONG_MIN +# define TIME_T_MAX LONG_MAX +# elif MKTIME_FITS_IN(LLONG_MIN, LLONG_MAX) +typedef long long timex_t; +# define TIME_T_MIN LLONG_MIN +# define TIME_T_MAX LLONG_MAX +# else +typedef intmax_t timex_t; +# define TIME_T_MIN INTMAX_MIN +# define TIME_T_MAX INTMAX_MAX +# endif + +# ifdef TM_GMTOFF +# undef timeoff +# define timeoff timex_timeoff +# undef EXTERN_TIMEOFF +# else +# undef mktime +# define mktime timex_mktime +# endif +#endif + +#ifndef TZ_ABBR_CHAR_SET +# define TZ_ABBR_CHAR_SET \ + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 :+-._" +#endif /* !defined TZ_ABBR_CHAR_SET */ + +#ifndef TZ_ABBR_ERR_CHAR +# define TZ_ABBR_ERR_CHAR '_' +#endif /* !defined TZ_ABBR_ERR_CHAR */ + +/* +** Support non-POSIX platforms that distinguish between text and binary files. +*/ + +#ifndef O_BINARY +# define O_BINARY 0 +#endif + +#ifndef WILDABBR +/* +** Someone might make incorrect use of a time zone abbreviation: +** 1. They might reference tzname[0] before calling tzset (explicitly +** or implicitly). +** 2. They might reference tzname[1] before calling tzset (explicitly +** or implicitly). +** 3. They might reference tzname[1] after setting to a time zone +** in which Daylight Saving Time is never observed. +** 4. They might reference tzname[0] after setting to a time zone +** in which Standard Time is never observed. +** 5. They might reference tm.TM_ZONE after calling offtime. +** What's best to do in the above cases is open to debate; +** for now, we just set things up so that in any of the five cases +** WILDABBR is used. Another possibility: initialize tzname[0] to the +** string "tzname[0] used before set", and similarly for the other cases. +** And another: initialize tzname[0] to "ERA", with an explanation in the +** manual page of what this "time zone abbreviation" means (doing this so +** that tzname[0] has the "normal" length of three characters). +*/ +# define WILDABBR " " +#endif /* !defined WILDABBR */ + +static const char wildabbr[] = WILDABBR; + +static char const etc_utc[] = "Etc/UTC"; + +#if defined TM_ZONE || ((!USE_TIMEX_T || !defined TM_GMTOFF) && defined TZ_NAME) +static char const *utc = etc_utc + sizeof "Etc/" - 1; +#endif + +/* +** The DST rules to use if TZ has no rules and we can't load TZDEFRULES. +** Default to US rules as of 2017-05-07. +** POSIX does not specify the default DST rules; +** for historical reasons, US rules are a common default. +*/ +#ifndef TZDEFRULESTRING +# define TZDEFRULESTRING ",M3.2.0,M11.1.0" +#endif + +struct ttinfo { /* time type information */ + int_fast32_t tt_utoff; /* UT offset in seconds */ + bool tt_isdst; /* used to set tm_isdst */ + int tt_desigidx; /* abbreviation list index */ + bool tt_ttisstd; /* transition is std time */ + bool tt_ttisut; /* transition is UT */ +}; + +struct lsinfo { /* leap second information */ + time_t ls_trans; /* transition time */ + int_fast32_t ls_corr; /* correction to apply */ +}; + +/* This abbreviation means local time is unspecified. */ +static char const UNSPEC[] = "-00"; + +/* How many extra bytes are needed at the end of struct state's chars array. + This needs to be at least 1 for null termination in case the input + data isn't properly terminated, and it also needs to be big enough + for ttunspecified to work without crashing. */ +enum { CHARS_EXTRA = max(sizeof UNSPEC, 2) - 1 }; + +/* Limit to time zone abbreviation length in proleptic TZ strings. + This is distinct from TZ_MAX_CHARS, which limits TZif file contents. */ +#ifndef TZNAME_MAXIMUM +# define TZNAME_MAXIMUM 255 +#endif + +/* A representation of the contents of a TZif file. Ideally this + would have no size limits; the following sizes should suffice for + practical use. This struct should not be too large, as instances + are put on the stack and stacks are relatively small on some platforms. + See tzfile.h for more about the sizes. */ +struct state { + int leapcnt; + int timecnt; + int typecnt; + int charcnt; + bool goback; + bool goahead; + time_t ats[TZ_MAX_TIMES]; + unsigned char types[TZ_MAX_TIMES]; + struct ttinfo ttis[TZ_MAX_TYPES]; + char chars[max(max(TZ_MAX_CHARS + CHARS_EXTRA, sizeof "UTC"), + 2 * (TZNAME_MAXIMUM + 1))]; + struct lsinfo lsis[TZ_MAX_LEAPS]; +}; + +enum r_type { + JULIAN_DAY, /* Jn = Julian day */ + DAY_OF_YEAR, /* n = day of year */ + MONTH_NTH_DAY_OF_WEEK /* Mm.n.d = month, week, day of week */ +}; + +struct rule { + enum r_type r_type; /* type of rule */ + int r_day; /* day number of rule */ + int r_week; /* week number of rule */ + int r_mon; /* month number of rule */ + int_fast32_t r_time; /* transition time of rule */ +}; + +static struct tm *gmtsub(struct state const *, time_t const *, int_fast32_t, + struct tm *); +static bool increment_overflow(int *, int); +static bool increment_overflow_time(time_t *, int_fast32_t); +static int_fast32_t leapcorr(struct state const *, time_t); +static struct tm *timesub(time_t const *, int_fast32_t, struct state const *, + struct tm *); +static bool tzparse(char const *, struct state *, struct state const *); + +#ifdef ALL_STATE +static struct state * lclptr; +static struct state * gmtptr; +#endif /* defined ALL_STATE */ + +#ifndef ALL_STATE +static struct state lclmem; +static struct state gmtmem; +static struct state *const lclptr = &lclmem; +static struct state *const gmtptr = &gmtmem; +#endif /* State Farm */ + +#ifndef TZ_STRLEN_MAX +# define TZ_STRLEN_MAX 255 +#endif /* !defined TZ_STRLEN_MAX */ + +#if !USE_TIMEX_T || !defined TM_GMTOFF +static char lcl_TZname[TZ_STRLEN_MAX + 1]; +static int lcl_is_set; +#endif + +/* +** Section 4.12.3 of X3.159-1989 requires that +** Except for the strftime function, these functions [asctime, +** ctime, gmtime, localtime] return values in one of two static +** objects: a broken-down time structure and an array of char. +** Thanks to Paul Eggert for noting this. +** +** Although this requirement was removed in C99 it is still present in POSIX. +** Follow the requirement if SUPPORT_C89, even though this is more likely to +** trigger latent bugs in programs. +*/ + +#if !USE_TIMEX_T + +# if SUPPORT_C89 +static struct tm tm; +#endif + +# if 2 <= HAVE_TZNAME + TZ_TIME_T +char * tzname[2] = { + (char *) wildabbr, + (char *) wildabbr +}; +# endif +# if 2 <= USG_COMPAT + TZ_TIME_T +long timezone; +int daylight; +# endif +# if 2 <= ALTZONE + TZ_TIME_T +long altzone; +# endif + +#endif + +/* Initialize *S to a value based on UTOFF, ISDST, and DESIGIDX. */ +static void +init_ttinfo(struct ttinfo *s, int_fast32_t utoff, bool isdst, int desigidx) +{ + s->tt_utoff = utoff; + s->tt_isdst = isdst; + s->tt_desigidx = desigidx; + s->tt_ttisstd = false; + s->tt_ttisut = false; +} + +/* Return true if SP's time type I does not specify local time. */ +static bool +ttunspecified(struct state const *sp, int i) +{ + char const *abbr = &sp->chars[sp->ttis[i].tt_desigidx]; + /* memcmp is likely faster than strcmp, and is safe due to CHARS_EXTRA. */ + return memcmp(abbr, UNSPEC, sizeof UNSPEC) == 0; +} + +static int_fast32_t +detzcode(const char *const codep) +{ + register int_fast32_t result; + register int i; + int_fast32_t one = 1; + int_fast32_t halfmaxval = one << (32 - 2); + int_fast32_t maxval = halfmaxval - 1 + halfmaxval; + int_fast32_t minval = -1 - maxval; + + result = codep[0] & 0x7f; + for (i = 1; i < 4; ++i) + result = (result << 8) | (codep[i] & 0xff); + + if (codep[0] & 0x80) { + /* Do two's-complement negation even on non-two's-complement machines. + If the result would be minval - 1, return minval. */ + result -= !TWOS_COMPLEMENT(int_fast32_t) && result != 0; + result += minval; + } + return result; +} + +static int_fast64_t +detzcode64(const char *const codep) +{ + register int_fast64_t result; + register int i; + int_fast64_t one = 1; + int_fast64_t halfmaxval = one << (64 - 2); + int_fast64_t maxval = halfmaxval - 1 + halfmaxval; + int_fast64_t minval = -TWOS_COMPLEMENT(int_fast64_t) - maxval; + + result = codep[0] & 0x7f; + for (i = 1; i < 8; ++i) + result = (result << 8) | (codep[i] & 0xff); + + if (codep[0] & 0x80) { + /* Do two's-complement negation even on non-two's-complement machines. + If the result would be minval - 1, return minval. */ + result -= !TWOS_COMPLEMENT(int_fast64_t) && result != 0; + result += minval; + } + return result; +} + +#if !USE_TIMEX_T || !defined TM_GMTOFF + +static void +update_tzname_etc(struct state const *sp, struct ttinfo const *ttisp) +{ +# if HAVE_TZNAME + tzname[ttisp->tt_isdst] = (char *) &sp->chars[ttisp->tt_desigidx]; +# endif +# if USG_COMPAT + if (!ttisp->tt_isdst) + timezone = - ttisp->tt_utoff; +# endif +# if ALTZONE + if (ttisp->tt_isdst) + altzone = - ttisp->tt_utoff; +# endif +} + +/* If STDDST_MASK indicates that SP's TYPE provides useful info, + update tzname, timezone, and/or altzone and return STDDST_MASK, + diminished by the provided info if it is a specified local time. + Otherwise, return STDDST_MASK. See settzname for STDDST_MASK. */ +static int +may_update_tzname_etc(int stddst_mask, struct state *sp, int type) +{ + struct ttinfo *ttisp = &sp->ttis[type]; + int this_bit = 1 << ttisp->tt_isdst; + if (stddst_mask & this_bit) { + update_tzname_etc(sp, ttisp); + if (!ttunspecified(sp, type)) + return stddst_mask & ~this_bit; + } + return stddst_mask; +} + +static void +settzname(void) +{ + register struct state * const sp = lclptr; + register int i; + + /* If STDDST_MASK & 1 we need info about a standard time. + If STDDST_MASK & 2 we need info about a daylight saving time. + When STDDST_MASK becomes zero we can stop looking. */ + int stddst_mask = 0; + +# if HAVE_TZNAME + tzname[0] = tzname[1] = (char *) (sp ? wildabbr : utc); + stddst_mask = 3; +# endif +# if USG_COMPAT + timezone = 0; + stddst_mask = 3; +# endif +# if ALTZONE + altzone = 0; + stddst_mask |= 2; +# endif + /* + ** And to get the latest time zone abbreviations into tzname. . . + */ + if (sp) { + for (i = sp->timecnt - 1; stddst_mask && 0 <= i; i--) + stddst_mask = may_update_tzname_etc(stddst_mask, sp, sp->types[i]); + for (i = sp->typecnt - 1; stddst_mask && 0 <= i; i--) + stddst_mask = may_update_tzname_etc(stddst_mask, sp, i); + } +# if USG_COMPAT + daylight = stddst_mask >> 1 ^ 1; +# endif +} + +/* Replace bogus characters in time zone abbreviations. + Return 0 on success, an errno value if a time zone abbreviation is + too long. */ +static int +scrub_abbrs(struct state *sp) +{ + int i; + + /* Reject overlong abbreviations. */ + for (i = 0; i < sp->charcnt - (TZNAME_MAXIMUM + 1); ) { + int len = strlen(&sp->chars[i]); + if (TZNAME_MAXIMUM < len) + return EOVERFLOW; + i += len + 1; + } + + /* Replace bogus characters. */ + for (i = 0; i < sp->charcnt; ++i) + if (strchr(TZ_ABBR_CHAR_SET, sp->chars[i]) == NULL) + sp->chars[i] = TZ_ABBR_ERR_CHAR; + + return 0; +} + +#endif + +/* Input buffer for data read from a compiled tz file. */ +union input_buffer { + /* The first part of the buffer, interpreted as a header. */ + struct tzhead tzhead; + + /* The entire buffer. Ideally this would have no size limits; + the following should suffice for practical use. */ + char buf[2 * sizeof(struct tzhead) + 2 * sizeof(struct state) + + 4 * TZ_MAX_TIMES]; +}; + +/* TZDIR with a trailing '/' rather than a trailing '\0'. */ +static char const tzdirslash[sizeof TZDIR] = TZDIR "/"; + +/* Local storage needed for 'tzloadbody'. */ +union local_storage { + /* The results of analyzing the file's contents after it is opened. */ + struct file_analysis { + /* The input buffer. */ + union input_buffer u; + + /* A temporary state used for parsing a TZ string in the file. */ + struct state st; + } u; + + /* The name of the file to be opened. Ideally this would have no + size limits, to support arbitrarily long Zone names. + Limiting Zone names to 1024 bytes should suffice for practical use. + However, there is no need for this to be smaller than struct + file_analysis as that struct is allocated anyway, as the other + union member. */ + char fullname[max(sizeof(struct file_analysis), sizeof tzdirslash + 1024)]; +}; + +/* Load tz data from the file named NAME into *SP. Read extended + format if DOEXTEND. Use *LSP for temporary storage. Return 0 on + success, an errno value on failure. */ +static int +tzloadbody(char const *name, struct state *sp, bool doextend, + union local_storage *lsp) +{ + register int i; + register int fid; + register int stored; + register ssize_t nread; + register bool doaccess; + register union input_buffer *up = &lsp->u.u; + register int tzheadsize = sizeof(struct tzhead); + + sp->goback = sp->goahead = false; + + if (! name) { + name = TZDEFAULT; + if (! name) + return EINVAL; + } + + if (name[0] == ':') + ++name; +#ifdef SUPPRESS_TZDIR + /* Do not prepend TZDIR. This is intended for specialized + applications only, due to its security implications. */ + doaccess = true; +#else + doaccess = name[0] == '/'; +#endif + if (!doaccess) { + char const *dot; + if (sizeof lsp->fullname - sizeof tzdirslash <= strlen(name)) + return ENAMETOOLONG; + + /* Create a string "TZDIR/NAME". Using sprintf here + would pull in stdio (and would fail if the + resulting string length exceeded INT_MAX!). */ + memcpy(lsp->fullname, tzdirslash, sizeof tzdirslash); + strcpy(lsp->fullname + sizeof tzdirslash, name); + + /* Set doaccess if NAME contains a ".." file name + component, as such a name could read a file outside + the TZDIR virtual subtree. */ + for (dot = name; (dot = strchr(dot, '.')); dot++) + if ((dot == name || dot[-1] == '/') && dot[1] == '.' + && (dot[2] == '/' || !dot[2])) { + doaccess = true; + break; + } + + name = lsp->fullname; + } + if (doaccess && access(name, R_OK) != 0) + return errno; + fid = open(name, O_RDONLY | O_BINARY); + if (fid < 0) + return errno; + + nread = read(fid, up->buf, sizeof up->buf); + if (nread < tzheadsize) { + int err = nread < 0 ? errno : EINVAL; + close(fid); + return err; + } + if (close(fid) < 0) + return errno; + for (stored = 4; stored <= 8; stored *= 2) { + char version = up->tzhead.tzh_version[0]; + bool skip_datablock = stored == 4 && version; + int_fast32_t datablock_size; + int_fast32_t ttisstdcnt = detzcode(up->tzhead.tzh_ttisstdcnt); + int_fast32_t ttisutcnt = detzcode(up->tzhead.tzh_ttisutcnt); + int_fast64_t prevtr = -1; + int_fast32_t prevcorr; + int_fast32_t leapcnt = detzcode(up->tzhead.tzh_leapcnt); + int_fast32_t timecnt = detzcode(up->tzhead.tzh_timecnt); + int_fast32_t typecnt = detzcode(up->tzhead.tzh_typecnt); + int_fast32_t charcnt = detzcode(up->tzhead.tzh_charcnt); + char const *p = up->buf + tzheadsize; + /* Although tzfile(5) currently requires typecnt to be nonzero, + support future formats that may allow zero typecnt + in files that have a TZ string and no transitions. */ + if (! (0 <= leapcnt && leapcnt < TZ_MAX_LEAPS + && 0 <= typecnt && typecnt < TZ_MAX_TYPES + && 0 <= timecnt && timecnt < TZ_MAX_TIMES + && 0 <= charcnt && charcnt < TZ_MAX_CHARS + && 0 <= ttisstdcnt && ttisstdcnt < TZ_MAX_TYPES + && 0 <= ttisutcnt && ttisutcnt < TZ_MAX_TYPES)) + return EINVAL; + datablock_size + = (timecnt * stored /* ats */ + + timecnt /* types */ + + typecnt * 6 /* ttinfos */ + + charcnt /* chars */ + + leapcnt * (stored + 4) /* lsinfos */ + + ttisstdcnt /* ttisstds */ + + ttisutcnt); /* ttisuts */ + if (nread < tzheadsize + datablock_size) + return EINVAL; + if (skip_datablock) + p += datablock_size; + else { + if (! ((ttisstdcnt == typecnt || ttisstdcnt == 0) + && (ttisutcnt == typecnt || ttisutcnt == 0))) + return EINVAL; + + sp->leapcnt = leapcnt; + sp->timecnt = timecnt; + sp->typecnt = typecnt; + sp->charcnt = charcnt; + + /* Read transitions, discarding those out of time_t range. + But pretend the last transition before TIME_T_MIN + occurred at TIME_T_MIN. */ + timecnt = 0; + for (i = 0; i < sp->timecnt; ++i) { + int_fast64_t at + = stored == 4 ? detzcode(p) : detzcode64(p); + sp->types[i] = at <= TIME_T_MAX; + if (sp->types[i]) { + time_t attime + = ((TYPE_SIGNED(time_t) ? at < TIME_T_MIN : at < 0) + ? TIME_T_MIN : at); + if (timecnt && attime <= sp->ats[timecnt - 1]) { + if (attime < sp->ats[timecnt - 1]) + return EINVAL; + sp->types[i - 1] = 0; + timecnt--; + } + sp->ats[timecnt++] = attime; + } + p += stored; + } + + timecnt = 0; + for (i = 0; i < sp->timecnt; ++i) { + unsigned char typ = *p++; + if (sp->typecnt <= typ) + return EINVAL; + if (sp->types[i]) + sp->types[timecnt++] = typ; + } + sp->timecnt = timecnt; + for (i = 0; i < sp->typecnt; ++i) { + register struct ttinfo * ttisp; + unsigned char isdst, desigidx; + + ttisp = &sp->ttis[i]; + ttisp->tt_utoff = detzcode(p); + p += 4; + isdst = *p++; + if (! (isdst < 2)) + return EINVAL; + ttisp->tt_isdst = isdst; + desigidx = *p++; + if (! (desigidx < sp->charcnt)) + return EINVAL; + ttisp->tt_desigidx = desigidx; + } + for (i = 0; i < sp->charcnt; ++i) + sp->chars[i] = *p++; + /* Ensure '\0'-terminated, and make it safe to call + ttunspecified later. */ + memset(&sp->chars[i], 0, CHARS_EXTRA); + + /* Read leap seconds, discarding those out of time_t range. */ + leapcnt = 0; + for (i = 0; i < sp->leapcnt; ++i) { + int_fast64_t tr = stored == 4 ? detzcode(p) : detzcode64(p); + int_fast32_t corr = detzcode(p + stored); + p += stored + 4; + + /* Leap seconds cannot occur before the Epoch, + or out of order. */ + if (tr <= prevtr) + return EINVAL; + + /* To avoid other botches in this code, each leap second's + correction must differ from the previous one's by 1 + second or less, except that the first correction can be + any value; these requirements are more generous than + RFC 9636, to allow future RFC extensions. */ + if (! (i == 0 + || (prevcorr < corr + ? corr == prevcorr + 1 + : (corr == prevcorr + || corr == prevcorr - 1)))) + return EINVAL; + prevtr = tr; + prevcorr = corr; + + if (tr <= TIME_T_MAX) { + sp->lsis[leapcnt].ls_trans = tr; + sp->lsis[leapcnt].ls_corr = corr; + leapcnt++; + } + } + sp->leapcnt = leapcnt; + + for (i = 0; i < sp->typecnt; ++i) { + register struct ttinfo * ttisp; + + ttisp = &sp->ttis[i]; + if (ttisstdcnt == 0) + ttisp->tt_ttisstd = false; + else { + if (*p != true && *p != false) + return EINVAL; + ttisp->tt_ttisstd = *p++; + } + } + for (i = 0; i < sp->typecnt; ++i) { + register struct ttinfo * ttisp; + + ttisp = &sp->ttis[i]; + if (ttisutcnt == 0) + ttisp->tt_ttisut = false; + else { + if (*p != true && *p != false) + return EINVAL; + ttisp->tt_ttisut = *p++; + } + } + } + + nread -= p - up->buf; + memmove(up->buf, p, nread); + + /* If this is an old file, we're done. */ + if (!version) + break; + } + if (doextend && nread > 2 && + up->buf[0] == '\n' && up->buf[nread - 1] == '\n' && + sp->typecnt + 2 <= TZ_MAX_TYPES) { + struct state *ts = &lsp->u.st; + + up->buf[nread - 1] = '\0'; + if (tzparse(&up->buf[1], ts, sp)) { + + /* Attempt to reuse existing abbreviations. + Without this, America/Anchorage would be right on + the edge after 2037 when TZ_MAX_CHARS is 50, as + sp->charcnt equals 40 (for LMT AST AWT APT AHST + AHDT YST AKDT AKST) and ts->charcnt equals 10 + (for AKST AKDT). Reusing means sp->charcnt can + stay 40 in this example. */ + int gotabbr = 0; + int charcnt = sp->charcnt; + for (i = 0; i < ts->typecnt; i++) { + char *tsabbr = ts->chars + ts->ttis[i].tt_desigidx; + int j; + for (j = 0; j < charcnt; j++) + if (strcmp(sp->chars + j, tsabbr) == 0) { + ts->ttis[i].tt_desigidx = j; + gotabbr++; + break; + } + if (! (j < charcnt)) { + int tsabbrlen = strlen(tsabbr); + if (j + tsabbrlen < TZ_MAX_CHARS) { + strcpy(sp->chars + j, tsabbr); + charcnt = j + tsabbrlen + 1; + ts->ttis[i].tt_desigidx = j; + gotabbr++; + } + } + } + if (gotabbr == ts->typecnt) { + sp->charcnt = charcnt; + + /* Ignore any trailing, no-op transitions generated + by zic as they don't help here and can run afoul + of bugs in zic 2016j or earlier. */ + while (1 < sp->timecnt + && (sp->types[sp->timecnt - 1] + == sp->types[sp->timecnt - 2])) + sp->timecnt--; + + sp->goahead = ts->goahead; + + for (i = 0; i < ts->timecnt; i++) { + time_t t = ts->ats[i]; + if (increment_overflow_time(&t, leapcorr(sp, t)) + || (0 < sp->timecnt + && t <= sp->ats[sp->timecnt - 1])) + continue; + if (TZ_MAX_TIMES <= sp->timecnt) { + sp->goahead = false; + break; + } + sp->ats[sp->timecnt] = t; + sp->types[sp->timecnt] = (sp->typecnt + + ts->types[i]); + sp->timecnt++; + } + for (i = 0; i < ts->typecnt; i++) + sp->ttis[sp->typecnt++] = ts->ttis[i]; + } + } + } + if (sp->typecnt == 0) + return EINVAL; + + return 0; +} + +/* Load tz data from the file named NAME into *SP. Read extended + format if DOEXTEND. Return 0 on success, an errno value on failure. */ +static int +tzload(char const *name, struct state *sp, bool doextend) +{ +#ifdef ALL_STATE + union local_storage *lsp = malloc(sizeof *lsp); + if (!lsp) { + return HAVE_MALLOC_ERRNO ? errno : ENOMEM; + } else { + int err = tzloadbody(name, sp, doextend, lsp); + free(lsp); + return err; + } +#else + union local_storage ls; + return tzloadbody(name, sp, doextend, &ls); +#endif +} + +static const int mon_lengths[2][MONSPERYEAR] = { + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } +}; + +static const int year_lengths[2] = { + DAYSPERNYEAR, DAYSPERLYEAR +}; + +/* Is C an ASCII digit? */ +static bool +is_digit(char c) +{ + return '0' <= c && c <= '9'; +} + +/* +** Given a pointer into a timezone string, scan until a character that is not +** a valid character in a time zone abbreviation is found. +** Return a pointer to that character. +*/ + +ATTRIBUTE_PURE_114833 static const char * +getzname(register const char *strp) +{ + register char c; + + while ((c = *strp) != '\0' && !is_digit(c) && c != ',' && c != '-' && + c != '+') + ++strp; + return strp; +} + +/* +** Given a pointer into an extended timezone string, scan until the ending +** delimiter of the time zone abbreviation is located. +** Return a pointer to the delimiter. +** +** As with getzname above, the legal character set is actually quite +** restricted, with other characters producing undefined results. +** We don't do any checking here; checking is done later in common-case code. +*/ + +ATTRIBUTE_PURE_114833 static const char * +getqzname(register const char *strp, const int delim) +{ + register int c; + + while ((c = *strp) != '\0' && c != delim) + ++strp; + return strp; +} + +/* +** Given a pointer into a timezone string, extract a number from that string. +** Check that the number is within a specified range; if it is not, return +** NULL. +** Otherwise, return a pointer to the first character not part of the number. +*/ + +static const char * +getnum(register const char *strp, int *const nump, const int min, const int max) +{ + register char c; + register int num; + + if (strp == NULL || !is_digit(c = *strp)) + return NULL; + num = 0; + do { + num = num * 10 + (c - '0'); + if (num > max) + return NULL; /* illegal value */ + c = *++strp; + } while (is_digit(c)); + if (num < min) + return NULL; /* illegal value */ + *nump = num; + return strp; +} + +/* +** Given a pointer into a timezone string, extract a number of seconds, +** in hh[:mm[:ss]] form, from the string. +** If any error occurs, return NULL. +** Otherwise, return a pointer to the first character not part of the number +** of seconds. +*/ + +static const char * +getsecs(register const char *strp, int_fast32_t *const secsp) +{ + int num; + int_fast32_t secsperhour = SECSPERHOUR; + + /* + ** 'HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-POSIX rules like + ** "M10.4.6/26", which does not conform to POSIX, + ** but which specifies the equivalent of + ** "02:00 on the first Sunday on or after 23 Oct". + */ + strp = getnum(strp, &num, 0, HOURSPERDAY * DAYSPERWEEK - 1); + if (strp == NULL) + return NULL; + *secsp = num * secsperhour; + if (*strp == ':') { + ++strp; + strp = getnum(strp, &num, 0, MINSPERHOUR - 1); + if (strp == NULL) + return NULL; + *secsp += num * SECSPERMIN; + if (*strp == ':') { + ++strp; + /* 'SECSPERMIN' allows for leap seconds. */ + strp = getnum(strp, &num, 0, SECSPERMIN); + if (strp == NULL) + return NULL; + *secsp += num; + } + } + return strp; +} + +/* +** Given a pointer into a timezone string, extract an offset, in +** [+-]hh[:mm[:ss]] form, from the string. +** If any error occurs, return NULL. +** Otherwise, return a pointer to the first character not part of the time. +*/ + +static const char * +getoffset(register const char *strp, int_fast32_t *const offsetp) +{ + register bool neg = false; + + if (*strp == '-') { + neg = true; + ++strp; + } else if (*strp == '+') + ++strp; + strp = getsecs(strp, offsetp); + if (strp == NULL) + return NULL; /* illegal time */ + if (neg) + *offsetp = -*offsetp; + return strp; +} + +/* +** Given a pointer into a timezone string, extract a rule in the form +** date[/time]. See POSIX Base Definitions section 8.3 variable TZ +** for the format of "date" and "time". +** If a valid rule is not found, return NULL. +** Otherwise, return a pointer to the first character not part of the rule. +*/ + +static const char * +getrule(const char *strp, register struct rule *const rulep) +{ + if (*strp == 'J') { + /* + ** Julian day. + */ + rulep->r_type = JULIAN_DAY; + ++strp; + strp = getnum(strp, &rulep->r_day, 1, DAYSPERNYEAR); + } else if (*strp == 'M') { + /* + ** Month, week, day. + */ + rulep->r_type = MONTH_NTH_DAY_OF_WEEK; + ++strp; + strp = getnum(strp, &rulep->r_mon, 1, MONSPERYEAR); + if (strp == NULL) + return NULL; + if (*strp++ != '.') + return NULL; + strp = getnum(strp, &rulep->r_week, 1, 5); + if (strp == NULL) + return NULL; + if (*strp++ != '.') + return NULL; + strp = getnum(strp, &rulep->r_day, 0, DAYSPERWEEK - 1); + } else if (is_digit(*strp)) { + /* + ** Day of year. + */ + rulep->r_type = DAY_OF_YEAR; + strp = getnum(strp, &rulep->r_day, 0, DAYSPERLYEAR - 1); + } else return NULL; /* invalid format */ + if (strp == NULL) + return NULL; + if (*strp == '/') { + /* + ** Time specified. + */ + ++strp; + strp = getoffset(strp, &rulep->r_time); + } else rulep->r_time = 2 * SECSPERHOUR; /* default = 2:00:00 */ + return strp; +} + +/* +** Given a year, a rule, and the offset from UT at the time that rule takes +** effect, calculate the year-relative time that rule takes effect. +*/ + +static int_fast32_t +transtime(const int year, register const struct rule *const rulep, + const int_fast32_t offset) +{ + register bool leapyear; + register int_fast32_t value; + register int i; + int d, m1, yy0, yy1, yy2, dow; + + leapyear = isleap(year); + switch (rulep->r_type) { + + case JULIAN_DAY: + /* + ** Jn - Julian day, 1 == January 1, 60 == March 1 even in leap + ** years. + ** In non-leap years, or if the day number is 59 or less, just + ** add SECSPERDAY times the day number-1 to the time of + ** January 1, midnight, to get the day. + */ + value = (rulep->r_day - 1) * SECSPERDAY; + if (leapyear && rulep->r_day >= 60) + value += SECSPERDAY; + break; + + case DAY_OF_YEAR: + /* + ** n - day of year. + ** Just add SECSPERDAY times the day number to the time of + ** January 1, midnight, to get the day. + */ + value = rulep->r_day * SECSPERDAY; + break; + + case MONTH_NTH_DAY_OF_WEEK: + /* + ** Mm.n.d - nth "dth day" of month m. + */ + + /* + ** Use Zeller's Congruence to get day-of-week of first day of + ** month. + */ + m1 = (rulep->r_mon + 9) % 12 + 1; + yy0 = (rulep->r_mon <= 2) ? (year - 1) : year; + yy1 = yy0 / 100; + yy2 = yy0 % 100; + dow = ((26 * m1 - 2) / 10 + + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7; + if (dow < 0) + dow += DAYSPERWEEK; + + /* + ** "dow" is the day-of-week of the first day of the month. Get + ** the day-of-month (zero-origin) of the first "dow" day of the + ** month. + */ + d = rulep->r_day - dow; + if (d < 0) + d += DAYSPERWEEK; + for (i = 1; i < rulep->r_week; ++i) { + if (d + DAYSPERWEEK >= + mon_lengths[leapyear][rulep->r_mon - 1]) + break; + d += DAYSPERWEEK; + } + + /* + ** "d" is the day-of-month (zero-origin) of the day we want. + */ + value = d * SECSPERDAY; + for (i = 0; i < rulep->r_mon - 1; ++i) + value += mon_lengths[leapyear][i] * SECSPERDAY; + break; + + default: unreachable(); + } + + /* + ** "value" is the year-relative time of 00:00:00 UT on the day in + ** question. To get the year-relative time of the specified local + ** time on that day, add the transition time and the current offset + ** from UT. + */ + return value + rulep->r_time + offset; +} + +/* +** Given a POSIX.1 proleptic TZ string, fill in the rule tables as +** appropriate. +*/ + +static bool +tzparse(const char *name, struct state *sp, struct state const *basep) +{ + const char * stdname; + const char * dstname; + int_fast32_t stdoffset; + int_fast32_t dstoffset; + register char * cp; + register bool load_ok; + ptrdiff_t stdlen, dstlen, charcnt; + time_t atlo = TIME_T_MIN, leaplo = TIME_T_MIN; + + stdname = name; + if (*name == '<') { + name++; + stdname = name; + name = getqzname(name, '>'); + if (*name != '>') + return false; + stdlen = name - stdname; + name++; + } else { + name = getzname(name); + stdlen = name - stdname; + } + if (! (0 < stdlen && stdlen <= TZNAME_MAXIMUM)) + return false; + name = getoffset(name, &stdoffset); + if (name == NULL) + return false; + charcnt = stdlen + 1; + if (basep) { + if (0 < basep->timecnt) + atlo = basep->ats[basep->timecnt - 1]; + load_ok = false; + sp->leapcnt = basep->leapcnt; + memcpy(sp->lsis, basep->lsis, sp->leapcnt * sizeof *sp->lsis); + } else { + load_ok = tzload(TZDEFRULES, sp, false) == 0; + if (!load_ok) + sp->leapcnt = 0; /* So, we're off a little. */ + } + if (0 < sp->leapcnt) + leaplo = sp->lsis[sp->leapcnt - 1].ls_trans; + sp->goback = sp->goahead = false; + if (*name != '\0') { + if (*name == '<') { + dstname = ++name; + name = getqzname(name, '>'); + if (*name != '>') + return false; + dstlen = name - dstname; + name++; + } else { + dstname = name; + name = getzname(name); + dstlen = name - dstname; /* length of DST abbr. */ + } + if (! (0 < dstlen && dstlen <= TZNAME_MAXIMUM)) + return false; + charcnt += dstlen + 1; + if (*name != '\0' && *name != ',' && *name != ';') { + name = getoffset(name, &dstoffset); + if (name == NULL) + return false; + } else dstoffset = stdoffset - SECSPERHOUR; + if (*name == '\0' && !load_ok) + name = TZDEFRULESTRING; + if (*name == ',' || *name == ';') { + struct rule start; + struct rule end; + register int year; + register int timecnt; + time_t janfirst; + int_fast32_t janoffset = 0; + int yearbeg, yearlim; + + ++name; + if ((name = getrule(name, &start)) == NULL) + return false; + if (*name++ != ',') + return false; + if ((name = getrule(name, &end)) == NULL) + return false; + if (*name != '\0') + return false; + sp->typecnt = 2; /* standard time and DST */ + /* + ** Two transitions per year, from EPOCH_YEAR forward. + */ + init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); + init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1); + timecnt = 0; + janfirst = 0; + yearbeg = EPOCH_YEAR; + + do { + int_fast32_t yearsecs + = year_lengths[isleap(yearbeg - 1)] * SECSPERDAY; + time_t janfirst1 = janfirst; + yearbeg--; + if (increment_overflow_time(&janfirst1, -yearsecs)) { + janoffset = -yearsecs; + break; + } + janfirst = janfirst1; + } while (atlo < janfirst + && EPOCH_YEAR - YEARSPERREPEAT / 2 < yearbeg); + + while (true) { + int_fast32_t yearsecs + = year_lengths[isleap(yearbeg)] * SECSPERDAY; + int yearbeg1 = yearbeg; + time_t janfirst1 = janfirst; + if (increment_overflow_time(&janfirst1, yearsecs) + || increment_overflow(&yearbeg1, 1) + || atlo <= janfirst1) + break; + yearbeg = yearbeg1; + janfirst = janfirst1; + } + + yearlim = yearbeg; + if (increment_overflow(&yearlim, years_of_observations)) + yearlim = INT_MAX; + for (year = yearbeg; year < yearlim; year++) { + int_fast32_t + starttime = transtime(year, &start, stdoffset), + endtime = transtime(year, &end, dstoffset); + int_fast32_t + yearsecs = (year_lengths[isleap(year)] + * SECSPERDAY); + bool reversed = endtime < starttime; + if (reversed) { + int_fast32_t swap = starttime; + starttime = endtime; + endtime = swap; + } + if (reversed + || (starttime < endtime + && endtime - starttime < yearsecs)) { + if (TZ_MAX_TIMES - 2 < timecnt) + break; + sp->ats[timecnt] = janfirst; + if (! increment_overflow_time + (&sp->ats[timecnt], + janoffset + starttime) + && atlo <= sp->ats[timecnt]) + sp->types[timecnt++] = !reversed; + sp->ats[timecnt] = janfirst; + if (! increment_overflow_time + (&sp->ats[timecnt], + janoffset + endtime) + && atlo <= sp->ats[timecnt]) { + sp->types[timecnt++] = reversed; + } + } + if (endtime < leaplo) { + yearlim = year; + if (increment_overflow(&yearlim, + years_of_observations)) + yearlim = INT_MAX; + } + if (increment_overflow_time + (&janfirst, janoffset + yearsecs)) + break; + janoffset = 0; + } + sp->timecnt = timecnt; + if (! timecnt) { + sp->ttis[0] = sp->ttis[1]; + sp->typecnt = 1; /* Perpetual DST. */ + } else if (years_of_observations <= year - yearbeg) + sp->goback = sp->goahead = true; + } else { + register int_fast32_t theirstdoffset; + register int_fast32_t theirdstoffset; + register int_fast32_t theiroffset; + register bool isdst; + register int i; + register int j; + + if (*name != '\0') + return false; + /* + ** Initial values of theirstdoffset and theirdstoffset. + */ + theirstdoffset = 0; + for (i = 0; i < sp->timecnt; ++i) { + j = sp->types[i]; + if (!sp->ttis[j].tt_isdst) { + theirstdoffset = + - sp->ttis[j].tt_utoff; + break; + } + } + theirdstoffset = 0; + for (i = 0; i < sp->timecnt; ++i) { + j = sp->types[i]; + if (sp->ttis[j].tt_isdst) { + theirdstoffset = + - sp->ttis[j].tt_utoff; + break; + } + } + /* + ** Initially we're assumed to be in standard time. + */ + isdst = false; + /* + ** Now juggle transition times and types + ** tracking offsets as you do. + */ + for (i = 0; i < sp->timecnt; ++i) { + j = sp->types[i]; + sp->types[i] = sp->ttis[j].tt_isdst; + if (sp->ttis[j].tt_ttisut) { + /* No adjustment to transition time */ + } else { + /* + ** If daylight saving time is in + ** effect, and the transition time was + ** not specified as standard time, add + ** the daylight saving time offset to + ** the transition time; otherwise, add + ** the standard time offset to the + ** transition time. + */ + /* + ** Transitions from DST to DDST + ** will effectively disappear since + ** proleptic TZ strings have only one + ** DST offset. + */ + if (isdst && !sp->ttis[j].tt_ttisstd) { + sp->ats[i] += dstoffset - + theirdstoffset; + } else { + sp->ats[i] += stdoffset - + theirstdoffset; + } + } + theiroffset = -sp->ttis[j].tt_utoff; + if (sp->ttis[j].tt_isdst) + theirdstoffset = theiroffset; + else theirstdoffset = theiroffset; + } + /* + ** Finally, fill in ttis. + */ + init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); + init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1); + sp->typecnt = 2; + } + } else { + dstlen = 0; + sp->typecnt = 1; /* only standard time */ + sp->timecnt = 0; + init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); + } + sp->charcnt = charcnt; + cp = sp->chars; + memcpy(cp, stdname, stdlen); + cp += stdlen; + *cp++ = '\0'; + if (dstlen != 0) { + memcpy(cp, dstname, dstlen); + *(cp + dstlen) = '\0'; + } + return true; +} + +static void +gmtload(struct state *const sp) +{ + if (tzload(etc_utc, sp, true) != 0) + tzparse("UTC0", sp, NULL); +} + +#if !USE_TIMEX_T || !defined TM_GMTOFF + +/* Initialize *SP to a value appropriate for the TZ setting NAME. + Return 0 on success, an errno value on failure. */ +static int +zoneinit(struct state *sp, char const *name) +{ + if (name && ! name[0]) { + /* + ** User wants it fast rather than right. + */ + sp->leapcnt = 0; /* so, we're off a little */ + sp->timecnt = 0; + sp->typecnt = 0; + sp->charcnt = 0; + sp->goback = sp->goahead = false; + init_ttinfo(&sp->ttis[0], 0, false, 0); + strcpy(sp->chars, utc); + return 0; + } else { + int err = tzload(name, sp, true); + if (err != 0 && name && name[0] != ':' && tzparse(name, sp, NULL)) + err = 0; + if (err == 0) + err = scrub_abbrs(sp); + return err; + } +} + +static void +tzset_unlocked(void) +{ + char const *name = getenv("TZ"); + struct state *sp = lclptr; + int lcl = name ? strlen(name) < sizeof lcl_TZname : -1; + if (lcl < 0 + ? lcl_is_set < 0 + : 0 < lcl_is_set && strcmp(lcl_TZname, name) == 0) + return; +# ifdef ALL_STATE + if (! sp) + lclptr = sp = malloc(sizeof *lclptr); +# endif + if (sp) { + if (zoneinit(sp, name) != 0) + zoneinit(sp, ""); + if (0 < lcl) + strcpy(lcl_TZname, name); + } + settzname(); + lcl_is_set = lcl; +} + +#endif + +#if !USE_TIMEX_T +void +tzset(void) +{ + if (lock() != 0) + return; + tzset_unlocked(); + unlock(); +} +#endif + +static void +gmtcheck(void) +{ + static bool gmt_is_set; + if (lock() != 0) + return; + if (! gmt_is_set) { +#ifdef ALL_STATE + gmtptr = malloc(sizeof *gmtptr); +#endif + if (gmtptr) + gmtload(gmtptr); + gmt_is_set = true; + } + unlock(); +} + +#if NETBSD_INSPIRED && !USE_TIMEX_T + +timezone_t +tzalloc(char const *name) +{ + timezone_t sp = malloc(sizeof *sp); + if (sp) { + int err = zoneinit(sp, name); + if (err != 0) { + free(sp); + errno = err; + return NULL; + } + } else if (!HAVE_MALLOC_ERRNO) + errno = ENOMEM; + return sp; +} + +void +tzfree(timezone_t sp) +{ + free(sp); +} + +/* +** NetBSD 6.1.4 has ctime_rz, but omit it because C23 deprecates ctime and +** POSIX.1-2024 removes ctime_r. Both have potential security problems that +** ctime_rz would share. Callers can instead use localtime_rz + strftime. +** +** NetBSD 6.1.4 has tzgetname, but omit it because it doesn't work +** in zones with three or more time zone abbreviations. +** Callers can instead use localtime_rz + strftime. +*/ + +#endif + +#if !USE_TIMEX_T || !defined TM_GMTOFF + +/* +** The easy way to behave "as if no library function calls" localtime +** is to not call it, so we drop its guts into "localsub", which can be +** freely called. (And no, the PANS doesn't require the above behavior, +** but it *is* desirable.) +** +** If successful and SETNAME is nonzero, +** set the applicable parts of tzname, timezone and altzone; +** however, it's OK to omit this step for proleptic TZ strings +** since in that case tzset should have already done this step correctly. +** SETNAME's type is int_fast32_t for compatibility with gmtsub, +** but it is actually a boolean and its value should be 0 or 1. +*/ + +/*ARGSUSED*/ +static struct tm * +localsub(struct state const *sp, time_t const *timep, int_fast32_t setname, + struct tm *const tmp) +{ + register const struct ttinfo * ttisp; + register int i; + register struct tm * result; + const time_t t = *timep; + + if (sp == NULL) { + /* Don't bother to set tzname etc.; tzset has already done it. */ + return gmtsub(gmtptr, timep, 0, tmp); + } + if ((sp->goback && t < sp->ats[0]) || + (sp->goahead && t > sp->ats[sp->timecnt - 1])) { + time_t newt; + register time_t seconds; + register time_t years; + + if (t < sp->ats[0]) + seconds = sp->ats[0] - t; + else seconds = t - sp->ats[sp->timecnt - 1]; + --seconds; + + /* Beware integer overflow, as SECONDS might + be close to the maximum time_t. */ + years = seconds / SECSPERREPEAT * YEARSPERREPEAT; + seconds = years * AVGSECSPERYEAR; + years += YEARSPERREPEAT; + if (t < sp->ats[0]) + newt = t + seconds + SECSPERREPEAT; + else + newt = t - seconds - SECSPERREPEAT; + + if (newt < sp->ats[0] || + newt > sp->ats[sp->timecnt - 1]) + return NULL; /* "cannot happen" */ + result = localsub(sp, &newt, setname, tmp); + if (result) { +# if defined ckd_add && defined ckd_sub + if (t < sp->ats[0] + ? ckd_sub(&result->tm_year, + result->tm_year, years) + : ckd_add(&result->tm_year, + result->tm_year, years)) + return NULL; +# else + register int_fast64_t newy; + + newy = result->tm_year; + if (t < sp->ats[0]) + newy -= years; + else newy += years; + if (! (INT_MIN <= newy && newy <= INT_MAX)) + return NULL; + result->tm_year = newy; +# endif + } + return result; + } + if (sp->timecnt == 0 || t < sp->ats[0]) { + i = 0; + } else { + register int lo = 1; + register int hi = sp->timecnt; + + while (lo < hi) { + register int mid = (lo + hi) >> 1; + + if (t < sp->ats[mid]) + hi = mid; + else lo = mid + 1; + } + i = sp->types[lo - 1]; + } + ttisp = &sp->ttis[i]; + /* + ** To get (wrong) behavior that's compatible with System V Release 2.0 + ** you'd replace the statement below with + ** t += ttisp->tt_utoff; + ** timesub(&t, 0L, sp, tmp); + */ + result = timesub(&t, ttisp->tt_utoff, sp, tmp); + if (result) { + result->tm_isdst = ttisp->tt_isdst; +# ifdef TM_ZONE + result->TM_ZONE = (char *) &sp->chars[ttisp->tt_desigidx]; +# endif + if (setname) + update_tzname_etc(sp, ttisp); + } + return result; +} +#endif + +#if !USE_TIMEX_T + +# if NETBSD_INSPIRED +struct tm * +localtime_rz(struct state *restrict sp, time_t const *restrict timep, + struct tm *restrict tmp) +{ + return localsub(sp, timep, 0, tmp); +} +# endif + +static struct tm * +localtime_tzset(time_t const *timep, struct tm *tmp, bool setname) +{ + int err = lock(); + if (err) { + errno = err; + return NULL; + } + if (setname || !lcl_is_set) + tzset_unlocked(); + tmp = localsub(lclptr, timep, setname, tmp); + unlock(); + return tmp; +} + +struct tm * +localtime(const time_t *timep) +{ +# if !SUPPORT_C89 + static struct tm tm; +# endif + return localtime_tzset(timep, &tm, true); +} + +struct tm * +localtime_r(const time_t *restrict timep, struct tm *restrict tmp) +{ + return localtime_tzset(timep, tmp, false); +} +#endif + +/* +** gmtsub is to gmtime as localsub is to localtime. +*/ + +static struct tm * +gmtsub(ATTRIBUTE_MAYBE_UNUSED struct state const *sp, time_t const *timep, + int_fast32_t offset, struct tm *tmp) +{ + register struct tm * result; + + result = timesub(timep, offset, gmtptr, tmp); +#ifdef TM_ZONE + /* + ** Could get fancy here and deliver something such as + ** "+xx" or "-xx" if offset is non-zero, + ** but this is no time for a treasure hunt. + */ + tmp->TM_ZONE = ((char *) + (offset ? wildabbr : gmtptr ? gmtptr->chars : utc)); +#endif /* defined TM_ZONE */ + return result; +} + +#if !USE_TIMEX_T + +/* +* Re-entrant version of gmtime. +*/ + +struct tm * +gmtime_r(time_t const *restrict timep, struct tm *restrict tmp) +{ + gmtcheck(); + return gmtsub(gmtptr, timep, 0, tmp); +} + +struct tm * +gmtime(const time_t *timep) +{ +# if !SUPPORT_C89 + static struct tm tm; +# endif + return gmtime_r(timep, &tm); +} + +# if STD_INSPIRED + +/* This function is obsolescent and may disappear in future releases. + Callers can instead use localtime_rz with a fixed-offset zone. */ + +struct tm * +offtime(const time_t *timep, long offset) +{ + gmtcheck(); + +# if !SUPPORT_C89 + static struct tm tm; +# endif + return gmtsub(gmtptr, timep, offset, &tm); +} + +# endif +#endif + +/* +** Return the number of leap years through the end of the given year +** where, to make the math easy, the answer for year zero is defined as zero. +*/ + +static time_t +leaps_thru_end_of_nonneg(time_t y) +{ + return y / 4 - y / 100 + y / 400; +} + +static time_t +leaps_thru_end_of(time_t y) +{ + return (y < 0 + ? -1 - leaps_thru_end_of_nonneg(-1 - y) + : leaps_thru_end_of_nonneg(y)); +} + +static struct tm * +timesub(const time_t *timep, int_fast32_t offset, + const struct state *sp, struct tm *tmp) +{ + register const struct lsinfo * lp; + register time_t tdays; + register const int * ip; + register int_fast32_t corr; + register int i; + int_fast32_t idays, rem, dayoff, dayrem; + time_t y; + + /* If less than SECSPERMIN, the number of seconds since the + most recent positive leap second; otherwise, do not add 1 + to localtime tm_sec because of leap seconds. */ + time_t secs_since_posleap = SECSPERMIN; + + corr = 0; + i = (sp == NULL) ? 0 : sp->leapcnt; + while (--i >= 0) { + lp = &sp->lsis[i]; + if (*timep >= lp->ls_trans) { + corr = lp->ls_corr; + if ((i == 0 ? 0 : lp[-1].ls_corr) < corr) + secs_since_posleap = *timep - lp->ls_trans; + break; + } + } + + /* Calculate the year, avoiding integer overflow even if + time_t is unsigned. */ + tdays = *timep / SECSPERDAY; + rem = *timep % SECSPERDAY; + rem += offset % SECSPERDAY - corr % SECSPERDAY + 3 * SECSPERDAY; + dayoff = offset / SECSPERDAY - corr / SECSPERDAY + rem / SECSPERDAY - 3; + rem %= SECSPERDAY; + /* y = (EPOCH_YEAR + + floor((tdays + dayoff) / DAYSPERREPEAT) * YEARSPERREPEAT), + sans overflow. But calculate against 1570 (EPOCH_YEAR - + YEARSPERREPEAT) instead of against 1970 so that things work + for localtime values before 1970 when time_t is unsigned. */ + dayrem = tdays % DAYSPERREPEAT; + dayrem += dayoff % DAYSPERREPEAT; + y = (EPOCH_YEAR - YEARSPERREPEAT + + ((1 + dayoff / DAYSPERREPEAT + dayrem / DAYSPERREPEAT + - ((dayrem % DAYSPERREPEAT) < 0) + + tdays / DAYSPERREPEAT) + * YEARSPERREPEAT)); + /* idays = (tdays + dayoff) mod DAYSPERREPEAT, sans overflow. */ + idays = tdays % DAYSPERREPEAT; + idays += dayoff % DAYSPERREPEAT + 2 * DAYSPERREPEAT; + idays %= DAYSPERREPEAT; + /* Increase Y and decrease IDAYS until IDAYS is in range for Y. */ + while (year_lengths[isleap(y)] <= idays) { + int tdelta = idays / DAYSPERLYEAR; + int_fast32_t ydelta = tdelta + !tdelta; + time_t newy = y + ydelta; + register int leapdays; + leapdays = leaps_thru_end_of(newy - 1) - + leaps_thru_end_of(y - 1); + idays -= ydelta * DAYSPERNYEAR; + idays -= leapdays; + y = newy; + } + +#ifdef ckd_add + if (ckd_add(&tmp->tm_year, y, -TM_YEAR_BASE)) { + errno = EOVERFLOW; + return NULL; + } +#else + if (!TYPE_SIGNED(time_t) && y < TM_YEAR_BASE) { + int signed_y = y; + tmp->tm_year = signed_y - TM_YEAR_BASE; + } else if ((!TYPE_SIGNED(time_t) || INT_MIN + TM_YEAR_BASE <= y) + && y - TM_YEAR_BASE <= INT_MAX) + tmp->tm_year = y - TM_YEAR_BASE; + else { + errno = EOVERFLOW; + return NULL; + } +#endif + tmp->tm_yday = idays; + /* + ** The "extra" mods below avoid overflow problems. + */ + tmp->tm_wday = (TM_WDAY_BASE + + ((tmp->tm_year % DAYSPERWEEK) + * (DAYSPERNYEAR % DAYSPERWEEK)) + + leaps_thru_end_of(y - 1) + - leaps_thru_end_of(TM_YEAR_BASE - 1) + + idays); + tmp->tm_wday %= DAYSPERWEEK; + if (tmp->tm_wday < 0) + tmp->tm_wday += DAYSPERWEEK; + tmp->tm_hour = rem / SECSPERHOUR; + rem %= SECSPERHOUR; + tmp->tm_min = rem / SECSPERMIN; + tmp->tm_sec = rem % SECSPERMIN; + + /* Use "... ??:??:60" at the end of the localtime minute containing + the second just before the positive leap second. */ + tmp->tm_sec += secs_since_posleap <= tmp->tm_sec; + + ip = mon_lengths[isleap(y)]; + for (tmp->tm_mon = 0; idays >= ip[tmp->tm_mon]; ++(tmp->tm_mon)) + idays -= ip[tmp->tm_mon]; + tmp->tm_mday = idays + 1; + tmp->tm_isdst = 0; +#ifdef TM_GMTOFF + tmp->TM_GMTOFF = offset; +#endif /* defined TM_GMTOFF */ + return tmp; +} + +/* +** Adapted from code provided by Robert Elz, who writes: +** The "best" way to do mktime I think is based on an idea of Bob +** Kridle's (so its said...) from a long time ago. +** It does a binary search of the time_t space. Since time_t's are +** just 32 bits, its a max of 32 iterations (even at 64 bits it +** would still be very reasonable). +*/ + +#ifndef WRONG +# define WRONG (-1) +#endif /* !defined WRONG */ + +/* +** Normalize logic courtesy Paul Eggert. +*/ + +static bool +increment_overflow(int *ip, int j) +{ +#ifdef ckd_add + return ckd_add(ip, *ip, j); +#else + register int const i = *ip; + + /* + ** If i >= 0 there can only be overflow if i + j > INT_MAX + ** or if j > INT_MAX - i; given i >= 0, INT_MAX - i cannot overflow. + ** If i < 0 there can only be overflow if i + j < INT_MIN + ** or if j < INT_MIN - i; given i < 0, INT_MIN - i cannot overflow. + */ + if ((i >= 0) ? (j > INT_MAX - i) : (j < INT_MIN - i)) + return true; + *ip += j; + return false; +#endif +} + +static bool +increment_overflow_time_iinntt(time_t *tp, iinntt j) +{ +#ifdef ckd_add + return ckd_add(tp, *tp, j); +#else + if (j < 0 + ? (TYPE_SIGNED(time_t) ? *tp < TIME_T_MIN - j : *tp <= -1 - j) + : TIME_T_MAX - j < *tp) + return true; + *tp += j; + return false; +#endif +} + +static bool +increment_overflow_time(time_t *tp, int_fast32_t j) +{ +#ifdef ckd_add + return ckd_add(tp, *tp, j); +#else + /* + ** This is like + ** 'if (! (TIME_T_MIN <= *tp + j && *tp + j <= TIME_T_MAX)) ...', + ** except that it does the right thing even if *tp + j would overflow. + */ + if (! (j < 0 + ? (TYPE_SIGNED(time_t) ? TIME_T_MIN - j <= *tp : -1 - j < *tp) + : *tp <= TIME_T_MAX - j)) + return true; + *tp += j; + return false; +#endif +} + +static int +tmcomp(register const struct tm *const atmp, + register const struct tm *const btmp) +{ + register int result; + + if (atmp->tm_year != btmp->tm_year) + return atmp->tm_year < btmp->tm_year ? -1 : 1; + if ((result = (atmp->tm_mon - btmp->tm_mon)) == 0 && + (result = (atmp->tm_mday - btmp->tm_mday)) == 0 && + (result = (atmp->tm_hour - btmp->tm_hour)) == 0 && + (result = (atmp->tm_min - btmp->tm_min)) == 0) + result = atmp->tm_sec - btmp->tm_sec; + return result; +} + +/* Copy to *DEST from *SRC. Copy only the members needed for mktime, + as other members might not be initialized. */ +static void +mktmcpy(struct tm *dest, struct tm const *src) +{ + dest->tm_sec = src->tm_sec; + dest->tm_min = src->tm_min; + dest->tm_hour = src->tm_hour; + dest->tm_mday = src->tm_mday; + dest->tm_mon = src->tm_mon; + dest->tm_year = src->tm_year; + dest->tm_isdst = src->tm_isdst; +#if defined TM_GMTOFF && ! UNINIT_TRAP + dest->TM_GMTOFF = src->TM_GMTOFF; +#endif +} + +static time_t +time2sub(struct tm *const tmp, + struct tm *(*funcp)(struct state const *, time_t const *, + int_fast32_t, struct tm *), + struct state const *sp, + const int_fast32_t offset, + bool *okayp, + bool do_norm_secs) +{ + register int dir; + register int i, j; + register time_t lo; + register time_t hi; + iinntt y, mday, hour, min, saved_seconds; + time_t newt; + time_t t; + struct tm yourtm, mytm; + + *okayp = false; + mktmcpy(&yourtm, tmp); + + min = yourtm.tm_min; + if (do_norm_secs) { + min += yourtm.tm_sec / SECSPERMIN; + yourtm.tm_sec %= SECSPERMIN; + if (yourtm.tm_sec < 0) { + yourtm.tm_sec += SECSPERMIN; + min--; + } + } + + hour = yourtm.tm_hour; + hour += min / MINSPERHOUR; + yourtm.tm_min = min % MINSPERHOUR; + if (yourtm.tm_min < 0) { + yourtm.tm_min += MINSPERHOUR; + hour--; + } + + mday = yourtm.tm_mday; + mday += hour / HOURSPERDAY; + yourtm.tm_hour = hour % HOURSPERDAY; + if (yourtm.tm_hour < 0) { + yourtm.tm_hour += HOURSPERDAY; + mday--; + } + + y = yourtm.tm_year; + y += yourtm.tm_mon / MONSPERYEAR; + yourtm.tm_mon %= MONSPERYEAR; + if (yourtm.tm_mon < 0) { + yourtm.tm_mon += MONSPERYEAR; + y--; + } + + /* + ** Turn y into an actual year number for now. + ** It is converted back to an offset from TM_YEAR_BASE later. + */ + y += TM_YEAR_BASE; + + while (mday <= 0) { + iinntt li = y - (yourtm.tm_mon <= 1); + mday += year_lengths[isleap(li)]; + y--; + } + while (DAYSPERLYEAR < mday) { + iinntt li = y + (1 < yourtm.tm_mon); + mday -= year_lengths[isleap(li)]; + y++; + } + yourtm.tm_mday = mday; + for ( ; ; ) { + i = mon_lengths[isleap(y)][yourtm.tm_mon]; + if (yourtm.tm_mday <= i) + break; + yourtm.tm_mday -= i; + if (++yourtm.tm_mon >= MONSPERYEAR) { + yourtm.tm_mon = 0; + y++; + } + } +#ifdef ckd_add + if (ckd_add(&yourtm.tm_year, y, -TM_YEAR_BASE)) + return WRONG; +#else + y -= TM_YEAR_BASE; + if (! (INT_MIN <= y && y <= INT_MAX)) + return WRONG; + yourtm.tm_year = y; +#endif + if (yourtm.tm_sec >= 0 && yourtm.tm_sec < SECSPERMIN) + saved_seconds = 0; + else if (yourtm.tm_year < EPOCH_YEAR - TM_YEAR_BASE) { + /* + ** We can't set tm_sec to 0, because that might push the + ** time below the minimum representable time. + ** Set tm_sec to 59 instead. + ** This assumes that the minimum representable time is + ** not in the same minute that a leap second was deleted from, + ** which is a safer assumption than using 58 would be. + */ + saved_seconds = yourtm.tm_sec; + saved_seconds -= SECSPERMIN - 1; + yourtm.tm_sec = SECSPERMIN - 1; + } else { + saved_seconds = yourtm.tm_sec; + yourtm.tm_sec = 0; + } + /* + ** Do a binary search (this works whatever time_t's type is). + */ + lo = TIME_T_MIN; + hi = TIME_T_MAX; + for ( ; ; ) { + t = lo / 2 + hi / 2; + if (t < lo) + t = lo; + else if (t > hi) + t = hi; + if (! funcp(sp, &t, offset, &mytm)) { + /* + ** Assume that t is too extreme to be represented in + ** a struct tm; arrange things so that it is less + ** extreme on the next pass. + */ + dir = (t > 0) ? 1 : -1; + } else dir = tmcomp(&mytm, &yourtm); + if (dir != 0) { + if (t == lo) { + if (t == TIME_T_MAX) + return WRONG; + ++t; + ++lo; + } else if (t == hi) { + if (t == TIME_T_MIN) + return WRONG; + --t; + --hi; + } + if (lo > hi) + return WRONG; + if (dir > 0) + hi = t; + else lo = t; + continue; + } +#if defined TM_GMTOFF && ! UNINIT_TRAP + if (mytm.TM_GMTOFF != yourtm.TM_GMTOFF + && (yourtm.TM_GMTOFF < 0 + ? (-SECSPERDAY <= yourtm.TM_GMTOFF + && (mytm.TM_GMTOFF <= + (min(INT_FAST32_MAX, LONG_MAX) + + yourtm.TM_GMTOFF))) + : (yourtm.TM_GMTOFF <= SECSPERDAY + && ((max(INT_FAST32_MIN, LONG_MIN) + + yourtm.TM_GMTOFF) + <= mytm.TM_GMTOFF)))) { + /* MYTM matches YOURTM except with the wrong UT offset. + YOURTM.TM_GMTOFF is plausible, so try it instead. + It's OK if YOURTM.TM_GMTOFF contains uninitialized data, + since the guess gets checked. */ + time_t altt = t; + int_fast32_t diff = mytm.TM_GMTOFF - yourtm.TM_GMTOFF; + if (!increment_overflow_time(&altt, diff)) { + struct tm alttm; + if (funcp(sp, &altt, offset, &alttm) + && alttm.tm_isdst == mytm.tm_isdst + && alttm.TM_GMTOFF == yourtm.TM_GMTOFF + && tmcomp(&alttm, &yourtm) == 0) { + t = altt; + mytm = alttm; + } + } + } +#endif + if (yourtm.tm_isdst < 0 || mytm.tm_isdst == yourtm.tm_isdst) + break; + /* + ** Right time, wrong type. + ** Hunt for right time, right type. + ** It's okay to guess wrong since the guess + ** gets checked. + */ + if (sp == NULL) + return WRONG; + for (i = sp->typecnt - 1; i >= 0; --i) { + if (sp->ttis[i].tt_isdst != yourtm.tm_isdst) + continue; + for (j = sp->typecnt - 1; j >= 0; --j) { + if (sp->ttis[j].tt_isdst == yourtm.tm_isdst) + continue; + if (ttunspecified(sp, j)) + continue; + newt = (t + sp->ttis[j].tt_utoff + - sp->ttis[i].tt_utoff); + if (! funcp(sp, &newt, offset, &mytm)) + continue; + if (tmcomp(&mytm, &yourtm) != 0) + continue; + if (mytm.tm_isdst != yourtm.tm_isdst) + continue; + /* + ** We have a match. + */ + t = newt; + goto label; + } + } + return WRONG; + } +label: + if (increment_overflow_time_iinntt(&t, saved_seconds)) + return WRONG; + if (funcp(sp, &t, offset, tmp)) + *okayp = true; + return t; +} + +static time_t +time2(struct tm * const tmp, + struct tm *(*funcp)(struct state const *, time_t const *, + int_fast32_t, struct tm *), + struct state const *sp, + const int_fast32_t offset, + bool *okayp) +{ + time_t t; + + /* + ** First try without normalization of seconds + ** (in case tm_sec contains a value associated with a leap second). + ** If that fails, try with normalization of seconds. + */ + t = time2sub(tmp, funcp, sp, offset, okayp, false); + return *okayp ? t : time2sub(tmp, funcp, sp, offset, okayp, true); +} + +static time_t +time1(struct tm *const tmp, + struct tm *(*funcp)(struct state const *, time_t const *, + int_fast32_t, struct tm *), + struct state const *sp, + const int_fast32_t offset) +{ + register time_t t; + register int samei, otheri; + register int sameind, otherind; + register int i; + register int nseen; + char seen[TZ_MAX_TYPES]; + unsigned char types[TZ_MAX_TYPES]; + bool okay; + + if (tmp == NULL) { + errno = EINVAL; + return WRONG; + } + if (tmp->tm_isdst > 1) + tmp->tm_isdst = 1; + t = time2(tmp, funcp, sp, offset, &okay); + if (okay) + return t; + if (tmp->tm_isdst < 0) +#ifdef PCTS + /* + ** POSIX Conformance Test Suite code courtesy Grant Sullivan. + */ + tmp->tm_isdst = 0; /* reset to std and try again */ +#else + return t; +#endif /* !defined PCTS */ + /* + ** We're supposed to assume that somebody took a time of one type + ** and did some math on it that yielded a "struct tm" that's bad. + ** We try to divine the type they started from and adjust to the + ** type they need. + */ + if (sp == NULL) + return WRONG; + for (i = 0; i < sp->typecnt; ++i) + seen[i] = false; + nseen = 0; + for (i = sp->timecnt - 1; i >= 0; --i) + if (!seen[sp->types[i]] && !ttunspecified(sp, sp->types[i])) { + seen[sp->types[i]] = true; + types[nseen++] = sp->types[i]; + } + for (sameind = 0; sameind < nseen; ++sameind) { + samei = types[sameind]; + if (sp->ttis[samei].tt_isdst != tmp->tm_isdst) + continue; + for (otherind = 0; otherind < nseen; ++otherind) { + otheri = types[otherind]; + if (sp->ttis[otheri].tt_isdst == tmp->tm_isdst) + continue; + tmp->tm_sec += (sp->ttis[otheri].tt_utoff + - sp->ttis[samei].tt_utoff); + tmp->tm_isdst = !tmp->tm_isdst; + t = time2(tmp, funcp, sp, offset, &okay); + if (okay) + return t; + tmp->tm_sec -= (sp->ttis[otheri].tt_utoff + - sp->ttis[samei].tt_utoff); + tmp->tm_isdst = !tmp->tm_isdst; + } + } + return WRONG; +} + +#if !defined TM_GMTOFF || !USE_TIMEX_T + +static time_t +mktime_tzname(struct state *sp, struct tm *tmp, bool setname) +{ + if (sp) + return time1(tmp, localsub, sp, setname); + else { + gmtcheck(); + return time1(tmp, gmtsub, gmtptr, 0); + } +} + +# if USE_TIMEX_T +static +# endif +time_t +mktime(struct tm *tmp) +{ + time_t t; + int err = lock(); + if (err) { + errno = err; + return -1; + } + tzset_unlocked(); + t = mktime_tzname(lclptr, tmp, true); + unlock(); + return t; +} + +#endif + +#if NETBSD_INSPIRED && !USE_TIMEX_T +time_t +mktime_z(struct state *restrict sp, struct tm *restrict tmp) +{ + return mktime_tzname(sp, tmp, false); +} +#endif + +#if STD_INSPIRED && !USE_TIMEX_T +/* This function is obsolescent and may disappear in future releases. + Callers can instead use mktime. */ +time_t +timelocal(struct tm *tmp) +{ + if (tmp != NULL) + tmp->tm_isdst = -1; /* in case it wasn't initialized */ + return mktime(tmp); +} +#endif + +#if defined TM_GMTOFF || !USE_TIMEX_T + +# ifndef EXTERN_TIMEOFF +# ifndef timeoff +# define timeoff my_timeoff /* Don't collide with OpenBSD 7.4 . */ +# endif +# define EXTERN_TIMEOFF static +# endif + +/* This function is obsolescent and may disappear in future releases. + Callers can instead use mktime_z with a fixed-offset zone. */ +EXTERN_TIMEOFF time_t +timeoff(struct tm *tmp, long offset) +{ + if (tmp) + tmp->tm_isdst = 0; + gmtcheck(); + return time1(tmp, gmtsub, gmtptr, offset); +} +#endif + +#if !USE_TIMEX_T +time_t +timegm(struct tm *tmp) +{ + time_t t; + struct tm tmcpy; + mktmcpy(&tmcpy, tmp); + tmcpy.tm_wday = -1; + t = timeoff(&tmcpy, 0); + if (0 <= tmcpy.tm_wday) + *tmp = tmcpy; + return t; +} +#endif + +static int_fast32_t +leapcorr(struct state const *sp, time_t t) +{ + register struct lsinfo const * lp; + register int i; + + i = sp->leapcnt; + while (--i >= 0) { + lp = &sp->lsis[i]; + if (t >= lp->ls_trans) + return lp->ls_corr; + } + return 0; +} + +/* +** XXX--is the below the right way to conditionalize?? +*/ + +#if !USE_TIMEX_T +# if STD_INSPIRED + +/* NETBSD_INSPIRED_EXTERN functions are exported to callers if + NETBSD_INSPIRED is defined, and are private otherwise. */ +# if NETBSD_INSPIRED +# define NETBSD_INSPIRED_EXTERN +# else +# define NETBSD_INSPIRED_EXTERN static +# endif + +/* +** IEEE Std 1003.1 (POSIX) says that 536457599 +** shall correspond to "Wed Dec 31 23:59:59 UTC 1986", which +** is not the case if we are accounting for leap seconds. +** So, we provide the following conversion routines for use +** when exchanging timestamps with POSIX conforming systems. +*/ + +NETBSD_INSPIRED_EXTERN time_t +time2posix_z(struct state *sp, time_t t) +{ + return t - leapcorr(sp, t); +} + +time_t +time2posix(time_t t) +{ + int err = lock(); + if (err) { + errno = err; + return -1; + } + if (!lcl_is_set) + tzset_unlocked(); + if (lclptr) + t = time2posix_z(lclptr, t); + unlock(); + return t; +} + +NETBSD_INSPIRED_EXTERN time_t +posix2time_z(struct state *sp, time_t t) +{ + time_t x; + time_t y; + /* + ** For a positive leap second hit, the result + ** is not unique. For a negative leap second + ** hit, the corresponding time doesn't exist, + ** so we return an adjacent second. + */ + x = t + leapcorr(sp, t); + y = x - leapcorr(sp, x); + if (y < t) { + do { + x++; + y = x - leapcorr(sp, x); + } while (y < t); + x -= y != t; + } else if (y > t) { + do { + --x; + y = x - leapcorr(sp, x); + } while (y > t); + x += y != t; + } + return x; +} + +time_t +posix2time(time_t t) +{ + int err = lock(); + if (err) { + errno = err; + return -1; + } + if (!lcl_is_set) + tzset_unlocked(); + if (lclptr) + t = posix2time_z(lclptr, t); + unlock(); + return t; +} + +# endif /* STD_INSPIRED */ + +# if TZ_TIME_T + +# if !USG_COMPAT +# define timezone 0 +# endif + +/* Convert from the underlying system's time_t to the ersatz time_tz, + which is called 'time_t' in this file. Typically, this merely + converts the time's integer width. On some platforms, the system + time is local time not UT, or uses some epoch other than the POSIX + epoch. + + Although this code appears to define a function named 'time' that + returns time_t, the macros in private.h cause this code to actually + define a function named 'tz_time' that returns tz_time_t. The call + to sys_time invokes the underlying system's 'time' function. */ + +time_t +time(time_t *p) +{ + time_t r = sys_time(0); + if (r != (time_t) -1) { + iinntt offset = EPOCH_LOCAL ? timezone : 0; + if (offset < IINNTT_MIN + EPOCH_OFFSET + || increment_overflow_time_iinntt(&r, offset - EPOCH_OFFSET)) { + errno = EOVERFLOW; + r = -1; + } + } + if (p) + *p = r; + return r; +} + +# endif +#endif diff --git a/source/os/src/timezone/private.h b/source/os/src/timezone/private.h new file mode 100644 index 0000000000..bdd0001395 --- /dev/null +++ b/source/os/src/timezone/private.h @@ -0,0 +1,1129 @@ +/* Private header for tzdb code. */ + +#ifndef PRIVATE_H + +#define PRIVATE_H + +/* +** This file is in the public domain, so clarified as of +** 1996-06-05 by Arthur David Olson. +*/ + +/* +** This header is for use ONLY with the time conversion code. +** There is no guarantee that it will remain unchanged, +** or that it will remain at all. +** Do NOT copy it to any system include directory. +** Thank you! +*/ + +/* PORT_TO_C89 means the code should work even if the underlying + compiler and library support only C89 plus C99's 'long long' + and perhaps a few other extensions to C89. + + This macro is obsolescent, and the plan is to remove it along with + associated code. A good time to do that might be in the year 2029 + because RHEL 7 (whose GCC defaults to C89) extended life cycle + support (ELS) is scheduled to end on 2028-06-30. */ +#ifndef PORT_TO_C89 +# define PORT_TO_C89 0 +#endif + +/* SUPPORT_C89 means the tzcode library should support C89 callers + in addition to the usual support for C99-and-later callers. + This defaults to 1 as POSIX requires, even though that can trigger + latent bugs in callers. */ +#ifndef SUPPORT_C89 +# define SUPPORT_C89 1 +#endif + +#ifndef __STDC_VERSION__ +# define __STDC_VERSION__ 0 +#endif + +/* Define true, false and bool if they don't work out of the box. */ +#if PORT_TO_C89 && __STDC_VERSION__ < 199901 +# define true 1 +# define false 0 +# define bool int +#elif __STDC_VERSION__ < 202311 +# include +#endif + +#if __STDC_VERSION__ < 202311 +# undef static_assert +# define static_assert(cond) extern int static_assert_check[(cond) ? 1 : -1] +#endif + +/* +** zdump has been made independent of the rest of the time +** conversion package to increase confidence in the verification it provides. +** You can use zdump to help in verifying other implementations. +** To do this, compile with -DUSE_LTZ=0 and link without the tz library. +*/ +#ifndef USE_LTZ +# define USE_LTZ 1 +#endif + +/* This string was in the Factory zone through version 2016f. */ +#define GRANDPARENTED "Local time zone must be set--see zic manual page" + +/* +** Defaults for preprocessor symbols. +** You can override these in your C compiler options, e.g. '-DHAVE_GETTEXT=1'. +*/ + +#if !defined HAVE__GENERIC && defined __has_extension +# if !__has_extension(c_generic_selections) +# define HAVE__GENERIC 0 +# endif +#endif +/* _Generic is buggy in pre-4.9 GCC. */ +#if !defined HAVE__GENERIC && defined __GNUC__ && !defined __STRICT_ANSI__ +# define HAVE__GENERIC (4 < __GNUC__ + (9 <= __GNUC_MINOR__)) +#endif +#ifndef HAVE__GENERIC +# define HAVE__GENERIC (201112 <= __STDC_VERSION__) +#endif + +#if !defined HAVE_GETTEXT && defined __has_include +# if __has_include() +# define HAVE_GETTEXT true +# endif +#endif +#ifndef HAVE_GETTEXT +# define HAVE_GETTEXT false +#endif + +#ifndef HAVE_INCOMPATIBLE_CTIME_R +# define HAVE_INCOMPATIBLE_CTIME_R 0 +#endif + +#ifndef HAVE_LINK +# define HAVE_LINK 1 +#endif /* !defined HAVE_LINK */ + +#ifndef HAVE_MALLOC_ERRNO +# define HAVE_MALLOC_ERRNO 1 +#endif + +#ifndef HAVE_POSIX_DECLS +# define HAVE_POSIX_DECLS 1 +#endif + +#ifndef HAVE_SETENV +# define HAVE_SETENV 1 +#endif + +#ifndef HAVE_STRDUP +# define HAVE_STRDUP 1 +#endif + +#ifndef HAVE_SYMLINK +# define HAVE_SYMLINK 1 +#endif /* !defined HAVE_SYMLINK */ + +#if !defined HAVE_SYS_STAT_H && defined __has_include +# if !__has_include() +# define HAVE_SYS_STAT_H false +# endif +#endif +#ifndef HAVE_SYS_STAT_H +# define HAVE_SYS_STAT_H true +#endif + +#if !defined HAVE_UNISTD_H && defined __has_include +# if !__has_include() +# define HAVE_UNISTD_H false +# endif +#endif +#ifndef HAVE_UNISTD_H +# define HAVE_UNISTD_H true +#endif + +#ifndef NETBSD_INSPIRED +# define NETBSD_INSPIRED 1 +#endif + +#if HAVE_INCOMPATIBLE_CTIME_R +# define asctime_r _incompatible_asctime_r +# define ctime_r _incompatible_ctime_r +#endif /* HAVE_INCOMPATIBLE_CTIME_R */ + +/* Enable tm_gmtoff, tm_zone, and environ on GNUish systems. */ +#define _GNU_SOURCE 1 +/* Fix asctime_r on Solaris 11. */ +#define _POSIX_PTHREAD_SEMANTICS 1 +/* Enable strtoimax on pre-C99 Solaris 11. */ +#define __EXTENSIONS__ 1 + +/* On GNUish systems where time_t might be 32 or 64 bits, use 64. + On these platforms _FILE_OFFSET_BITS must also be 64; otherwise + setting _TIME_BITS to 64 does not work. The code does not + otherwise rely on _FILE_OFFSET_BITS being 64, since it does not + use off_t or functions like 'stat' that depend on off_t. */ +#ifndef _TIME_BITS +# ifndef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 +# endif +# if _FILE_OFFSET_BITS == 64 +# define _TIME_BITS 64 +# endif +#endif + +/* +** Nested includes +*/ + +/* Avoid clashes with NetBSD by renaming NetBSD's declarations. + If defining the 'timezone' variable, avoid a clash with FreeBSD's + 'timezone' function by renaming its declaration. */ +#define localtime_rz sys_localtime_rz +#define mktime_z sys_mktime_z +#define posix2time_z sys_posix2time_z +#define time2posix_z sys_time2posix_z +#if defined USG_COMPAT && USG_COMPAT == 2 +# define timezone sys_timezone +#endif +#define timezone_t sys_timezone_t +#define tzalloc sys_tzalloc +#define tzfree sys_tzfree +#include +#undef localtime_rz +#undef mktime_z +#undef posix2time_z +#undef time2posix_z +#if defined USG_COMPAT && USG_COMPAT == 2 +# undef timezone +#endif +#undef timezone_t +#undef tzalloc +#undef tzfree + +#include +#include +#if !PORT_TO_C89 +# include +#endif +#include /* for CHAR_BIT et al. */ +#include + +#include + +#ifndef EINVAL +# define EINVAL ERANGE +#endif + +#ifndef ELOOP +# define ELOOP EINVAL +#endif +#ifndef ENAMETOOLONG +# define ENAMETOOLONG EINVAL +#endif +#ifndef ENOMEM +# define ENOMEM EINVAL +#endif +#ifndef ENOTSUP +# define ENOTSUP EINVAL +#endif +#ifndef EOVERFLOW +# define EOVERFLOW EINVAL +#endif + +#if HAVE_GETTEXT +# include +#endif /* HAVE_GETTEXT */ + +#if HAVE_UNISTD_H +# include /* for R_OK, and other POSIX goodness */ +#endif /* HAVE_UNISTD_H */ + +/* SUPPORT_POSIX2008 means the tzcode library should support + POSIX.1-2017-and-earlier callers in addition to the usual support for + POSIX.1-2024-and-later callers; however, this can be + incompatible with POSIX.1-2024-and-later callers. + This macro is obsolescent, and the plan is to remove it + along with any code needed only when it is nonzero. + A good time to do that might be in the year 2034. + This macro's name is SUPPORT_POSIX2008 because _POSIX_VERSION == 200809 + in POSIX.1-2017, a minor revision of POSIX.1-2008. */ +#ifndef SUPPORT_POSIX2008 +# if defined _POSIX_VERSION && _POSIX_VERSION <= 200809 +# define SUPPORT_POSIX2008 1 +# else +# define SUPPORT_POSIX2008 0 +# endif +#endif + +#ifndef HAVE_DECL_ASCTIME_R +# if SUPPORT_POSIX2008 +# define HAVE_DECL_ASCTIME_R 1 +# else +# define HAVE_DECL_ASCTIME_R 0 +# endif +#endif + +#ifndef HAVE_STRFTIME_L +# if _POSIX_VERSION < 200809 +# define HAVE_STRFTIME_L 0 +# else +# define HAVE_STRFTIME_L 1 +# endif +#endif + +#ifndef USG_COMPAT +# ifndef _XOPEN_VERSION +# define USG_COMPAT 0 +# else +# define USG_COMPAT 1 +# endif +#endif + +#ifndef HAVE_TZNAME +# if _POSIX_VERSION < 198808 && !USG_COMPAT +# define HAVE_TZNAME 0 +# else +# define HAVE_TZNAME 1 +# endif +#endif + +#ifndef ALTZONE +# if defined __sun || defined _M_XENIX +# define ALTZONE 1 +# else +# define ALTZONE 0 +# endif +#endif + +#ifndef R_OK +# define R_OK 4 +#endif /* !defined R_OK */ + +#if PORT_TO_C89 + +/* +** Define HAVE_STDINT_H's default value here, rather than at the +** start, since __GLIBC__ and INTMAX_MAX's values depend on +** previously included files. glibc 2.1 and Solaris 10 and later have +** stdint.h, even with pre-C99 compilers. +*/ +#if !defined HAVE_STDINT_H && defined __has_include +# define HAVE_STDINT_H true /* C23 __has_include implies C99 stdint.h. */ +#endif +#ifndef HAVE_STDINT_H +# define HAVE_STDINT_H \ + (199901 <= __STDC_VERSION__ \ + || 2 < __GLIBC__ + (1 <= __GLIBC_MINOR__) \ + || __CYGWIN__ || INTMAX_MAX) +#endif /* !defined HAVE_STDINT_H */ + +#if HAVE_STDINT_H +# include +#endif /* !HAVE_STDINT_H */ + +#ifndef HAVE_INTTYPES_H +# define HAVE_INTTYPES_H HAVE_STDINT_H +#endif +#if HAVE_INTTYPES_H +# include +#endif + +/* Pre-C99 GCC compilers define __LONG_LONG_MAX__ instead of LLONG_MAX. */ +#if defined __LONG_LONG_MAX__ && !defined __STRICT_ANSI__ +# ifndef LLONG_MAX +# define LLONG_MAX __LONG_LONG_MAX__ +# endif +# ifndef LLONG_MIN +# define LLONG_MIN (-1 - LLONG_MAX) +# endif +# ifndef ULLONG_MAX +# define ULLONG_MAX (LLONG_MAX * 2ull + 1) +# endif +#endif + +#ifndef INT_FAST64_MAX +# if 1 <= LONG_MAX >> 31 >> 31 +typedef long int_fast64_t; +# define INT_FAST64_MIN LONG_MIN +# define INT_FAST64_MAX LONG_MAX +# else +/* If this fails, compile with -DHAVE_STDINT_H or with a better compiler. */ +typedef long long int_fast64_t; +# define INT_FAST64_MIN LLONG_MIN +# define INT_FAST64_MAX LLONG_MAX +# endif +#endif + +#ifndef PRIdFAST64 +# if INT_FAST64_MAX == LONG_MAX +# define PRIdFAST64 "ld" +# else +# define PRIdFAST64 "lld" +# endif +#endif + +#ifndef SCNdFAST64 +# define SCNdFAST64 PRIdFAST64 +#endif + +#ifndef INT_FAST32_MAX +# if INT_MAX >> 31 == 0 +typedef long int_fast32_t; +# define INT_FAST32_MAX LONG_MAX +# define INT_FAST32_MIN LONG_MIN +# else +typedef int int_fast32_t; +# define INT_FAST32_MAX INT_MAX +# define INT_FAST32_MIN INT_MIN +# endif +#endif + +#ifndef INTMAX_MAX +# ifdef LLONG_MAX +typedef long long intmax_t; +# ifndef HAVE_STRTOLL +# define HAVE_STRTOLL true +# endif +# if HAVE_STRTOLL +# define strtoimax strtoll +# endif +# define INTMAX_MAX LLONG_MAX +# define INTMAX_MIN LLONG_MIN +# else +typedef long intmax_t; +# define INTMAX_MAX LONG_MAX +# define INTMAX_MIN LONG_MIN +# endif +# ifndef strtoimax +# define strtoimax strtol +# endif +#endif + +#ifndef PRIdMAX +# if INTMAX_MAX == LLONG_MAX +# define PRIdMAX "lld" +# else +# define PRIdMAX "ld" +# endif +#endif + +#ifndef PTRDIFF_MAX +# define PTRDIFF_MAX MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t)) +#endif + +#ifndef UINT_FAST32_MAX +typedef unsigned long uint_fast32_t; +#endif + +#ifndef UINT_FAST64_MAX +# if 3 <= ULONG_MAX >> 31 >> 31 +typedef unsigned long uint_fast64_t; +# define UINT_FAST64_MAX ULONG_MAX +# else +/* If this fails, compile with -DHAVE_STDINT_H or with a better compiler. */ +typedef unsigned long long uint_fast64_t; +# define UINT_FAST64_MAX ULLONG_MAX +# endif +#endif + +#ifndef UINTMAX_MAX +# ifdef ULLONG_MAX +typedef unsigned long long uintmax_t; +# define UINTMAX_MAX ULLONG_MAX +# else +typedef unsigned long uintmax_t; +# define UINTMAX_MAX ULONG_MAX +# endif +#endif + +#ifndef PRIuMAX +# ifdef ULLONG_MAX +# define PRIuMAX "llu" +# else +# define PRIuMAX "lu" +# endif +#endif + +#ifndef SIZE_MAX +# define SIZE_MAX ((size_t) -1) +#endif + +#endif /* PORT_TO_C89 */ + +/* The maximum size of any created object, as a signed integer. + Although the C standard does not outright prohibit larger objects, + behavior is undefined if the result of pointer subtraction does not + fit into ptrdiff_t, and the code assumes in several places that + pointer subtraction works. As a practical matter it's OK to not + support objects larger than this. */ +#define INDEX_MAX ((ptrdiff_t) min(PTRDIFF_MAX, SIZE_MAX)) + +/* Support ckd_add, ckd_sub, ckd_mul on C23 or recent-enough GCC-like + hosts, unless compiled with -DHAVE_STDCKDINT_H=0 or with pre-C23 EDG. */ +#if !defined HAVE_STDCKDINT_H && defined __has_include +# if __has_include() +# define HAVE_STDCKDINT_H true +# endif +#endif +#ifdef HAVE_STDCKDINT_H +# if HAVE_STDCKDINT_H +# include +# endif +#elif defined __EDG__ +/* Do nothing, to work around EDG bug . */ +#elif defined __has_builtin +# if __has_builtin(__builtin_add_overflow) +# define ckd_add(r, a, b) __builtin_add_overflow(a, b, r) +# endif +# if __has_builtin(__builtin_sub_overflow) +# define ckd_sub(r, a, b) __builtin_sub_overflow(a, b, r) +# endif +# if __has_builtin(__builtin_mul_overflow) +# define ckd_mul(r, a, b) __builtin_mul_overflow(a, b, r) +# endif +#elif 7 <= __GNUC__ +# define ckd_add(r, a, b) __builtin_add_overflow(a, b, r) +# define ckd_sub(r, a, b) __builtin_sub_overflow(a, b, r) +# define ckd_mul(r, a, b) __builtin_mul_overflow(a, b, r) +#endif + +#if (defined __has_c_attribute \ + && (202311 <= __STDC_VERSION__ || !defined __STRICT_ANSI__)) +# define HAVE___HAS_C_ATTRIBUTE true +#else +# define HAVE___HAS_C_ATTRIBUTE false +#endif + +#if HAVE___HAS_C_ATTRIBUTE +# if __has_c_attribute(deprecated) +# define ATTRIBUTE_DEPRECATED [[deprecated]] +# endif +#endif +#ifndef ATTRIBUTE_DEPRECATED +# if 3 < __GNUC__ + (2 <= __GNUC_MINOR__) +# define ATTRIBUTE_DEPRECATED __attribute__((deprecated)) +# else +# define ATTRIBUTE_DEPRECATED /* empty */ +# endif +#endif + +#if HAVE___HAS_C_ATTRIBUTE +# if __has_c_attribute(fallthrough) +# define ATTRIBUTE_FALLTHROUGH [[fallthrough]] +# endif +#endif +#ifndef ATTRIBUTE_FALLTHROUGH +# if 7 <= __GNUC__ +# define ATTRIBUTE_FALLTHROUGH __attribute__((fallthrough)) +# else +# define ATTRIBUTE_FALLTHROUGH ((void) 0) +# endif +#endif + +#if HAVE___HAS_C_ATTRIBUTE +# if __has_c_attribute(maybe_unused) +# define ATTRIBUTE_MAYBE_UNUSED [[maybe_unused]] +# endif +#endif +#ifndef ATTRIBUTE_MAYBE_UNUSED +# if 2 < __GNUC__ + (7 <= __GNUC_MINOR__) +# define ATTRIBUTE_MAYBE_UNUSED __attribute__((unused)) +# else +# define ATTRIBUTE_MAYBE_UNUSED /* empty */ +# endif +#endif + +#if HAVE___HAS_C_ATTRIBUTE +# if __has_c_attribute(noreturn) +# define ATTRIBUTE_NORETURN [[noreturn]] +# endif +#endif +#ifndef ATTRIBUTE_NORETURN +# if 201112 <= __STDC_VERSION__ +# define ATTRIBUTE_NORETURN _Noreturn +# elif 2 < __GNUC__ + (8 <= __GNUC_MINOR__) +# define ATTRIBUTE_NORETURN __attribute__((noreturn)) +# else +# define ATTRIBUTE_NORETURN /* empty */ +# endif +#endif + +#if HAVE___HAS_C_ATTRIBUTE +# if __has_c_attribute(reproducible) +# define ATTRIBUTE_REPRODUCIBLE [[reproducible]] +# endif +#endif +#ifndef ATTRIBUTE_REPRODUCIBLE +# define ATTRIBUTE_REPRODUCIBLE /* empty */ +#endif + +#if HAVE___HAS_C_ATTRIBUTE +# if __has_c_attribute(unsequenced) +# define ATTRIBUTE_UNSEQUENCED [[unsequenced]] +# endif +#endif +#ifndef ATTRIBUTE_UNSEQUENCED +# define ATTRIBUTE_UNSEQUENCED /* empty */ +#endif + +/* GCC attributes that are useful in tzcode. + __attribute__((const)) is stricter than [[unsequenced]], + so the latter is an adequate substitute in non-GCC C23 platforms. + __attribute__((pure)) is stricter than [[reproducible]], + so the latter is an adequate substitute in non-GCC C23 platforms. */ +#if __GNUC__ < 3 +# define ATTRIBUTE_CONST ATTRIBUTE_UNSEQUENCED +# define ATTRIBUTE_FORMAT(spec) /* empty */ +# define ATTRIBUTE_PURE ATTRIBUTE_REPRODUCIBLE +#else +# define ATTRIBUTE_CONST __attribute__((const)) +# define ATTRIBUTE_FORMAT(spec) __attribute__((format spec)) +# define ATTRIBUTE_PURE __attribute__((pure)) +#endif + +/* Avoid GCC bug 114833 . + Remove this macro and its uses when the bug is fixed in a GCC release, + because only the latest GCC matters for $(GCC_DEBUG_FLAGS). */ +#ifdef GCC_LINT +# define ATTRIBUTE_PURE_114833 ATTRIBUTE_PURE +#else +# define ATTRIBUTE_PURE_114833 /* empty */ +#endif + +#if (__STDC_VERSION__ < 199901 && !defined restrict \ + && (PORT_TO_C89 || defined _MSC_VER)) +# define restrict /* empty */ +#endif + +/* +** Workarounds for compilers/systems. +*/ + +#ifndef EPOCH_LOCAL +# define EPOCH_LOCAL 0 +#endif +#ifndef EPOCH_OFFSET +# define EPOCH_OFFSET 0 +#endif +#ifndef RESERVE_STD_EXT_IDS +# define RESERVE_STD_EXT_IDS 0 +#endif + +#ifdef time_tz +# define defined_time_tz true +#else +# define defined_time_tz false +#endif + +/* If standard C identifiers with external linkage (e.g., localtime) + are reserved and are not already being renamed anyway, rename them + as if compiling with '-Dtime_tz=time_t'. */ +#if !defined time_tz && RESERVE_STD_EXT_IDS && USE_LTZ +# define time_tz time_t +#endif + +/* +** Compile with -Dtime_tz=T to build the tz package with a private +** time_t type equivalent to T rather than the system-supplied time_t. +** This debugging feature can test unusual design decisions +** (e.g., time_t wider than 'long', or unsigned time_t) even on +** typical platforms. +*/ +#if defined time_tz || EPOCH_LOCAL || EPOCH_OFFSET != 0 +# define TZ_TIME_T 1 +#else +# define TZ_TIME_T 0 +#endif + +#if defined LOCALTIME_IMPLEMENTATION && TZ_TIME_T +static time_t sys_time(time_t *x) { return time(x); } +#endif + +#if TZ_TIME_T + +typedef time_tz tz_time_t; + +# undef asctime +# define asctime tz_asctime +# undef ctime +# define ctime tz_ctime +# undef difftime +# define difftime tz_difftime +# undef gmtime +# define gmtime tz_gmtime +# undef gmtime_r +# define gmtime_r tz_gmtime_r +# undef localtime +# define localtime tz_localtime +# undef localtime_r +# define localtime_r tz_localtime_r +# undef localtime_rz +# define localtime_rz tz_localtime_rz +# undef mktime +# define mktime tz_mktime +# undef mktime_z +# define mktime_z tz_mktime_z +# undef offtime +# define offtime tz_offtime +# undef posix2time +# define posix2time tz_posix2time +# undef posix2time_z +# define posix2time_z tz_posix2time_z +# undef strftime +# define strftime tz_strftime +# undef time +# define time tz_time +# undef time2posix +# define time2posix tz_time2posix +# undef time2posix_z +# define time2posix_z tz_time2posix_z +# undef time_t +# define time_t tz_time_t +# undef timegm +# define timegm tz_timegm +# undef timelocal +# define timelocal tz_timelocal +# undef timeoff +# define timeoff tz_timeoff +# undef tzalloc +# define tzalloc tz_tzalloc +# undef tzfree +# define tzfree tz_tzfree +# undef tzset +# define tzset tz_tzset +# if SUPPORT_POSIX2008 +# undef asctime_r +# define asctime_r tz_asctime_r +# undef ctime_r +# define ctime_r tz_ctime_r +# endif +# if HAVE_STRFTIME_L +# undef strftime_l +# define strftime_l tz_strftime_l +# endif +# if HAVE_TZNAME +# undef tzname +# define tzname tz_tzname +# endif +# if USG_COMPAT +# undef daylight +# define daylight tz_daylight +# undef timezone +# define timezone tz_timezone +# endif +# if ALTZONE +# undef altzone +# define altzone tz_altzone +# endif + +# if __STDC_VERSION__ < 202311 +# define DEPRECATED_IN_C23 /* empty */ +# else +# define DEPRECATED_IN_C23 ATTRIBUTE_DEPRECATED +# endif +DEPRECATED_IN_C23 char *asctime(struct tm const *); +DEPRECATED_IN_C23 char *ctime(time_t const *); +#if SUPPORT_POSIX2008 +char *asctime_r(struct tm const *restrict, char *restrict); +char *ctime_r(time_t const *, char *); +#endif +ATTRIBUTE_CONST double difftime(time_t, time_t); +size_t strftime(char *restrict, size_t, char const *restrict, + struct tm const *restrict); +# if HAVE_STRFTIME_L +size_t strftime_l(char *restrict, size_t, char const *restrict, + struct tm const *restrict, locale_t); +# endif +struct tm *gmtime(time_t const *); +struct tm *gmtime_r(time_t const *restrict, struct tm *restrict); +struct tm *localtime(time_t const *); +struct tm *localtime_r(time_t const *restrict, struct tm *restrict); +time_t mktime(struct tm *); +time_t time(time_t *); +time_t timegm(struct tm *); +void tzset(void); +#endif + +#ifndef HAVE_DECL_TIMEGM +# if (202311 <= __STDC_VERSION__ \ + || defined __GLIBC__ || defined __tm_zone /* musl */ \ + || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \ + || (defined __APPLE__ && defined __MACH__)) +# define HAVE_DECL_TIMEGM true +# else +# define HAVE_DECL_TIMEGM false +# endif +#endif +#if !HAVE_DECL_TIMEGM && !defined timegm +time_t timegm(struct tm *); +#endif + +#if !HAVE_DECL_ASCTIME_R && !defined asctime_r && SUPPORT_POSIX2008 +extern char *asctime_r(struct tm const *restrict, char *restrict); +#endif + +#ifndef HAVE_DECL_ENVIRON +# if defined environ || defined __USE_GNU +# define HAVE_DECL_ENVIRON 1 +# else +# define HAVE_DECL_ENVIRON 0 +# endif +#endif + +#if !HAVE_DECL_ENVIRON +extern char **environ; +#endif + +#if 2 <= HAVE_TZNAME + (TZ_TIME_T || !HAVE_POSIX_DECLS) +extern char *tzname[]; +#endif +#if 2 <= USG_COMPAT + (TZ_TIME_T || !HAVE_POSIX_DECLS) +extern long timezone; +extern int daylight; +#endif +#if 2 <= ALTZONE + (TZ_TIME_T || !HAVE_POSIX_DECLS) +extern long altzone; +#endif + +/* +** The STD_INSPIRED functions are similar, but most also need +** declarations if time_tz is defined. +*/ + +#ifndef STD_INSPIRED +# define STD_INSPIRED 0 +#endif +#if STD_INSPIRED +# if TZ_TIME_T || !defined offtime +struct tm *offtime(time_t const *, long); +# endif +# if TZ_TIME_T || !defined timelocal +time_t timelocal(struct tm *); +# endif +# if TZ_TIME_T || !defined timeoff +# define EXTERN_TIMEOFF +# endif +# if TZ_TIME_T || !defined time2posix +time_t time2posix(time_t); +# endif +# if TZ_TIME_T || !defined posix2time +time_t posix2time(time_t); +# endif +#endif + +/* Infer TM_ZONE on systems where this information is known, but suppress + guessing if NO_TM_ZONE is defined. Similarly for TM_GMTOFF. */ +#if (200809 < _POSIX_VERSION \ + || defined __GLIBC__ \ + || defined __tm_zone /* musl */ \ + || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \ + || (defined __APPLE__ && defined __MACH__)) +# if !defined TM_GMTOFF && !defined NO_TM_GMTOFF +# define TM_GMTOFF tm_gmtoff +# endif +# if !defined TM_ZONE && !defined NO_TM_ZONE +# define TM_ZONE tm_zone +# endif +#endif + +/* +** Define functions that are ABI compatible with NetBSD but have +** better prototypes. NetBSD 6.1.4 defines a pointer type timezone_t +** and labors under the misconception that 'const timezone_t' is a +** pointer to a constant. This use of 'const' is ineffective, so it +** is not done here. What we call 'struct state' NetBSD calls +** 'struct __state', but this is a private name so it doesn't matter. +*/ +#if NETBSD_INSPIRED +typedef struct state *timezone_t; +struct tm *localtime_rz(timezone_t restrict, time_t const *restrict, + struct tm *restrict); +time_t mktime_z(timezone_t restrict, struct tm *restrict); +timezone_t tzalloc(char const *); +void tzfree(timezone_t); +# if STD_INSPIRED +# if TZ_TIME_T || !defined posix2time_z +ATTRIBUTE_PURE time_t posix2time_z(timezone_t, time_t); +# endif +# if TZ_TIME_T || !defined time2posix_z +ATTRIBUTE_PURE time_t time2posix_z(timezone_t, time_t); +# endif +# endif +#endif + +/* +** Finally, some convenience items. +*/ + +#define TYPE_BIT(type) (CHAR_BIT * (ptrdiff_t) sizeof(type)) +#define TYPE_SIGNED(type) (((type) -1) < 0) +#define TWOS_COMPLEMENT(t) ((t) ~ (t) 0 < 0) + +/* Minimum and maximum of two values. Use lower case to avoid + naming clashes with standard include files. */ +#define max(a, b) ((a) > (b) ? (a) : (b)) +#define min(a, b) ((a) < (b) ? (a) : (b)) + +/* Max and min values of the integer type T, of which only the bottom + B bits are used, and where the highest-order used bit is considered + to be a sign bit if T is signed. */ +#define MAXVAL(t, b) \ + ((t) (((t) 1 << ((b) - 1 - TYPE_SIGNED(t))) \ + - 1 + ((t) 1 << ((b) - 1 - TYPE_SIGNED(t))))) +#define MINVAL(t, b) \ + ((t) (TYPE_SIGNED(t) ? - TWOS_COMPLEMENT(t) - MAXVAL(t, b) : 0)) + +/* The extreme time values, assuming no padding. */ +#define TIME_T_MIN_NO_PADDING MINVAL(time_t, TYPE_BIT(time_t)) +#define TIME_T_MAX_NO_PADDING MAXVAL(time_t, TYPE_BIT(time_t)) + +/* The extreme time values. These are macros, not constants, so that + any portability problems occur only when compiling .c files that use + the macros, which is safer for applications that need only zdump and zic. + This implementation assumes no padding if time_t is signed and + either the compiler lacks support for _Generic or time_t is not one + of the standard signed integer types. */ +#if HAVE__GENERIC +# define TIME_T_MIN \ + _Generic((time_t) 0, \ + signed char: SCHAR_MIN, short: SHRT_MIN, \ + int: INT_MIN, long: LONG_MIN, long long: LLONG_MIN, \ + default: TIME_T_MIN_NO_PADDING) +# define TIME_T_MAX \ + (TYPE_SIGNED(time_t) \ + ? _Generic((time_t) 0, \ + signed char: SCHAR_MAX, short: SHRT_MAX, \ + int: INT_MAX, long: LONG_MAX, long long: LLONG_MAX, \ + default: TIME_T_MAX_NO_PADDING) \ + : (time_t) -1) +enum { SIGNED_PADDING_CHECK_NEEDED + = _Generic((time_t) 0, + signed char: false, short: false, + int: false, long: false, long long: false, + default: true) }; +#else +# define TIME_T_MIN TIME_T_MIN_NO_PADDING +# define TIME_T_MAX TIME_T_MAX_NO_PADDING +enum { SIGNED_PADDING_CHECK_NEEDED = true }; +#endif +/* Try to check the padding assumptions. Although TIME_T_MAX and the + following check can both have undefined behavior on oddball + platforms due to shifts exceeding widths of signed integers, these + platforms' compilers are likely to diagnose these issues in integer + constant expressions, so it shouldn't hurt to check statically. */ +static_assert(! TYPE_SIGNED(time_t) || ! SIGNED_PADDING_CHECK_NEEDED + || TIME_T_MAX >> (TYPE_BIT(time_t) - 2) == 1); + +/* If true, the value returned by an idealized unlimited-range mktime + always fits into an integer type with bounds MIN and MAX. + If false, the value might not fit. + This macro is usable in #if if its arguments are. + Add or subtract 2**31 - 1 for the maximum UT offset allowed in a TZif file, + divide by the maximum number of non-leap seconds in a year, + divide again by two just to be safe, + and account for the tm_year origin (1900) and time_t origin (1970). */ +#define MKTIME_FITS_IN(min, max) \ + ((min) < 0 \ + && ((min) + 0x7fffffff) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900 < INT_MIN \ + && INT_MAX < ((max) - 0x7fffffff) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900) + +/* MKTIME_MIGHT_OVERFLOW is true if mktime can fail due to time_t overflow + or if it is not known whether mktime can fail, + and is false if mktime definitely cannot fail. + This macro is usable in #if, and so does not use TIME_T_MAX or sizeof. + If the builder has not configured this macro, guess based on what + known platforms do. When in doubt, guess true. */ +#ifndef MKTIME_MIGHT_OVERFLOW +# if defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ +# include +# endif +# if ((/* The following heuristics assume native time_t. */ \ + defined_time_tz) \ + || ((/* Traditional time_t is 'long', so if 'long' is not wide enough \ + assume overflow unless we're on a known-safe host. */ \ + !MKTIME_FITS_IN(LONG_MIN, LONG_MAX)) \ + && (/* GNU C Library 2.29 (2019-02-01) and later has 64-bit time_t \ + if __TIMESIZE is 64. */ \ + !defined __TIMESIZE || __TIMESIZE < 64) \ + && (/* FreeBSD 12 r320347 (__FreeBSD_version 1200036; 2017-06-26), \ + and later has 64-bit time_t on all platforms but i386 which \ + is currently scheduled for end-of-life on 2028-11-30. */ \ + !defined __FreeBSD_version || __FreeBSD_version < 1200036 \ + || defined __i386) \ + && (/* NetBSD 6.0 (2012-10-17) and later has 64-bit time_t. */ \ + !defined __NetBSD_Version__ || __NetBSD_Version__ < 600000000) \ + && (/* OpenBSD 5.5 (2014-05-01) and later has 64-bit time_t. */ \ + !defined OpenBSD || OpenBSD < 201405))) +# define MKTIME_MIGHT_OVERFLOW true +# else +# define MKTIME_MIGHT_OVERFLOW false +# endif +#endif +/* Check that MKTIME_MIGHT_OVERFLOW is consistent with time_t's range. */ +static_assert(MKTIME_MIGHT_OVERFLOW + || MKTIME_FITS_IN(TIME_T_MIN, TIME_T_MAX)); + +/* +** 302 / 1000 is log10(2.0) rounded up. +** Subtract one for the sign bit if the type is signed; +** add one for integer division truncation; +** add one more for a minus sign if the type is signed. +*/ +#define INT_STRLEN_MAXIMUM(type) \ + ((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + \ + 1 + TYPE_SIGNED(type)) + +/* +** INITIALIZE(x) +*/ + +#ifdef GCC_LINT +# define INITIALIZE(x) ((x) = 0) +#else +# define INITIALIZE(x) +#endif + +/* Whether memory access must strictly follow the C standard. + If 0, it's OK to read uninitialized storage so long as the value is + not relied upon. Defining it to 0 lets mktime access parts of + struct tm that might be uninitialized, as a heuristic when the + standard doesn't say what to return and when tm_gmtoff can help + mktime likely infer a better value. */ +#ifndef UNINIT_TRAP +# define UNINIT_TRAP 0 +#endif + +/* strftime.c sometimes needs access to timeoff if it is not already public. + tz_private_timeoff should be used only by localtime.c and strftime.c. */ +#if (!defined EXTERN_TIMEOFF && ! (defined USE_TIMEX_T && USE_TIMEX_T) \ + && defined TM_GMTOFF && (200809 < _POSIX_VERSION || ! UNINIT_TRAP)) +# ifndef timeoff +# define timeoff tz_private_timeoff +# endif +# define EXTERN_TIMEOFF +#endif +#ifdef EXTERN_TIMEOFF +time_t timeoff(struct tm *, long); +#endif + +#ifdef DEBUG +# undef unreachable +# define unreachable() abort() +#elif !defined unreachable +# ifdef __has_builtin +# if __has_builtin(__builtin_unreachable) +# define unreachable() __builtin_unreachable() +# endif +# elif 4 < __GNUC__ + (5 <= __GNUC_MINOR__) +# define unreachable() __builtin_unreachable() +# endif +# ifndef unreachable +# define unreachable() ((void) 0) +# endif +#endif + +/* +** For the benefit of GNU folk... +** '_(MSGID)' uses the current locale's message library string for MSGID. +** The default is to use gettext if available, and use MSGID otherwise. +*/ + +#if HAVE_GETTEXT +#define _(msgid) gettext(msgid) +#else /* !HAVE_GETTEXT */ +#define _(msgid) msgid +#endif /* !HAVE_GETTEXT */ + +#if !defined TZ_DOMAIN && defined HAVE_GETTEXT +# define TZ_DOMAIN "tz" +#endif + +#if HAVE_INCOMPATIBLE_CTIME_R +#undef asctime_r +#undef ctime_r +char *asctime_r(struct tm const *restrict, char *restrict); +char *ctime_r(time_t const *, char *); +#endif /* HAVE_INCOMPATIBLE_CTIME_R */ + +/* Handy macros that are independent of tzfile implementation. */ + +enum { + SECSPERMIN = 60, + MINSPERHOUR = 60, + SECSPERHOUR = SECSPERMIN * MINSPERHOUR, + HOURSPERDAY = 24, + DAYSPERWEEK = 7, + DAYSPERNYEAR = 365, + DAYSPERLYEAR = DAYSPERNYEAR + 1, + MONSPERYEAR = 12, + YEARSPERREPEAT = 400 /* years before a Gregorian repeat */ +}; + +#define SECSPERDAY ((int_fast32_t) SECSPERHOUR * HOURSPERDAY) + +#define DAYSPERREPEAT ((int_fast32_t) 400 * 365 + 100 - 4 + 1) +#define SECSPERREPEAT ((int_fast64_t) DAYSPERREPEAT * SECSPERDAY) +#define AVGSECSPERYEAR (SECSPERREPEAT / YEARSPERREPEAT) + +/* How many years to generate (in zic.c) or search through (in localtime.c). + This is two years larger than the obvious 400, to avoid edge cases. + E.g., suppose a rule applies from 2012 on with transitions + in March and September, plus one-off transitions in November 2013, + and suppose the rule cannot be expressed as a proleptic TZ string. + If zic looked only at the last 400 years, it would set max_year=2413, + with the intent that the 400 years 2014 through 2413 will be repeated. + The last transition listed in the tzfile would be in 2413-09, + less than 400 years after the last one-off transition in 2013-11. + Two years is not overkill for localtime.c, as a one-year bump + would mishandle 2023d's America/Ciudad_Juarez for November 2422. */ +enum { years_of_observations = YEARSPERREPEAT + 2 }; + +enum { + TM_SUNDAY, + TM_MONDAY, + TM_TUESDAY, + TM_WEDNESDAY, + TM_THURSDAY, + TM_FRIDAY, + TM_SATURDAY +}; + +enum { + TM_JANUARY, + TM_FEBRUARY, + TM_MARCH, + TM_APRIL, + TM_MAY, + TM_JUNE, + TM_JULY, + TM_AUGUST, + TM_SEPTEMBER, + TM_OCTOBER, + TM_NOVEMBER, + TM_DECEMBER +}; + +enum { + TM_YEAR_BASE = 1900, + TM_WDAY_BASE = TM_MONDAY, + EPOCH_YEAR = 1970, + EPOCH_WDAY = TM_THURSDAY +}; + +#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) + +/* +** Since everything in isleap is modulo 400 (or a factor of 400), we know that +** isleap(y) == isleap(y % 400) +** and so +** isleap(a + b) == isleap((a + b) % 400) +** or +** isleap(a + b) == isleap(a % 400 + b % 400) +** This is true even if % means modulo rather than Fortran remainder +** (which is allowed by C89 but not by C99 or later). +** We use this to avoid addition overflow problems. +*/ + +#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400) + +#endif /* !defined PRIVATE_H */ diff --git a/source/os/src/timezone/strftime.c b/source/os/src/timezone/strftime.c new file mode 100644 index 0000000000..7be94de303 --- /dev/null +++ b/source/os/src/timezone/strftime.c @@ -0,0 +1,662 @@ +/* Convert a broken-down timestamp to a string. */ + +/* Copyright 1989 The Regents of the University of California. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. */ + +/* +** Based on the UCB version with the copyright notice appearing above. +** +** This is ANSIish only when "multibyte character == plain character". +*/ + +#include "private.h" + +#include +#include +#include + +#if MKTIME_MIGHT_OVERFLOW +/* Do this after system includes as it redefines time_t, mktime, timeoff. */ +# define USE_TIMEX_T true +# include "localtime.c" +#endif + +#ifndef DEPRECATE_TWO_DIGIT_YEARS +# define DEPRECATE_TWO_DIGIT_YEARS false +#endif + +struct lc_time_T { + const char * mon[MONSPERYEAR]; + const char * month[MONSPERYEAR]; + const char * wday[DAYSPERWEEK]; + const char * weekday[DAYSPERWEEK]; + const char * X_fmt; + const char * x_fmt; + const char * c_fmt; + const char * am; + const char * pm; + const char * date_fmt; +}; + +static const struct lc_time_T C_time_locale = { + { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }, { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" + }, { + "Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat" + }, { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday" + }, + + /* X_fmt */ + "%H:%M:%S", + + /* + ** x_fmt + ** C99 and later require this format. + ** Using just numbers (as here) makes Quakers happier; + ** it's also compatible with SVR4. + */ + "%m/%d/%y", + + /* + ** c_fmt + ** C99 and later require this format. + ** Previously this code used "%D %X", but we now conform to C99. + ** Note that + ** "%a %b %d %H:%M:%S %Y" + ** is used by Solaris 2.3. + */ + "%a %b %e %T %Y", + + /* am */ + "AM", + + /* pm */ + "PM", + + /* date_fmt */ + "%a %b %e %H:%M:%S %Z %Y" +}; + +enum warn { IN_NONE, IN_SOME, IN_THIS, IN_ALL }; + +static char * _add(const char *, char *, const char *); +static char * _conv(int, const char *, char *, const char *); +static char * _fmt(const char *, const struct tm *, char *, const char *, + enum warn *); +static char * _yconv(int, int, bool, bool, char *, char const *); + +#ifndef YEAR_2000_NAME +# define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS" +#endif /* !defined YEAR_2000_NAME */ + +#if HAVE_STRFTIME_L +size_t +strftime_l(char *restrict s, size_t maxsize, char const *restrict format, + struct tm const *restrict t, + ATTRIBUTE_MAYBE_UNUSED locale_t locale) +{ + /* Just call strftime, as only the C locale is supported. */ + return strftime(s, maxsize, format, t); +} +#endif + +size_t +strftime(char *restrict s, size_t maxsize, char const *restrict format, + struct tm const *restrict t) +{ + char * p; + int saved_errno = errno; + enum warn warn = IN_NONE; + + tzset(); + p = _fmt(format, t, s, s + maxsize, &warn); + if (DEPRECATE_TWO_DIGIT_YEARS + && warn != IN_NONE && getenv(YEAR_2000_NAME)) { + fprintf(stderr, "\n"); + fprintf(stderr, "strftime format \"%s\" ", format); + fprintf(stderr, "yields only two digits of years in "); + if (warn == IN_SOME) + fprintf(stderr, "some locales"); + else if (warn == IN_THIS) + fprintf(stderr, "the current locale"); + else fprintf(stderr, "all locales"); + fprintf(stderr, "\n"); + } + if (p == s + maxsize) { + errno = ERANGE; + return 0; + } + *p = '\0'; + errno = saved_errno; + return p - s; +} + +static char * +_fmt(const char *format, const struct tm *t, char *pt, + const char *ptlim, enum warn *warnp) +{ + struct lc_time_T const *Locale = &C_time_locale; + + for ( ; *format; ++format) { + if (*format == '%') { +label: + switch (*++format) { + default: + /* Output unknown conversion specifiers as-is, + to aid debugging. This includes '%' at + format end. This conforms to C23 section + 7.29.3.5 paragraph 6, which says behavior + is undefined here. */ + --format; + break; + case 'A': + pt = _add((t->tm_wday < 0 || + t->tm_wday >= DAYSPERWEEK) ? + "?" : Locale->weekday[t->tm_wday], + pt, ptlim); + continue; + case 'a': + pt = _add((t->tm_wday < 0 || + t->tm_wday >= DAYSPERWEEK) ? + "?" : Locale->wday[t->tm_wday], + pt, ptlim); + continue; + case 'B': + pt = _add((t->tm_mon < 0 || + t->tm_mon >= MONSPERYEAR) ? + "?" : Locale->month[t->tm_mon], + pt, ptlim); + continue; + case 'b': + case 'h': + pt = _add((t->tm_mon < 0 || + t->tm_mon >= MONSPERYEAR) ? + "?" : Locale->mon[t->tm_mon], + pt, ptlim); + continue; + case 'C': + /* + ** %C used to do a... + ** _fmt("%a %b %e %X %Y", t); + ** ...whereas now POSIX 1003.2 calls for + ** something completely different. + ** (ado, 1993-05-24) + */ + pt = _yconv(t->tm_year, TM_YEAR_BASE, + true, false, pt, ptlim); + continue; + case 'c': + { + enum warn warn2 = IN_SOME; + + pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2); + if (warn2 == IN_ALL) + warn2 = IN_THIS; + if (warn2 > *warnp) + *warnp = warn2; + } + continue; + case 'D': + pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp); + continue; + case 'd': + pt = _conv(t->tm_mday, "%02d", pt, ptlim); + continue; + case 'E': + case 'O': + /* + ** Locale modifiers of C99 and later. + ** The sequences + ** %Ec %EC %Ex %EX %Ey %EY + ** %Od %oe %OH %OI %Om %OM + ** %OS %Ou %OU %OV %Ow %OW %Oy + ** are supposed to provide alternative + ** representations. + */ + goto label; + case 'e': + pt = _conv(t->tm_mday, "%2d", pt, ptlim); + continue; + case 'F': + pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp); + continue; + case 'H': + pt = _conv(t->tm_hour, "%02d", pt, ptlim); + continue; + case 'I': + pt = _conv((t->tm_hour % 12) ? + (t->tm_hour % 12) : 12, + "%02d", pt, ptlim); + continue; + case 'j': + pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim); + continue; + case 'k': + /* + ** This used to be... + ** _conv(t->tm_hour % 12 ? + ** t->tm_hour % 12 : 12, 2, ' '); + ** ...and has been changed to the below to + ** match SunOS 4.1.1 and Arnold Robbins' + ** strftime version 3.0. That is, "%k" and + ** "%l" have been swapped. + ** (ado, 1993-05-24) + */ + pt = _conv(t->tm_hour, "%2d", pt, ptlim); + continue; +#ifdef KITCHEN_SINK + case 'K': + /* + ** After all this time, still unclaimed! + */ + pt = _add("kitchen sink", pt, ptlim); + continue; +#endif /* defined KITCHEN_SINK */ + case 'l': + /* + ** This used to be... + ** _conv(t->tm_hour, 2, ' '); + ** ...and has been changed to the below to + ** match SunOS 4.1.1 and Arnold Robbin's + ** strftime version 3.0. That is, "%k" and + ** "%l" have been swapped. + ** (ado, 1993-05-24) + */ + pt = _conv((t->tm_hour % 12) ? + (t->tm_hour % 12) : 12, + "%2d", pt, ptlim); + continue; + case 'M': + pt = _conv(t->tm_min, "%02d", pt, ptlim); + continue; + case 'm': + pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim); + continue; + case 'n': + pt = _add("\n", pt, ptlim); + continue; + case 'p': + pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ? + Locale->pm : + Locale->am, + pt, ptlim); + continue; + case 'R': + pt = _fmt("%H:%M", t, pt, ptlim, warnp); + continue; + case 'r': + pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp); + continue; + case 'S': + pt = _conv(t->tm_sec, "%02d", pt, ptlim); + continue; + case 's': + { + struct tm tm; + char buf[INT_STRLEN_MAXIMUM( + time_t) + 1]; + time_t mkt; + + tm.tm_sec = t->tm_sec; + tm.tm_min = t->tm_min; + tm.tm_hour = t->tm_hour; + tm.tm_mday = t->tm_mday; + tm.tm_mon = t->tm_mon; + tm.tm_year = t->tm_year; + + /* Get the time_t value for TM. + Native time_t, or its redefinition + by localtime.c above, is wide enough + so that this cannot overflow. */ +#ifdef TM_GMTOFF + mkt = timeoff(&tm, t->TM_GMTOFF); +#else + tm.tm_isdst = t->tm_isdst; + mkt = mktime(&tm); +#endif + if (TYPE_SIGNED(time_t)) { + intmax_t n = mkt; + sprintf(buf, "%"PRIdMAX, n); + } else { + uintmax_t n = mkt; + sprintf(buf, "%"PRIuMAX, n); + } + pt = _add(buf, pt, ptlim); + } + continue; + case 'T': + pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp); + continue; + case 't': + pt = _add("\t", pt, ptlim); + continue; + case 'U': + pt = _conv((t->tm_yday + DAYSPERWEEK - + t->tm_wday) / DAYSPERWEEK, + "%02d", pt, ptlim); + continue; + case 'u': + /* + ** From Arnold Robbins' strftime version 3.0: + ** "ISO 8601: Weekday as a decimal number + ** [1 (Monday) - 7]" + ** (ado, 1993-05-24) + */ + pt = _conv((t->tm_wday == 0) ? + DAYSPERWEEK : t->tm_wday, + "%d", pt, ptlim); + continue; + case 'V': /* ISO 8601 week number */ + case 'G': /* ISO 8601 year (four digits) */ + case 'g': /* ISO 8601 year (two digits) */ +/* +** From Arnold Robbins' strftime version 3.0: "the week number of the +** year (the first Monday as the first day of week 1) as a decimal number +** (01-53)." +** (ado, 1993-05-24) +** +** From by Markus Kuhn: +** "Week 01 of a year is per definition the first week which has the +** Thursday in this year, which is equivalent to the week which contains +** the fourth day of January. In other words, the first week of a new year +** is the week which has the majority of its days in the new year. Week 01 +** might also contain days from the previous year and the week before week +** 01 of a year is the last week (52 or 53) of the previous year even if +** it contains days from the new year. A week starts with Monday (day 1) +** and ends with Sunday (day 7). For example, the first week of the year +** 1997 lasts from 1996-12-30 to 1997-01-05..." +** (ado, 1996-01-02) +*/ + { + int year; + int base; + int yday; + int wday; + int w; + + year = t->tm_year; + base = TM_YEAR_BASE; + yday = t->tm_yday; + wday = t->tm_wday; + for ( ; ; ) { + int len; + int bot; + int top; + + len = isleap_sum(year, base) ? + DAYSPERLYEAR : + DAYSPERNYEAR; + /* + ** What yday (-3 ... 3) does + ** the ISO year begin on? + */ + bot = ((yday + 11 - wday) % + DAYSPERWEEK) - 3; + /* + ** What yday does the NEXT + ** ISO year begin on? + */ + top = bot - + (len % DAYSPERWEEK); + if (top < -3) + top += DAYSPERWEEK; + top += len; + if (yday >= top) { + ++base; + w = 1; + break; + } + if (yday >= bot) { + w = 1 + ((yday - bot) / + DAYSPERWEEK); + break; + } + --base; + yday += isleap_sum(year, base) ? + DAYSPERLYEAR : + DAYSPERNYEAR; + } +#ifdef XPG4_1994_04_09 + if ((w == 52 && + t->tm_mon == TM_JANUARY) || + (w == 1 && + t->tm_mon == TM_DECEMBER)) + w = 53; +#endif /* defined XPG4_1994_04_09 */ + if (*format == 'V') + pt = _conv(w, "%02d", + pt, ptlim); + else if (*format == 'g') { + *warnp = IN_ALL; + pt = _yconv(year, base, + false, true, + pt, ptlim); + } else pt = _yconv(year, base, + true, true, + pt, ptlim); + } + continue; + case 'v': + /* + ** From Arnold Robbins' strftime version 3.0: + ** "date as dd-bbb-YYYY" + ** (ado, 1993-05-24) + */ + pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp); + continue; + case 'W': + pt = _conv((t->tm_yday + DAYSPERWEEK - + (t->tm_wday ? + (t->tm_wday - 1) : + (DAYSPERWEEK - 1))) / DAYSPERWEEK, + "%02d", pt, ptlim); + continue; + case 'w': + pt = _conv(t->tm_wday, "%d", pt, ptlim); + continue; + case 'X': + pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp); + continue; + case 'x': + { + enum warn warn2 = IN_SOME; + + pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2); + if (warn2 == IN_ALL) + warn2 = IN_THIS; + if (warn2 > *warnp) + *warnp = warn2; + } + continue; + case 'y': + *warnp = IN_ALL; + pt = _yconv(t->tm_year, TM_YEAR_BASE, + false, true, + pt, ptlim); + continue; + case 'Y': + pt = _yconv(t->tm_year, TM_YEAR_BASE, + true, true, + pt, ptlim); + continue; + case 'Z': +#ifdef TM_ZONE + pt = _add(t->TM_ZONE, pt, ptlim); +#elif HAVE_TZNAME + if (t->tm_isdst >= 0) + pt = _add(tzname[t->tm_isdst != 0], + pt, ptlim); +#endif + /* + ** C99 and later say that %Z must be + ** replaced by the empty string if the + ** time zone abbreviation is not + ** determinable. + */ + continue; + case 'z': +#if defined TM_GMTOFF || USG_COMPAT || ALTZONE + { + long diff; + char const * sign; + bool negative; + +# ifdef TM_GMTOFF + diff = t->TM_GMTOFF; +# else + /* + ** C99 and later say that the UT offset must + ** be computed by looking only at + ** tm_isdst. This requirement is + ** incorrect, since it means the code + ** must rely on magic (in this case + ** altzone and timezone), and the + ** magic might not have the correct + ** offset. Doing things correctly is + ** tricky and requires disobeying the standard; + ** see GNU C strftime for details. + ** For now, punt and conform to the + ** standard, even though it's incorrect. + ** + ** C99 and later say that %z must be replaced by + ** the empty string if the time zone is not + ** determinable, so output nothing if the + ** appropriate variables are not available. + */ + if (t->tm_isdst < 0) + continue; + if (t->tm_isdst == 0) +# if USG_COMPAT + diff = -timezone; +# else + continue; +# endif + else +# if ALTZONE + diff = -altzone; +# else + continue; +# endif +# endif + negative = diff < 0; + if (diff == 0) { +# ifdef TM_ZONE + negative = t->TM_ZONE[0] == '-'; +# else + negative = t->tm_isdst < 0; +# if HAVE_TZNAME + if (tzname[t->tm_isdst != 0][0] == '-') + negative = true; +# endif +# endif + } + if (negative) { + sign = "-"; + diff = -diff; + } else sign = "+"; + pt = _add(sign, pt, ptlim); + diff /= SECSPERMIN; + diff = (diff / MINSPERHOUR) * 100 + + (diff % MINSPERHOUR); + pt = _conv(diff, "%04d", pt, ptlim); + } +#endif + continue; + case '+': + pt = _fmt(Locale->date_fmt, t, pt, ptlim, + warnp); + continue; + case '%': + break; + } + } + if (pt == ptlim) + break; + *pt++ = *format; + } + return pt; +} + +static char * +_conv(int n, const char *format, char *pt, const char *ptlim) +{ + char buf[INT_STRLEN_MAXIMUM(int) + 1]; + + sprintf(buf, format, n); + return _add(buf, pt, ptlim); +} + +static char * +_add(const char *str, char *pt, const char *ptlim) +{ + while (pt < ptlim && (*pt = *str++) != '\0') + ++pt; + return pt; +} + +/* +** POSIX and the C Standard are unclear or inconsistent about +** what %C and %y do if the year is negative or exceeds 9999. +** Use the convention that %C concatenated with %y yields the +** same output as %Y, and that %Y contains at least 4 bytes, +** with more only if necessary. +*/ + +static char * +_yconv(int a, int b, bool convert_top, bool convert_yy, + char *pt, const char *ptlim) +{ + register int lead; + register int trail; + + int DIVISOR = 100; + trail = a % DIVISOR + b % DIVISOR; + lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR; + trail %= DIVISOR; + if (trail < 0 && lead > 0) { + trail += DIVISOR; + --lead; + } else if (lead < 0 && trail > 0) { + trail -= DIVISOR; + ++lead; + } + if (convert_top) { + if (lead == 0 && trail < 0) + pt = _add("-0", pt, ptlim); + else pt = _conv(lead, "%02d", pt, ptlim); + } + if (convert_yy) + pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim); + return pt; +} diff --git a/source/os/src/timezone/tzdir.h b/source/os/src/timezone/tzdir.h new file mode 100644 index 0000000000..dc5668ec39 --- /dev/null +++ b/source/os/src/timezone/tzdir.h @@ -0,0 +1,6 @@ +#ifndef TZDEFAULT +# define TZDEFAULT "/etc/localtime" /* default zone */ +#endif +#ifndef TZDIR +# define TZDIR "/Users/mingmingwanng/source_code/TDengine/debug/build/share/timezone/" /* TZif directory */ +#endif diff --git a/source/os/src/timezone/tzfile.h b/source/os/src/timezone/tzfile.h new file mode 100644 index 0000000000..f8c6c8c550 --- /dev/null +++ b/source/os/src/timezone/tzfile.h @@ -0,0 +1,121 @@ +/* Layout and location of TZif files. */ + +#ifndef TZFILE_H + +#define TZFILE_H + +/* +** This file is in the public domain, so clarified as of +** 1996-06-05 by Arthur David Olson. +*/ + +/* +** This header is for use ONLY with the time conversion code. +** There is no guarantee that it will remain unchanged, +** or that it will remain at all. +** Do NOT copy it to any system include directory. +** Thank you! +*/ + +/* +** Information about time zone files. +*/ + +#ifndef TZDEFRULES +# define TZDEFRULES "posixrules" +#endif /* !defined TZDEFRULES */ + + +/* See Internet RFC 9636 for more details about the following format. */ + +/* +** Each file begins with. . . +*/ + +#define TZ_MAGIC "TZif" + +struct tzhead { + char tzh_magic[4]; /* TZ_MAGIC */ + char tzh_version[1]; /* '\0' or '2'-'4' as of 2021 */ + char tzh_reserved[15]; /* reserved; must be zero */ + char tzh_ttisutcnt[4]; /* coded number of trans. time flags */ + char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */ + char tzh_leapcnt[4]; /* coded number of leap seconds */ + char tzh_timecnt[4]; /* coded number of transition times */ + char tzh_typecnt[4]; /* coded number of local time types */ + char tzh_charcnt[4]; /* coded number of abbr. chars */ +}; + +/* +** . . .followed by. . . +** +** tzh_timecnt (char [4])s coded transition times a la time(2) +** tzh_timecnt (unsigned char)s types of local time starting at above +** tzh_typecnt repetitions of +** one (char [4]) coded UT offset in seconds +** one (unsigned char) used to set tm_isdst +** one (unsigned char) that's an abbreviation list index +** tzh_charcnt (char)s '\0'-terminated zone abbreviations +** tzh_leapcnt repetitions of +** one (char [4]) coded leap second transition times +** one (char [4]) total correction after above +** tzh_ttisstdcnt (char)s indexed by type; if 1, transition +** time is standard time, if 0, +** transition time is local (wall clock) +** time; if absent, transition times are +** assumed to be local time +** tzh_ttisutcnt (char)s indexed by type; if 1, transition +** time is UT, if 0, transition time is +** local time; if absent, transition +** times are assumed to be local time. +** When this is 1, the corresponding +** std/wall indicator must also be 1. +*/ + +/* +** If tzh_version is '2' or greater, the above is followed by a second instance +** of tzhead and a second instance of the data in which each coded transition +** time uses 8 rather than 4 chars, +** then a POSIX.1-2017 proleptic TZ string for use in handling +** instants after the last transition time stored in the file +** (with nothing between the newlines if there is no POSIX.1-2017 +** representation for such instants). +** +** If tz_version is '3' or greater, the TZ string can be any POSIX.1-2024 +** proleptic TZ string, which means the above is extended as follows. +** First, the TZ string's hour offset may range from -167 +** through 167 as compared to the range 0 through 24 required +** by POSIX.1-2017 and earlier. +** Second, its DST start time may be January 1 at 00:00 and its stop +** time December 31 at 24:00 plus the difference between DST and +** standard time, indicating DST all year. +*/ + +/* +** In the current implementation, "tzset()" refuses to deal with files that +** exceed any of the limits below. +*/ + +#ifndef TZ_MAX_TIMES +/* This must be at least 242 for Europe/London with 'zic -b fat'. */ +# define TZ_MAX_TIMES 2000 +#endif /* !defined TZ_MAX_TIMES */ + +#ifndef TZ_MAX_TYPES +/* This must be at least 18 for Europe/Vilnius with 'zic -b fat'. */ +# define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */ +#endif /* !defined TZ_MAX_TYPES */ + +#ifndef TZ_MAX_CHARS +/* This must be at least 40 for America/Anchorage. */ +# define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */ + /* (limited by what unsigned chars can hold) */ +#endif /* !defined TZ_MAX_CHARS */ + +#ifndef TZ_MAX_LEAPS +/* This must be at least 27 for leap seconds from 1972 through mid-2023. + There's a plan to discontinue leap seconds by 2035. */ +# define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */ +#endif /* !defined TZ_MAX_LEAPS */ + +#endif /* !defined TZFILE_H */ diff --git a/utils/test/c/timezone_test.c b/utils/test/c/timezone_test.c index f8bedc62cc..5a3fa87976 100644 --- a/utils/test/c/timezone_test.c +++ b/utils/test/c/timezone_test.c @@ -78,7 +78,6 @@ void timezone_insert_test(){ /* select today(); - select cast(1 as timestamp) + '2013-04-12T10:52:01'; tsConver.sim "COMPACT DATABASE test START WITH '2023-03-07 14:01:23' END WITH '2023-03-08 14:01:23'" TK_TIMEZONE From a9d73d7b059a743a2a37c79945b2cf2a0fd10c30 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Tue, 26 Nov 2024 00:02:17 +0800 Subject: [PATCH 06/45] feat:[TD-32642] add timezone logic --- source/libs/scalar/src/sclvector.c | 8 ++++++-- source/os/src/osTimezone.c | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/source/libs/scalar/src/sclvector.c b/source/libs/scalar/src/sclvector.c index d9becf199a..c69501d50b 100644 --- a/source/libs/scalar/src/sclvector.c +++ b/source/libs/scalar/src/sclvector.c @@ -2128,7 +2128,9 @@ int32_t vectorIsNull(SScalarParam *pLeft, SScalarParam *pRight, SScalarParam *pO } int32_t vectorNotNull(SScalarParam *pLeft, SScalarParam *pRight, SScalarParam *pOut, int32_t _ord) { - pRight->tz = pLeft->tz; + if (pRight != NULL) { + pRight->tz = pLeft->tz; + } for (int32_t i = 0; i < pLeft->numOfRows; ++i) { int8_t v = IS_HELPER_NULL(pLeft->columnData, i) ? 0 : 1; if (v) { @@ -2142,7 +2144,9 @@ int32_t vectorNotNull(SScalarParam *pLeft, SScalarParam *pRight, SScalarParam *p } int32_t vectorIsTrue(SScalarParam *pLeft, SScalarParam *pRight, SScalarParam *pOut, int32_t _ord) { - pRight->tz = pLeft->tz; + if (pRight != NULL) { + pRight->tz = pLeft->tz; + } SCL_ERR_RET(vectorConvertSingleColImpl(pLeft, pOut, NULL, -1, -1)); for (int32_t i = 0; i < pOut->numOfRows; ++i) { if (colDataIsNull_s(pOut->columnData, i)) { diff --git a/source/os/src/osTimezone.c b/source/os/src/osTimezone.c index 5511043cd2..1f2210fbe1 100644 --- a/source/os/src/osTimezone.c +++ b/source/os/src/osTimezone.c @@ -873,7 +873,7 @@ void getTimezoneStr(char *tz) { break; } zi += sizeof("zoneinfo"); - memcpy(tz, zi, TD_TIMEZONE_LEN - (zi - tz)); + memmove(tz, zi, TD_TIMEZONE_LEN - (zi - tz)); goto END; } while (0); From 0e2a16d70f164751fd87516d13a8ba3d4289df16 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Wed, 27 Nov 2024 14:25:34 +0800 Subject: [PATCH 07/45] feat:[TD-32642] add charset for connection support --- include/common/tdataformat.h | 2 +- include/common/tmsg.h | 2 +- include/common/ttime.h | 2 +- include/libs/function/function.h | 1 + include/libs/nodes/querynodes.h | 4 + include/libs/parser/parser.h | 25 ++-- include/libs/qcom/query.h | 2 +- include/libs/scalar/scalar.h | 1 - include/os/osEnv.h | 6 +- include/os/osLocale.h | 2 +- include/os/osString.h | 24 +++- include/os/osTimezone.h | 29 +--- include/util/tconv.h | 41 ++++++ source/client/inc/clientInt.h | 3 +- source/client/src/clientEnv.c | 9 +- source/client/src/clientImpl.c | 15 ++- source/client/src/clientMain.c | 96 +++++++------ source/client/src/clientRawBlockWrite.c | 4 +- source/client/src/clientSml.c | 10 +- source/client/src/clientStmt.c | 10 +- source/client/src/clientStmt2.c | 10 +- source/common/src/tdatablock.c | 2 +- source/common/src/tglobal.c | 47 +------ source/common/src/ttime.c | 10 +- source/common/test/commonTests.cpp | 6 +- source/dnode/mgmt/exe/dmMain.c | 3 +- source/dnode/mgmt/node_mgmt/src/dmMgmt.c | 1 + source/dnode/mnode/impl/inc/mndDef.h | 2 +- source/dnode/mnode/impl/src/mndSma.c | 8 +- source/dnode/vnode/src/meta/metaQuery.c | 2 +- source/dnode/vnode/src/meta/metaTable.c | 4 +- source/libs/catalog/src/catalog.c | 2 +- source/libs/catalog/src/ctgAsync.c | 2 +- source/libs/command/src/command.c | 2 +- source/libs/executor/src/sysscanoperator.c | 4 +- source/libs/executor/test/queryPlanTests.cpp | 2 +- source/libs/executor/test/sortTests.cpp | 4 +- source/libs/function/src/builtins.c | 2 +- source/libs/parser/inc/parUtil.h | 2 +- source/libs/parser/src/parAstCreater.c | 9 ++ source/libs/parser/src/parInsertSml.c | 14 +- source/libs/parser/src/parInsertSql.c | 18 +-- source/libs/parser/src/parInsertStmt.c | 48 +++---- source/libs/parser/src/parTranslater.c | 14 +- source/libs/parser/src/parUtil.c | 4 +- source/libs/parser/src/parser.c | 20 +-- source/libs/planner/src/planOptimizer.c | 2 +- source/libs/planner/test/planTestUtil.cpp | 2 +- source/libs/qcom/src/queryUtil.c | 6 +- source/libs/scalar/src/filter.c | 8 +- source/libs/scalar/src/scalar.c | 7 +- source/libs/scalar/src/sclfunc.c | 112 ++++++++-------- source/libs/scalar/src/sclvector.c | 26 ++-- .../libs/scalar/test/scalar/scalarTests.cpp | 6 +- source/os/src/osEnv.c | 8 +- source/os/src/osLocale.c | 63 +-------- source/os/src/osString.c | 126 ++++++------------ source/os/src/osTimezone.c | 10 ++ source/util/src/tcompare.c | 4 +- source/util/src/tconfig.c | 52 +++++++- source/util/src/tconv.c | 123 +++++++++++++++++ 61 files changed, 598 insertions(+), 487 deletions(-) create mode 100644 include/util/tconv.h create mode 100644 source/util/src/tconv.c diff --git a/include/common/tdataformat.h b/include/common/tdataformat.h index f899fc5589..cb05f98f45 100644 --- a/include/common/tdataformat.h +++ b/include/common/tdataformat.h @@ -154,7 +154,7 @@ int32_t tEncodeTag(SEncoder *pEncoder, const STag *pTag); int32_t tDecodeTag(SDecoder *pDecoder, STag **ppTag); int32_t tTagToValArray(const STag *pTag, SArray **ppArray); void debugPrintSTag(STag *pTag, const char *tag, int32_t ln); // TODO: remove -int32_t parseJsontoTagData(const char *json, SArray *pTagVals, STag **ppTag, void *pMsgBuf); +int32_t parseJsontoTagData(const char *json, SArray *pTagVals, STag **ppTag, void *pMsgBuf, void *charsetCxt); // SColData ================================ typedef struct { diff --git a/include/common/tmsg.h b/include/common/tmsg.h index a110e10351..42301773e9 100644 --- a/include/common/tmsg.h +++ b/include/common/tmsg.h @@ -3842,7 +3842,7 @@ typedef struct { int8_t igExists; int8_t intervalUnit; int8_t slidingUnit; - int8_t timezone; + int8_t timezone; // int8_t is not enough, timezone is unit of second int32_t dstVgId; // for stream int64_t interval; int64_t offset; diff --git a/include/common/ttime.h b/include/common/ttime.h index 41dd0bf6be..85ca5ae1e6 100644 --- a/include/common/ttime.h +++ b/include/common/ttime.h @@ -74,7 +74,7 @@ char getPrecisionUnit(int32_t precision); int64_t convertTimePrecision(int64_t ts, int32_t fromPrecision, int32_t toPrecision); int32_t convertTimeFromPrecisionToUnit(int64_t time, int32_t fromPrecision, char toUnit, int64_t* pRes); -int32_t convertStringToTimestamp(int16_t type, char* inputData, int64_t timePrec, int64_t* timeVal, timezone_t tz); +int32_t convertStringToTimestamp(int16_t type, char* inputData, int64_t timePrec, int64_t* timeVal, timezone_t tz, void* charsetCxt); int32_t getDuration(int64_t val, char unit, int64_t* result, int32_t timePrecision); int32_t taosFormatUtcTime(char* buf, int32_t bufLen, int64_t ts, int32_t precision); diff --git a/include/libs/function/function.h b/include/libs/function/function.h index cd3ad7597e..824b9cdada 100644 --- a/include/libs/function/function.h +++ b/include/libs/function/function.h @@ -293,6 +293,7 @@ struct SScalarParam { int32_t numOfRows; int32_t numOfQualified; // number of qualified elements in the final results timezone_t tz; + void *charsetCxt; }; #define cleanupResultRowEntry(p) p->initialized = false diff --git a/include/libs/nodes/querynodes.h b/include/libs/nodes/querynodes.h index e559d136e1..9da6117a0e 100644 --- a/include/libs/nodes/querynodes.h +++ b/include/libs/nodes/querynodes.h @@ -132,6 +132,7 @@ typedef struct SValueNode { int64_t typeData; int8_t unit; timezone_t tz; + void *charsetCxt; } SValueNode; typedef struct SLeftValueNode { @@ -161,6 +162,7 @@ typedef struct SOperatorNode { SNode* pLeft; SNode* pRight; timezone_t tz; + void* charsetCxt; } SOperatorNode; typedef struct SLogicConditionNode { @@ -195,6 +197,7 @@ typedef struct SFunctionNode { bool dual; // whether select stmt without from stmt, true for without. // char timezone[TD_TIMEZONE_LEN]; timezone_t tz; + void *charsetCxt; } SFunctionNode; typedef struct STableNode { @@ -404,6 +407,7 @@ typedef struct SCaseWhenNode { SNode* pElse; SNodeList* pWhenThenList; timezone_t tz; + void* charsetCxt; } SCaseWhenNode; typedef struct SWindowOffsetNode { diff --git a/include/libs/parser/parser.h b/include/libs/parser/parser.h index de390c6dd2..ebf84cff57 100644 --- a/include/libs/parser/parser.h +++ b/include/libs/parser/parser.h @@ -102,6 +102,7 @@ typedef struct SParseContext { SArray* pSubMetaList; setQueryFn setQueryFp; timezone_t timezone; + void *charsetCxt; } SParseContext; int32_t qParseSql(SParseContext* pCxt, SQuery** pQuery); @@ -140,27 +141,27 @@ void qDestroyStmtDataBlock(STableDataCxt* pBlock); STableMeta* qGetTableMetaInDataBlock(STableDataCxt* pDataBlock); int32_t qCloneCurrentTbData(STableDataCxt* pDataBlock, SSubmitTbData** pData); -int32_t qStmtBindParams(SQuery* pQuery, TAOS_MULTI_BIND* pParams, int32_t colIdx); +int32_t qStmtBindParams(SQuery* pQuery, TAOS_MULTI_BIND* pParams, int32_t colIdx, void *charsetCxt); int32_t qStmtParseQuerySql(SParseContext* pCxt, SQuery* pQuery); int32_t qBindStmtStbColsValue(void* pBlock, SArray* pCols, TAOS_MULTI_BIND* bind, char* msgBuf, int32_t msgBufLen, - STSchema** pTSchema, SBindInfo* pBindInfos); -int32_t qBindStmtColsValue(void* pBlock, SArray* pCols, TAOS_MULTI_BIND* bind, char* msgBuf, int32_t msgBufLen); + STSchema** pTSchema, SBindInfo* pBindInfos, void* charsetCxt); +int32_t qBindStmtColsValue(void* pBlock, SArray* pCols, TAOS_MULTI_BIND* bind, char* msgBuf, int32_t msgBufLen, void* charsetCxt); int32_t qBindStmtSingleColValue(void* pBlock, SArray* pCols, TAOS_MULTI_BIND* bind, char* msgBuf, int32_t msgBufLen, - int32_t colIdx, int32_t rowNum); + int32_t colIdx, int32_t rowNum, void* charsetCxt); int32_t qBuildStmtColFields(void* pDataBlock, int32_t* fieldNum, TAOS_FIELD_E** fields); int32_t qBuildStmtStbColFields(void* pBlock, int32_t* fieldNum, TAOS_FIELD_STB** fields); int32_t qBuildStmtTagFields(void* pBlock, void* boundTags, int32_t* fieldNum, TAOS_FIELD_E** fields); int32_t qBindStmtTagsValue(void* pBlock, void* boundTags, int64_t suid, const char* sTableName, char* tName, - TAOS_MULTI_BIND* bind, char* msgBuf, int32_t msgBufLen); + TAOS_MULTI_BIND* bind, char* msgBuf, int32_t msgBufLen, void* charsetCxt); -int32_t qStmtBindParams2(SQuery* pQuery, TAOS_STMT2_BIND* pParams, int32_t colIdx); +int32_t qStmtBindParams2(SQuery* pQuery, TAOS_STMT2_BIND* pParams, int32_t colIdx, void* charsetCxt); int32_t qBindStmtStbColsValue2(void* pBlock, SArray* pCols, TAOS_STMT2_BIND* bind, char* msgBuf, int32_t msgBufLen, - STSchema** pTSchema, SBindInfo2* pBindInfos); -int32_t qBindStmtColsValue2(void* pBlock, SArray* pCols, TAOS_STMT2_BIND* bind, char* msgBuf, int32_t msgBufLen); + STSchema** pTSchema, SBindInfo2* pBindInfos, void *charsetCxt); +int32_t qBindStmtColsValue2(void* pBlock, SArray* pCols, TAOS_STMT2_BIND* bind, char* msgBuf, int32_t msgBufLen, void *charsetCxt); int32_t qBindStmtSingleColValue2(void* pBlock, SArray* pCols, TAOS_STMT2_BIND* bind, char* msgBuf, int32_t msgBufLen, - int32_t colIdx, int32_t rowNum); + int32_t colIdx, int32_t rowNum, void *charsetCxt); int32_t qBindStmtTagsValue2(void* pBlock, void* boundTags, int64_t suid, const char* sTableName, char* tName, - TAOS_STMT2_BIND* bind, char* msgBuf, int32_t msgBufLen); + TAOS_STMT2_BIND* bind, char* msgBuf, int32_t msgBufLen, void *charsetCxt); void destroyBoundColumnInfo(void* pBoundInfo); int32_t qCreateSName(SName* pName, const char* pTableName, int32_t acctId, char* dbName, char* msgBuf, @@ -170,13 +171,13 @@ void qDestroyBoundColInfo(void* pInfo); int32_t smlInitHandle(SQuery** query); int32_t smlBuildRow(STableDataCxt* pTableCxt); -int32_t smlBuildCol(STableDataCxt* pTableCxt, SSchema* schema, void* kv, int32_t index); +int32_t smlBuildCol(STableDataCxt* pTableCxt, SSchema* schema, void* kv, int32_t index, void* charsetCxt); int32_t smlInitTableDataCtx(SQuery* query, STableMeta* pTableMeta, STableDataCxt** cxt); void clearColValArraySml(SArray* pCols); int32_t smlBindData(SQuery* handle, bool dataFormat, SArray* tags, SArray* colsSchema, SArray* cols, STableMeta* pTableMeta, char* tableName, const char* sTableName, int32_t sTableNameLen, int32_t ttl, - char* msgBuf, int32_t msgBufLen); + char* msgBuf, int32_t msgBufLen, void* charsetCxt); int32_t smlBuildOutput(SQuery* handle, SHashObj* pVgHash); int rawBlockBindData(SQuery* query, STableMeta* pTableMeta, void* data, SVCreateTbReq* pCreateTb, void* fields, int numFields, bool needChangeLength, char* errstr, int32_t errstrLen, bool raw); diff --git a/include/libs/qcom/query.h b/include/libs/qcom/query.h index d2f714f400..de2588af0c 100644 --- a/include/libs/qcom/query.h +++ b/include/libs/qcom/query.h @@ -337,7 +337,7 @@ SSchema createSchema(int8_t type, int32_t bytes, col_id_t colId, const char* nam void destroyQueryExecRes(SExecResult* pRes); int32_t dataConverToStr(char* str, int64_t capacity, int type, void* buf, int32_t bufSize, int32_t* len); -void parseTagDatatoJson(void* p, char** jsonStr); +void parseTagDatatoJson(void* p, char** jsonStr, void *charsetCxt); int32_t cloneTableMeta(STableMeta* pSrc, STableMeta** pDst); void getColumnTypeFromMeta(STableMeta* pMeta, char* pName, ETableColumnType* pType); int32_t cloneDbVgInfo(SDBVgInfo* pSrc, SDBVgInfo** pDst); diff --git a/include/libs/scalar/scalar.h b/include/libs/scalar/scalar.h index 4b89a6a439..fd936dd087 100644 --- a/include/libs/scalar/scalar.h +++ b/include/libs/scalar/scalar.h @@ -105,7 +105,6 @@ int32_t timeTruncateFunction(SScalarParam *pInput, int32_t inputNum, SScalarPara int32_t timeDiffFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput); int32_t nowFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput); int32_t todayFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput); -int32_t timeZoneStrLen(); int32_t timezoneFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput); int32_t weekdayFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput); int32_t dayofweekFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput); diff --git a/include/os/osEnv.h b/include/os/osEnv.h index f235d3f235..85fdb7c38c 100644 --- a/include/os/osEnv.h +++ b/include/os/osEnv.h @@ -25,10 +25,9 @@ extern "C" { extern char tsOsName[]; extern char tsTimezoneStr[]; -extern enum TdTimezone tsTimezone; extern char tsCharset[]; +extern void *tsCharsetCxt; extern char tsLocale[]; -extern int8_t tsDaylight; extern bool tsEnableCoreFile; extern int64_t tsPageSizeKB; extern int64_t tsOpenMax; @@ -67,8 +66,7 @@ bool osDataSpaceSufficient(); bool osTempSpaceSufficient(); int32_t osSetTimezone(const char *timezone); -void osSetSystemLocale(const char *inLocale, const char *inCharSet); -void osSetProcPath(int32_t argc, char **argv); +void osSetProcPath(int32_t argc, char **argv); #ifdef __cplusplus } diff --git a/include/os/osLocale.h b/include/os/osLocale.h index 5f2a1c35de..d7a3540887 100644 --- a/include/os/osLocale.h +++ b/include/os/osLocale.h @@ -30,7 +30,7 @@ extern "C" { char *taosCharsetReplace(char *charsetstr); void taosGetSystemLocale(char *outLocale, char *outCharset); -int32_t taosSetSystemLocale(const char *inLocale, const char *inCharSet); +int32_t taosSetSystemLocale(const char *inLocale); #ifdef __cplusplus } diff --git a/include/os/osString.h b/include/os/osString.h index 995aa4a591..810c59131e 100644 --- a/include/os/osString.h +++ b/include/os/osString.h @@ -27,7 +27,19 @@ typedef int32_t TdUcs4; #else typedef void *iconv_t; #endif -typedef enum { M2C = 0, C2M } ConvType; +typedef enum { M2C = 0, C2M, CM_NUM } ConvType; + +typedef struct { + iconv_t conv; + int8_t inUse; +} SConv; + +typedef struct { + SConv *gConv[CM_NUM]; + int32_t convUsed[CM_NUM]; + int32_t gConvMaxNum[CM_NUM]; + char charset[TD_CHARSET_LEN]; +} SConvInfo; // If the error is in a third-party library, place this header file under the third-party library header file. // When you want to use this feature, you should find or add the same function in the following section. @@ -78,13 +90,11 @@ int32_t taosStr2int16(const char *str, int16_t *val); int32_t taosStr2int32(const char *str, int32_t *val); int32_t taosStr2int8(const char *str, int8_t *val); -int32_t taosConvInit(void); -void taosConvDestroy(); -iconv_t taosAcquireConv(int32_t *idx, ConvType type); -void taosReleaseConv(int32_t idx, iconv_t conv, ConvType type); -int32_t taosUcs4ToMbs(TdUcs4 *ucs4, int32_t ucs4_max_len, char *mbs); +iconv_t taosAcquireConv(int32_t *idx, ConvType type, void* charsetCxt); +void taosReleaseConv(int32_t idx, iconv_t conv, ConvType type, void* charsetCxt); +int32_t taosUcs4ToMbs(TdUcs4 *ucs4, int32_t ucs4_max_len, char *mbs, void* charsetCxt); int32_t taosUcs4ToMbsEx(TdUcs4 *ucs4, int32_t ucs4_max_len, char *mbs, iconv_t conv); -bool taosMbsToUcs4(const char *mbs, size_t mbs_len, TdUcs4 *ucs4, int32_t ucs4_max_len, int32_t *len); +bool taosMbsToUcs4(const char *mbs, size_t mbs_len, TdUcs4 *ucs4, int32_t ucs4_max_len, int32_t *len, void* charsetCxt); int32_t tasoUcs4Compare(TdUcs4 *f1_ucs4, TdUcs4 *f2_ucs4, int32_t bytes); int32_t tasoUcs4Copy(TdUcs4 *target_ucs4, TdUcs4 *source_ucs4, int32_t len_ucs4); bool taosValidateEncodec(const char *encodec); diff --git a/include/os/osTimezone.h b/include/os/osTimezone.h index 2e3d27212f..7057ced35a 100644 --- a/include/os/osTimezone.h +++ b/include/os/osTimezone.h @@ -20,33 +20,7 @@ extern "C" { #endif -enum TdTimezone { - TdWestZone12 = -12, - TdWestZone11, - TdWestZone10, - TdWestZone9, - TdWestZone8, - TdWestZone7, - TdWestZone6, - TdWestZone5, - TdWestZone4, - TdWestZone3, - TdWestZone2, - TdWestZone1, - TdZeroZone, - TdEastZone1, - TdEastZone2, - TdEastZone3, - TdEastZone4, - TdEastZone5, - TdEastZone6, - TdEastZone7, - TdEastZone8, - TdEastZone9, - TdEastZone10, - TdEastZone11, - TdEastZone12 -}; +#define TdEastZone8 8*60*60 typedef struct state *timezone_t; struct tm *localtime_rz(timezone_t , time_t const *, struct tm *); @@ -55,6 +29,7 @@ timezone_t tzalloc(char const *); void tzfree(timezone_t); void getTimezoneStr(char *tz); +int32_t taosGetLocalTimezoneOffset(); int32_t taosGetSystemTimezone(char *outTimezone); int32_t taosSetGlobalTimezone(const char *tz); int32_t taosFormatTimezoneStr(time_t t, const char* tzStr, timezone_t sp, char *outTimezoneStr); diff --git a/include/util/tconv.h b/include/util/tconv.h new file mode 100644 index 0000000000..c886b38f51 --- /dev/null +++ b/include/util/tconv.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 TAOS Data, Inc. + * + * This program is free software: you can use, redistribute, and/or modify + * it under the terms of the GNU Affero General Public License, version 3 + * or later ("AGPL"), as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#ifndef TDENGINE_TCONV_H +#define TDENGINE_TCONV_H + +#ifdef __cplusplus +extern "C" { +#endif + +//#include "osString.h" +// +//bool taosValidateEncodec(const char *encodec); +//int32_t taosUcs4len(TdUcs4 *ucs4); +void* taosConvInit(const char* charset); +void taosConvDestroy(); +//iconv_t taosAcquireConv(int32_t *idx, ConvType type, void* charsetCxt); +//void taosReleaseConv(int32_t idx, iconv_t conv, ConvType type, void* charsetCxt); +//int32_t taosUcs4ToMbs(TdUcs4 *ucs4, int32_t ucs4_max_len, char *mbs, void* charsetCxt); +//int32_t taosUcs4ToMbsEx(TdUcs4 *ucs4, int32_t ucs4_max_len, char *mbs, iconv_t conv); +//bool taosMbsToUcs4(const char *mbs, size_t mbs_len, TdUcs4 *ucs4, int32_t ucs4_max_len, int32_t *len, void* charsetCxt); +//int32_t tasoUcs4Compare(TdUcs4 *f1_ucs4, TdUcs4 *f2_ucs4, int32_t bytes); +//int32_t tasoUcs4Copy(TdUcs4 *target_ucs4, TdUcs4 *source_ucs4, int32_t len_ucs4); + +#ifdef __cplusplus +} +#endif + +#endif // TDENGINE_TCONV_H diff --git a/source/client/inc/clientInt.h b/source/client/inc/clientInt.h index 46cc8a869c..a6ab0f8489 100644 --- a/source/client/inc/clientInt.h +++ b/source/client/inc/clientInt.h @@ -155,7 +155,7 @@ typedef struct { typedef struct { timezone_t timezone; - char charset[TD_CHARSET_LEN]; + void *charsetCxt; char app[TSDB_APP_NAME_LEN]; uint32_t ip; }SOptionInfo; @@ -219,6 +219,7 @@ typedef struct SReqResultInfo { int32_t precision; int32_t payloadLen; char* convertJson; + void* charsetCxt; } SReqResultInfo; typedef struct SRequestSendRecvBody { diff --git a/source/client/src/clientEnv.c b/source/client/src/clientEnv.c index dbc623214e..b67d0daf73 100644 --- a/source/client/src/clientEnv.c +++ b/source/client/src/clientEnv.c @@ -36,6 +36,7 @@ #include "tsched.h" #include "ttime.h" #include "tversion.h" +#include "tconv.h" #if defined(CUS_NAME) || defined(CUS_PROMPT) || defined(CUS_EMAIL) #include "cus_name.h" @@ -556,6 +557,7 @@ int32_t createRequest(uint64_t connId, int32_t type, int64_t reqid, SRequestObj (*pRequest)->metric.start = taosGetTimestampUs(); (*pRequest)->body.resInfo.convertUcs4 = true; // convert ucs4 by default + (*pRequest)->body.resInfo.charsetCxt = pTscObj->optionInfo.charsetCxt; (*pRequest)->type = type; (*pRequest)->allocatorRefId = -1; @@ -969,7 +971,12 @@ void taos_init_imp(void) { ENV_ERR_RET(taosInitCfg(configDir, NULL, NULL, NULL, NULL, 1), "failed to init cfg"); initQueryModuleMsgHandle(); - ENV_ERR_RET(taosConvInit(), "failed to init conv"); + if ((tsCharsetCxt = taosConvInit(tsCharset)) == NULL){ + tscInitRes = terrno; + tscError("failed to init conv"); + return; + } + ENV_ERR_RET(monitorInit(), "failed to init monitor"); ENV_ERR_RET(rpcInit(), "failed to init rpc"); diff --git a/source/client/src/clientImpl.c b/source/client/src/clientImpl.c index 0620145bcc..26bef9301e 100644 --- a/source/client/src/clientImpl.c +++ b/source/client/src/clientImpl.c @@ -301,7 +301,8 @@ int32_t parseSql(SRequestObj* pRequest, bool topicQuery, SQuery** pQuery, SStmtC .nodeOffline = (pTscObj->pAppInfo->onlineDnodes < pTscObj->pAppInfo->totalDnodes), .isStmtBind = pRequest->isStmtBind, .setQueryFp = setQueryRequest, - .timezone = pTscObj->optionInfo.timezone,}; + .timezone = pTscObj->optionInfo.timezone, + .charsetCxt = pTscObj->optionInfo.charsetCxt,}; cxt.mgmtEpSet = getEpSet_s(&pTscObj->pAppInfo->mgmtEp); int32_t code = catalogGetHandle(pTscObj->pAppInfo->clusterId, &cxt.pCatalog); @@ -2087,7 +2088,7 @@ static int32_t doPrepareResPtr(SReqResultInfo* pResInfo) { static int32_t doConvertUCS4(SReqResultInfo* pResultInfo, int32_t* colLength) { int32_t idx = -1; - iconv_t conv = taosAcquireConv(&idx, C2M); + iconv_t conv = taosAcquireConv(&idx, C2M, pResultInfo->charsetCxt); if (conv == (iconv_t)-1) return TSDB_CODE_TSC_INTERNAL_ERROR; for (int32_t i = 0; i < pResultInfo->numOfCols; ++i) { @@ -2097,7 +2098,7 @@ static int32_t doConvertUCS4(SReqResultInfo* pResultInfo, int32_t* colLength) { if (type == TSDB_DATA_TYPE_NCHAR && colLength[i] > 0) { char* p = taosMemoryRealloc(pResultInfo->convertBuf[i], colLength[i]); if (p == NULL) { - taosReleaseConv(idx, conv, C2M); + taosReleaseConv(idx, conv, C2M, pResultInfo->charsetCxt); return terrno; } @@ -2114,7 +2115,7 @@ static int32_t doConvertUCS4(SReqResultInfo* pResultInfo, int32_t* colLength) { "doConvertUCS4 error, invalid data. len:%d, bytes:%d, (p + len):%p, (pResultInfo->convertBuf[i] + " "colLength[i]):%p", len, bytes, (p + len), (pResultInfo->convertBuf[i] + colLength[i])); - taosReleaseConv(idx, conv, C2M); + taosReleaseConv(idx, conv, C2M, pResultInfo->charsetCxt); return TSDB_CODE_TSC_INTERNAL_ERROR; } @@ -2128,7 +2129,7 @@ static int32_t doConvertUCS4(SReqResultInfo* pResultInfo, int32_t* colLength) { pResultInfo->row[i] = pResultInfo->pCol[i].pData; } } - taosReleaseConv(idx, conv, C2M); + taosReleaseConv(idx, conv, C2M, pResultInfo->charsetCxt); return TSDB_CODE_SUCCESS; } @@ -2293,7 +2294,7 @@ static int32_t doConvertJson(SReqResultInfo* pResultInfo) { varDataSetLen(dst, strlen(varDataVal(dst))); } else if (tTagIsJson(data)) { char* jsonString = NULL; - parseTagDatatoJson(data, &jsonString); + parseTagDatatoJson(data, &jsonString, pResultInfo->charsetCxt); if (jsonString == NULL) { tscError("doConvertJson error: parseTagDatatoJson failed"); return terrno; @@ -2303,7 +2304,7 @@ static int32_t doConvertJson(SReqResultInfo* pResultInfo) { } else if (jsonInnerType == TSDB_DATA_TYPE_NCHAR) { // value -> "value" *(char*)varDataVal(dst) = '\"'; int32_t length = taosUcs4ToMbs((TdUcs4*)varDataVal(jsonInnerData), varDataLen(jsonInnerData), - varDataVal(dst) + CHAR_BYTES); + varDataVal(dst) + CHAR_BYTES, pResultInfo->charsetCxt); if (length <= 0) { tscError("charset:%s to %s. convert failed.", DEFAULT_UNICODE_ENCODEC, tsCharset); length = 0; diff --git a/source/client/src/clientMain.c b/source/client/src/clientMain.c index 10c337fb74..d368fb9a36 100644 --- a/source/client/src/clientMain.c +++ b/source/client/src/clientMain.c @@ -30,6 +30,7 @@ #include "tref.h" #include "trpc.h" #include "version.h" +#include "tconv.h" #define TSC_VAR_NOT_RELEASE 1 #define TSC_VAR_RELEASED 0 @@ -52,6 +53,49 @@ int taos_options(TSDB_OPTION option, const void *arg, ...) { return ret; } +static timezone_t setConnnectionTz(const char* val){ + timezone_t tz = NULL; + static int32_t lock_c = 0; + + for (int i = 1; atomic_val_compare_exchange_32(&lock_c, 0, 1) != 0; ++i) { + if (i % 1000 == 0) { + tscInfo("haven't acquire lock after spin %d times.", i); + (void)sched_yield(); + } + } + + if (pTimezoneMap == NULL){ + pTimezoneMap = taosHashInit(0, MurmurHash3_32, false, HASH_ENTRY_LOCK); + if (pTimezoneMap == NULL) { + atomic_store_32(&lock_c, 0); + goto END; + } + taosHashSetFreeFp(pTimezoneMap, (_hash_free_fn_t)tzfree); + } + + timezone_t *tmp = taosHashGet(pTimezoneMap, val, strlen(val)); + if (tmp != NULL && *tmp != NULL){ + tz = *tmp; + goto END; + } + + tscDebug("set timezone to %s", val); + tz = tzalloc(val); + if (tz == NULL) { + tscError("%s unknown timezone %s", __func__, val); + terrno = TAOS_SYSTEM_ERROR(errno); + goto END; + } + int32_t code = taosHashPut(pTimezoneMap, val, strlen(val), &tz, sizeof(timezone_t)); + if (code != 0){ + tzfree(tz); + tz = NULL; + } + +END: + atomic_store_32(&lock_c, 0); + return tz; +} static int32_t setConnectionOption(TAOS *taos, TSDB_OPTION_CONNECTION option, const char* val){ if (taos == NULL) { return TSDB_CODE_INVALID_PARA; @@ -74,32 +118,24 @@ static int32_t setConnectionOption(TAOS *taos, TSDB_OPTION_CONNECTION option, co code = terrno; goto END; } - tstrncpy(pObj->optionInfo.charset, val, TD_CHARSET_LEN); }else{ - pObj->optionInfo.charset[0] = 0; + val = tsCharset; } + void *tmp = taosConvInit(val); + if (tmp == NULL) { + code = terrno; + goto END; + } + pObj->optionInfo.charsetCxt = tmp; } else if (option == TSDB_OPTION_CONNECTION_TIMEZONE) { if (val != NULL){ if (strlen(val) == 0){ code = TSDB_CODE_INVALID_PARA; goto END; } - timezone_t *tmp = taosHashGet(pTimezoneMap, val, strlen(val)); - if (tmp && *tmp){ - pObj->optionInfo.timezone = *tmp; - goto END; - } - - tscDebug("set timezone to %s", val); - timezone_t tz = tzalloc(val); - if (!tz) { - tscError("%s unknown timezone %s", __func__, val); - code = TAOS_SYSTEM_ERROR(errno); - goto END; - } - code = taosHashPut(pTimezoneMap, val, strlen(val), &tz, sizeof(timezone_t)); - if (code != 0){ - tzfree(tz); + timezone_t tz = setConnnectionTz(val); + if (tz == NULL){ + code = terrno; goto END; } pObj->optionInfo.timezone = tz; @@ -126,26 +162,7 @@ END: } int taos_options_connection(TAOS *taos, TSDB_OPTION_CONNECTION option, const void *arg, ...){ - static int32_t lock_c = 0; - - for (int i = 1; atomic_val_compare_exchange_32(&lock_c, 0, 1) != 0; ++i) { - if (i % 1000 == 0) { - tscInfo("haven't acquire lock after spin %d times.", i); - (void)sched_yield(); - } - } - - if (pTimezoneMap == NULL){ - pTimezoneMap = taosHashInit(0, taosGetDefaultHashFunction(TSDB_DATA_TYPE_BINARY), false, HASH_ENTRY_LOCK); - if (pTimezoneMap == NULL) { - atomic_store_32(&lock_c, 0); - return terrno; - } - taosHashSetFreeFp(pTimezoneMap, (_hash_free_fn_t)tzfree); - } - int ret = setConnectionOption(taos, option, (const char *)arg); - atomic_store_32(&lock_c, 0); - return ret; + return setConnectionOption(taos, option, (const char *)arg); } // this function may be called by user or system, or by both simultaneously. @@ -1342,7 +1359,8 @@ int32_t createParseContext(const SRequestObj *pRequest, SParseContext **pCxt, SS .parseSqlFp = clientParseSql, .parseSqlParam = pWrapper, .setQueryFp = setQueryRequest, - .timezone = pTscObj->optionInfo.timezone}; + .timezone = pTscObj->optionInfo.timezone, + .charsetCxt = pTscObj->optionInfo.charsetCxt}; int8_t biMode = atomic_load_8(&((STscObj *)pTscObj)->biMode); (*pCxt)->biMode = biMode; return TSDB_CODE_SUCCESS; diff --git a/source/client/src/clientRawBlockWrite.c b/source/client/src/clientRawBlockWrite.c index 1799f29eb4..fe6ae95b7f 100644 --- a/source/client/src/clientRawBlockWrite.c +++ b/source/client/src/clientRawBlockWrite.c @@ -414,7 +414,7 @@ static void buildChildElement(cJSON* json, SVCreateTbReq* pCreateReq) { goto end; } char* pJson = NULL; - parseTagDatatoJson(pTag, &pJson); + parseTagDatatoJson(pTag, &pJson, NULL); if (pJson == NULL) { uError("parseTagDatatoJson failed, pJson == NULL"); goto end; @@ -730,7 +730,7 @@ static void processAlterTable(SMqMetaRsp* metaRsp, cJSON** pJson) { uError("processAlterTable isJson false"); goto end; } - parseTagDatatoJson(vAlterTbReq.pTagVal, &buf); + parseTagDatatoJson(vAlterTbReq.pTagVal, &buf, NULL); if (buf == NULL) { uError("parseTagDatatoJson failed, buf == NULL"); goto end; diff --git a/source/client/src/clientSml.c b/source/client/src/clientSml.c index 8602421ed0..e2f278400d 100644 --- a/source/client/src/clientSml.c +++ b/source/client/src/clientSml.c @@ -267,7 +267,7 @@ bool isSmlColAligned(SSmlHandle *info, int cnt, SSmlKv *kv) { goto END; } // bind data - int32_t ret = smlBuildCol(info->currTableDataCtx, info->currSTableMeta->schema, kv, cnt + 1); + int32_t ret = smlBuildCol(info->currTableDataCtx, info->currSTableMeta->schema, kv, cnt + 1, info->taos->optionInfo.charsetCxt); if (unlikely(ret != TSDB_CODE_SUCCESS)) { uDebug("smlBuildCol error, retry"); goto END; @@ -411,8 +411,8 @@ int32_t smlParseEndTelnetJsonFormat(SSmlHandle *info, SSmlLineInfo *elements, SS int32_t code = 0; int32_t lino = 0; uDebug("SML:0x%" PRIx64 " %s format true, ts:%" PRId64, info->id, __FUNCTION__ , kvTs->i); - SML_CHECK_CODE(smlBuildCol(info->currTableDataCtx, info->currSTableMeta->schema, kvTs, 0)); - SML_CHECK_CODE(smlBuildCol(info->currTableDataCtx, info->currSTableMeta->schema, kv, 1)); + SML_CHECK_CODE(smlBuildCol(info->currTableDataCtx, info->currSTableMeta->schema, kvTs, 0, info->taos->optionInfo.charsetCxt)); + SML_CHECK_CODE(smlBuildCol(info->currTableDataCtx, info->currSTableMeta->schema, kv, 1, info->taos->optionInfo.charsetCxt)); SML_CHECK_CODE(smlBuildRow(info->currTableDataCtx)); END: @@ -438,7 +438,7 @@ END: int32_t smlParseEndLine(SSmlHandle *info, SSmlLineInfo *elements, SSmlKv *kvTs) { if (info->dataFormat) { uDebug("SML:0x%" PRIx64 " %s format true, ts:%" PRId64, info->id, __FUNCTION__, kvTs->i); - int32_t ret = smlBuildCol(info->currTableDataCtx, info->currSTableMeta->schema, kvTs, 0); + int32_t ret = smlBuildCol(info->currTableDataCtx, info->currSTableMeta->schema, kvTs, 0, info->taos->optionInfo.charsetCxt); if (ret == TSDB_CODE_SUCCESS) { ret = smlBuildRow(info->currTableDataCtx); } @@ -1482,7 +1482,7 @@ static int32_t smlInsertData(SSmlHandle *info) { SML_CHECK_CODE(smlBindData(info->pQuery, info->dataFormat, tableData->tags, (*pMeta)->cols, tableData->cols, (*pMeta)->tableMeta, tableData->childTableName, measure, measureLen, info->ttl, info->msgBuf.buf, - info->msgBuf.len)); + info->msgBuf.len, info->taos->optionInfo.charsetCxt)); taosMemoryFreeClear(measure); oneTable = (SSmlTableInfo **)taosHashIterate(info->childTables, oneTable); } diff --git a/source/client/src/clientStmt.c b/source/client/src/clientStmt.c index e56d4cc4f6..fc16242b79 100644 --- a/source/client/src/clientStmt.c +++ b/source/client/src/clientStmt.c @@ -1071,7 +1071,7 @@ int stmtSetTbTags(TAOS_STMT* stmt, TAOS_MULTI_BIND* tags) { tscDebug("start to bind stmt tag values"); STMT_ERR_RET(qBindStmtTagsValue(*pDataBlock, pStmt->bInfo.boundTags, pStmt->bInfo.tbSuid, pStmt->bInfo.stbFName, pStmt->bInfo.sname.tname, tags, pStmt->exec.pRequest->msgBuf, - pStmt->exec.pRequest->msgBufLen)); + pStmt->exec.pRequest->msgBufLen, pStmt->taos->optionInfo.charsetCxt)); return TSDB_CODE_SUCCESS; } @@ -1239,7 +1239,7 @@ int stmtBindBatch(TAOS_STMT* stmt, TAOS_MULTI_BIND* bind, int32_t colIdx) { } if (STMT_TYPE_QUERY == pStmt->sql.type) { - STMT_ERR_RET(qStmtBindParams(pStmt->sql.pQuery, bind, colIdx)); + STMT_ERR_RET(qStmtBindParams(pStmt->sql.pQuery, bind, colIdx, pStmt->taos->optionInfo.charsetCxt)); SParseContext ctx = {.requestId = pStmt->exec.pRequest->requestId, .acctId = pStmt->taos->acctId, @@ -1325,10 +1325,10 @@ int stmtBindBatch(TAOS_STMT* stmt, TAOS_MULTI_BIND* bind, int32_t colIdx) { if (pStmt->sql.stbInterlaceMode) { (*pDataBlock)->pData->flags = 0; code = qBindStmtStbColsValue(*pDataBlock, pCols, bind, pStmt->exec.pRequest->msgBuf, - pStmt->exec.pRequest->msgBufLen, &pStmt->sql.siInfo.pTSchema, pStmt->sql.pBindInfo); + pStmt->exec.pRequest->msgBufLen, &pStmt->sql.siInfo.pTSchema, pStmt->sql.pBindInfo, pStmt->taos->optionInfo.charsetCxt); } else { code = - qBindStmtColsValue(*pDataBlock, pCols, bind, pStmt->exec.pRequest->msgBuf, pStmt->exec.pRequest->msgBufLen); + qBindStmtColsValue(*pDataBlock, pCols, bind, pStmt->exec.pRequest->msgBuf, pStmt->exec.pRequest->msgBufLen, pStmt->taos->optionInfo.charsetCxt); } if (code) { @@ -1353,7 +1353,7 @@ int stmtBindBatch(TAOS_STMT* stmt, TAOS_MULTI_BIND* bind, int32_t colIdx) { } code = qBindStmtSingleColValue(*pDataBlock, pCols, bind, pStmt->exec.pRequest->msgBuf, - pStmt->exec.pRequest->msgBufLen, colIdx, pStmt->bInfo.sBindRowNum); + pStmt->exec.pRequest->msgBufLen, colIdx, pStmt->bInfo.sBindRowNum, pStmt->taos->optionInfo.charsetCxt); if (code) { tscError("qBindStmtSingleColValue failed, error:%s", tstrerror(code)); STMT_ERR_RET(code); diff --git a/source/client/src/clientStmt2.c b/source/client/src/clientStmt2.c index 4bbfc6afaa..9dec5a481f 100644 --- a/source/client/src/clientStmt2.c +++ b/source/client/src/clientStmt2.c @@ -1014,7 +1014,7 @@ int stmtSetTbTags2(TAOS_STMT2* stmt, TAOS_STMT2_BIND* tags) { tscDebug("start to bind stmt tag values"); STMT_ERR_RET(qBindStmtTagsValue2(*pDataBlock, pStmt->bInfo.boundTags, pStmt->bInfo.tbSuid, pStmt->bInfo.stbFName, pStmt->bInfo.sname.tname, tags, pStmt->exec.pRequest->msgBuf, - pStmt->exec.pRequest->msgBufLen)); + pStmt->exec.pRequest->msgBufLen, pStmt->taos->optionInfo.charsetCxt)); return TSDB_CODE_SUCCESS; } @@ -1323,7 +1323,7 @@ int stmtBindBatch2(TAOS_STMT2* stmt, TAOS_STMT2_BIND* bind, int32_t colIdx) { } if (STMT_TYPE_QUERY == pStmt->sql.type) { - STMT_ERR_RET(qStmtBindParams2(pStmt->sql.pQuery, bind, colIdx)); + STMT_ERR_RET(qStmtBindParams2(pStmt->sql.pQuery, bind, colIdx, pStmt->taos->optionInfo.charsetCxt)); SParseContext ctx = {.requestId = pStmt->exec.pRequest->requestId, .acctId = pStmt->taos->acctId, @@ -1407,10 +1407,10 @@ int stmtBindBatch2(TAOS_STMT2* stmt, TAOS_STMT2_BIND* bind, int32_t colIdx) { if (pStmt->sql.stbInterlaceMode) { (*pDataBlock)->pData->flags = 0; code = qBindStmtStbColsValue2(*pDataBlock, pCols, bind, pStmt->exec.pRequest->msgBuf, - pStmt->exec.pRequest->msgBufLen, &pStmt->sql.siInfo.pTSchema, pStmt->sql.pBindInfo); + pStmt->exec.pRequest->msgBufLen, &pStmt->sql.siInfo.pTSchema, pStmt->sql.pBindInfo, pStmt->taos->optionInfo.charsetCxt); } else { code = - qBindStmtColsValue2(*pDataBlock, pCols, bind, pStmt->exec.pRequest->msgBuf, pStmt->exec.pRequest->msgBufLen); + qBindStmtColsValue2(*pDataBlock, pCols, bind, pStmt->exec.pRequest->msgBuf, pStmt->exec.pRequest->msgBufLen, pStmt->taos->optionInfo.charsetCxt); } if (code) { @@ -1435,7 +1435,7 @@ int stmtBindBatch2(TAOS_STMT2* stmt, TAOS_STMT2_BIND* bind, int32_t colIdx) { } code = qBindStmtSingleColValue2(*pDataBlock, pCols, bind, pStmt->exec.pRequest->msgBuf, - pStmt->exec.pRequest->msgBufLen, colIdx, pStmt->bInfo.sBindRowNum); + pStmt->exec.pRequest->msgBufLen, colIdx, pStmt->bInfo.sBindRowNum, pStmt->taos->optionInfo.charsetCxt); if (code) { tscError("qBindStmtSingleColValue failed, error:%s", tstrerror(code)); STMT_ERR_RET(code); diff --git a/source/common/src/tdatablock.c b/source/common/src/tdatablock.c index 452c9f38ef..3b3c4b72d9 100644 --- a/source/common/src/tdatablock.c +++ b/source/common/src/tdatablock.c @@ -2641,7 +2641,7 @@ int32_t dumpBlockData(SSDataBlock* pDataBlock, const char* flag, char** pDataBuf char* pData = colDataGetVarData(pColInfoData, j); int32_t dataSize = TMIN(sizeof(pBuf), varDataLen(pData)); memset(pBuf, 0, sizeof(pBuf)); - code = taosUcs4ToMbs((TdUcs4*)varDataVal(pData), dataSize, pBuf); + code = taosUcs4ToMbs((TdUcs4*)varDataVal(pData), dataSize, pBuf, NULL); if (code < 0) { uError("func %s failed to convert to ucs charset since %s", __func__, tstrerror(code)); lino = __LINE__; diff --git a/source/common/src/tglobal.c b/source/common/src/tglobal.c index 32faaf2bc2..b31c86bd82 100644 --- a/source/common/src/tglobal.c +++ b/source/common/src/tglobal.c @@ -654,7 +654,6 @@ static int32_t taosAddClientCfg(SConfig *pCfg) { static int32_t taosAddSystemCfg(SConfig *pCfg) { SysNameInfo info = taosGetSysNameInfo(); - (void)taosGetSystemTimezone(tsTimezoneStr); TAOS_CHECK_RETURN(cfgAddTimezone(pCfg, "timezone", tsTimezoneStr, CFG_SCOPE_BOTH, CFG_DYN_CLIENT)); TAOS_CHECK_RETURN(cfgAddLocale(pCfg, "locale", tsLocale, CFG_SCOPE_BOTH, CFG_DYN_CLIENT)); TAOS_CHECK_RETURN(cfgAddCharset(pCfg, "charset", tsCharset, CFG_SCOPE_BOTH, CFG_DYN_NONE)); @@ -1317,25 +1316,6 @@ static int32_t taosSetClientCfg(SConfig *pCfg) { static int32_t taosSetSystemCfg(SConfig *pCfg) { SConfigItem *pItem = NULL; - TAOS_CHECK_GET_CFG_ITEM(pCfg, pItem, "locale"); - const char *locale = pItem->str; - - TAOS_CHECK_GET_CFG_ITEM(pCfg, pItem, "charset"); - const char *charset = pItem->str; - - int32_t code = taosSetSystemLocale(locale, charset); - if (TSDB_CODE_SUCCESS != code) { - uError("failed to set locale:%s, since: %s", locale, tstrerror(code)); - char curLocale[TD_LOCALE_LEN] = {0}; - char curCharset[TD_CHARSET_LEN] = {0}; - taosGetSystemLocale(curLocale, curCharset); - if (0 != strlen(curLocale) && 0 != strlen(curCharset)) { - uInfo("current locale: %s, charset: %s", curLocale, curCharset); - } - } - - osSetSystemLocale(locale, charset); - TAOS_CHECK_GET_CFG_ITEM(pCfg, pItem, "enableCoreFile"); tsEnableCoreFile = pItem->bval; taosSetCoreDump(tsEnableCoreFile); @@ -2062,6 +2042,10 @@ static int32_t taosCfgDynamicOptionsForClient(SConfig *pCfg, const char *name) { int32_t code = TSDB_CODE_SUCCESS; int32_t lino = 0; + if (strcasecmp("locale", name) == 0 || strcasecmp("charset", name) == 0 + || strcasecmp("timezone", name) == 0) { + goto _out; + } cfgLock(pCfg); SConfigItem *pItem = cfgGetItem(pCfg, name); @@ -2150,25 +2134,6 @@ static int32_t taosCfgDynamicOptionsForClient(SConfig *pCfg, const char *name) { } break; } - case 'l': { - if (strcasecmp("locale", name) == 0) { - SConfigItem *pLocaleItem = cfgGetItem(pCfg, "locale"); - SConfigItem *pCharsetItem = cfgGetItem(pCfg, "charset"); - if (pLocaleItem == NULL || pCharsetItem == NULL) { - uError("failed to get locale or charset from cfg"); - code = TSDB_CODE_CFG_NOT_FOUND; - goto _out; - } - - const char *locale = pLocaleItem->str; - const char *charset = pCharsetItem->str; - TAOS_CHECK_GOTO(taosSetSystemLocale(locale, charset), &lino, _out); - osSetSystemLocale(locale, charset); - uInfo("locale set to '%s', charset set to '%s'", locale, charset); - matched = true; - } - break; - } case 'm': { if (strcasecmp("metaCacheMaxSize", name) == 0) { atomic_store_32(&tsMetaCacheMaxSize, pItem->i32); @@ -2239,9 +2204,7 @@ static int32_t taosCfgDynamicOptionsForClient(SConfig *pCfg, const char *name) { break; } case 't': { - if (strcasecmp("timezone", name) == 0) { - matched = true; - } else if (strcasecmp("tempDir", name) == 0) { + if (strcasecmp("tempDir", name) == 0) { uInfo("%s set from %s to %s", name, tsTempDir, pItem->str); tstrncpy(tsTempDir, pItem->str, PATH_MAX); TAOS_CHECK_GOTO(taosExpandDir(tsTempDir, tsTempDir, PATH_MAX), &lino, _out); diff --git a/source/common/src/ttime.c b/source/common/src/ttime.c index 574b2d2fce..893c03335e 100644 --- a/source/common/src/ttime.c +++ b/source/common/src/ttime.c @@ -480,7 +480,7 @@ int32_t convertTimeFromPrecisionToUnit(int64_t time, int32_t fromPrecision, char TAOS_RETURN(TSDB_CODE_SUCCESS); } -int32_t convertStringToTimestamp(int16_t type, char* inputData, int64_t timePrec, int64_t* timeVal, timezone_t tz) { +int32_t convertStringToTimestamp(int16_t type, char* inputData, int64_t timePrec, int64_t* timeVal, timezone_t tz, void* charsetCxt) { int32_t charLen = varDataLen(inputData); char* newColData; if (type == TSDB_DATA_TYPE_BINARY || type == TSDB_DATA_TYPE_VARBINARY) { @@ -500,7 +500,7 @@ int32_t convertStringToTimestamp(int16_t type, char* inputData, int64_t timePrec if (NULL == newColData) { TAOS_RETURN(terrno); } - int len = taosUcs4ToMbs((TdUcs4*)varDataVal(inputData), charLen, newColData); + int len = taosUcs4ToMbs((TdUcs4*)varDataVal(inputData), charLen, newColData, charsetCxt); if (len < 0) { taosMemoryFree(newColData); TAOS_RETURN(TSDB_CODE_FAILED); @@ -2011,7 +2011,11 @@ int64_t taosGetTimestampToday(int32_t precision, timezone_t tz) { : (precision == TSDB_TIME_PRECISION_MILLI) ? 1000 : (precision == TSDB_TIME_PRECISION_MICRO) ? 1000000 : 1000000000; - time_t t = taosTime(NULL); + time_t t; + int32_t code = taosTime(&t); + if (code != 0) { + return -1; + } struct tm tm; if (taosLocalTime(&t, &tm, NULL, 0, tz) == NULL){ uError("%s failed to get local time, code:%d", __FUNCTION__, errno); diff --git a/source/common/test/commonTests.cpp b/source/common/test/commonTests.cpp index 19fe6b00d3..a6ffe2cd40 100644 --- a/source/common/test/commonTests.cpp +++ b/source/common/test/commonTests.cpp @@ -477,7 +477,7 @@ void test_ts2char(int64_t ts, const char* format, int32_t precison, const char* TEST(timeTest, ts2char) { osDefaultInit(); - if (tsTimezone != TdEastZone8) GTEST_SKIP(); + if (taosGetLocalTimezoneOffset() != TdEastZone8) GTEST_SKIP(); int64_t ts; const char* format = "YYYY-MM-DD"; ts = 0; @@ -529,7 +529,7 @@ TEST(timeTest, ts2char) { TEST(timeTest, char2ts) { osDefaultInit(); - if (tsTimezone != TdEastZone8) GTEST_SKIP(); + if (taosGetLocalTimezoneOffset() != TdEastZone8) GTEST_SKIP(); int64_t ts; int32_t code = TEST_char2ts("YYYY-DD-MM HH12:MI:SS:MSPM", &ts, TSDB_TIME_PRECISION_MILLI, "2023-10-10 12:00:00.000AM"); @@ -630,7 +630,7 @@ TEST(timeTest, char2ts) { // default to 1970-1-1 00:00:00+08 -> 1969-12-31 16:00:00+00 ASSERT_EQ(0, TEST_char2ts("YYYY", &ts, TSDB_TIME_PRECISION_SECONDS, "1970")); - ASSERT_EQ(ts, -1 * tsTimezone * 60 * 60); + ASSERT_EQ(ts, -1 * taosGetLocalTimezoneOffset()); ASSERT_EQ(0, TEST_char2ts("yyyyMM1/dd ", &ts, TSDB_TIME_PRECISION_MICRO, "210001/2")); ASSERT_EQ(ts, 4102502400000000LL); diff --git a/source/dnode/mgmt/exe/dmMain.c b/source/dnode/mgmt/exe/dmMain.c index ade5e16894..e9911bb25d 100644 --- a/source/dnode/mgmt/exe/dmMain.c +++ b/source/dnode/mgmt/exe/dmMain.c @@ -20,6 +20,7 @@ #include "tconfig.h" #include "tglobal.h" #include "version.h" +#include "tconv.h" #ifdef TD_JEMALLOC_ENABLED #include "jemalloc/jemalloc.h" #endif @@ -444,7 +445,7 @@ int mainWindows(int argc, char **argv) { return code; } - if ((code = taosConvInit()) != 0) { + if ((tsCharsetCxt = taosConvInit(tsCharset)) == NULL) { dError("failed to init conv"); taosCloseLog(); taosCleanupArgs(); diff --git a/source/dnode/mgmt/node_mgmt/src/dmMgmt.c b/source/dnode/mgmt/node_mgmt/src/dmMgmt.c index 9f1c292a90..f11e1508a8 100644 --- a/source/dnode/mgmt/node_mgmt/src/dmMgmt.c +++ b/source/dnode/mgmt/node_mgmt/src/dmMgmt.c @@ -22,6 +22,7 @@ #include "tglobal.h" #include "tgrant.h" #include "tstream.h" +#include "tconv.h" static bool dmRequireNode(SDnode *pDnode, SMgmtWrapper *pWrapper) { SMgmtInputOpt input = dmBuildMgmtInputOpt(pWrapper); diff --git a/source/dnode/mnode/impl/inc/mndDef.h b/source/dnode/mnode/impl/inc/mndDef.h index d2d9b2e8eb..65708628b3 100644 --- a/source/dnode/mnode/impl/inc/mndDef.h +++ b/source/dnode/mnode/impl/inc/mndDef.h @@ -489,7 +489,7 @@ typedef struct { int64_t dstTbUid; int8_t intervalUnit; int8_t slidingUnit; - int8_t timezone; + int8_t timezone; // int8_t is not enough, timezone is unit of second int32_t dstVgId; // for stream int64_t interval; int64_t offset; diff --git a/source/dnode/mnode/impl/src/mndSma.c b/source/dnode/mnode/impl/src/mndSma.c index a54c7f1b14..d0872009fd 100644 --- a/source/dnode/mnode/impl/src/mndSma.c +++ b/source/dnode/mnode/impl/src/mndSma.c @@ -316,7 +316,7 @@ static void *mndBuildVCreateSmaReq(SMnode *pMnode, SVgObj *pVgroup, SSmaObj *pSm req.version = 0; req.intervalUnit = pSma->intervalUnit; req.slidingUnit = pSma->slidingUnit; - req.timezoneInt = pSma->timezone; +// req.timezoneInt = pSma->timezone; tstrncpy(req.indexName, (char *)tNameGetTableName(&name), TSDB_INDEX_NAME_LEN); req.exprLen = pSma->exprLen; req.tagsFilterLen = pSma->tagsFilterLen; @@ -617,9 +617,9 @@ static int32_t mndCreateSma(SMnode *pMnode, SRpcMsg *pReq, SMCreateSmaReq *pCrea smaObj.intervalUnit = pCreate->intervalUnit; smaObj.slidingUnit = pCreate->slidingUnit; #if 0 - smaObj.timezone = pCreate->timezone; +// smaObj.timezone = pCreate->timezone; #endif - smaObj.timezone = tsTimezone; // use timezone of server +// smaObj.timezone = taosGetLocalTimezoneOffset(); // use timezone of server smaObj.interval = pCreate->interval; smaObj.offset = pCreate->offset; smaObj.sliding = pCreate->sliding; @@ -1554,7 +1554,7 @@ static void initSMAObj(SCreateTSMACxt* pCxt) { pCxt->pSma->dbUid = pCxt->pDb->uid; pCxt->pSma->interval = pCxt->pCreateSmaReq->interval; pCxt->pSma->intervalUnit = pCxt->pCreateSmaReq->intervalUnit; - pCxt->pSma->timezone = tsTimezone; +// pCxt->pSma->timezone = taosGetLocalTimezoneOffset(); pCxt->pSma->version = 1; pCxt->pSma->exprLen = pCxt->pCreateSmaReq->exprLen; diff --git a/source/dnode/vnode/src/meta/metaQuery.c b/source/dnode/vnode/src/meta/metaQuery.c index e2ba8d9ccb..87beb8842b 100644 --- a/source/dnode/vnode/src/meta/metaQuery.c +++ b/source/dnode/vnode/src/meta/metaQuery.c @@ -1328,7 +1328,7 @@ int32_t metaFilterTableIds(void *pVnode, SMetaFltParam *arg, SArray *pUids) { TAOS_CHECK_GOTO(terrno, NULL, END); } - if (false == taosMbsToUcs4(tagData, nTagData, (TdUcs4 *)buf, maxSize, &maxSize)) { + if (false == taosMbsToUcs4(tagData, nTagData, (TdUcs4 *)buf, maxSize, &maxSize, NULL)) { TAOS_CHECK_GOTO(terrno, NULL, END); } diff --git a/source/dnode/vnode/src/meta/metaTable.c b/source/dnode/vnode/src/meta/metaTable.c index 5c3516a962..3414d18590 100644 --- a/source/dnode/vnode/src/meta/metaTable.c +++ b/source/dnode/vnode/src/meta/metaTable.c @@ -153,7 +153,7 @@ static int metaSaveJsonVarToIdx(SMeta *pMeta, const SMetaEntry *pCtbEntry, const if (val == NULL) { TAOS_CHECK_GOTO(terrno, NULL, _exception); } - int32_t len = taosUcs4ToMbs((TdUcs4 *)pTagVal->pData, pTagVal->nData, val + VARSTR_HEADER_SIZE); + int32_t len = taosUcs4ToMbs((TdUcs4 *)pTagVal->pData, pTagVal->nData, val + VARSTR_HEADER_SIZE, NULL); if (len < 0) { TAOS_CHECK_GOTO(len, NULL, _exception); } @@ -237,7 +237,7 @@ int metaDelJsonVarFromIdx(SMeta *pMeta, const SMetaEntry *pCtbEntry, const SSche if (val == NULL) { TAOS_CHECK_GOTO(terrno, NULL, _exception); } - int32_t len = taosUcs4ToMbs((TdUcs4 *)pTagVal->pData, pTagVal->nData, val + VARSTR_HEADER_SIZE); + int32_t len = taosUcs4ToMbs((TdUcs4 *)pTagVal->pData, pTagVal->nData, val + VARSTR_HEADER_SIZE, NULL); if (len < 0) { TAOS_CHECK_GOTO(len, NULL, _exception); } diff --git a/source/libs/catalog/src/catalog.c b/source/libs/catalog/src/catalog.c index 0ea1d98312..c194b51b07 100644 --- a/source/libs/catalog/src/catalog.c +++ b/source/libs/catalog/src/catalog.c @@ -447,7 +447,7 @@ int32_t ctgGetTbTag(SCatalog* pCtg, SRequestConnInfo* pConn, SName* pTableName, } char* pJson = NULL; - parseTagDatatoJson(pTag, &pJson); + parseTagDatatoJson(pTag, &pJson, NULL); if(NULL == pJson) { taosArrayDestroy(pTagVals); CTG_ERR_JRET(terrno); diff --git a/source/libs/catalog/src/ctgAsync.c b/source/libs/catalog/src/ctgAsync.c index 9bfb4102aa..e3f7ad4811 100644 --- a/source/libs/catalog/src/ctgAsync.c +++ b/source/libs/catalog/src/ctgAsync.c @@ -2340,7 +2340,7 @@ int32_t ctgHandleGetTbTagRsp(SCtgTaskReq* tReq, int32_t reqType, const SDataBuf* } char* pJson = NULL; - parseTagDatatoJson(pTag, &pJson); + parseTagDatatoJson(pTag, &pJson, NULL); if (NULL == pJson) { taosArrayDestroy(pTagVals); CTG_ERR_JRET(terrno); diff --git a/source/libs/command/src/command.c b/source/libs/command/src/command.c index 94c000991a..b86621738d 100644 --- a/source/libs/command/src/command.c +++ b/source/libs/command/src/command.c @@ -585,7 +585,7 @@ static int32_t appendTagValues(char* buf, int32_t* len, STableCfg* pCfg) { if (tTagIsJson(pTag)) { char* pJson = NULL; - parseTagDatatoJson(pTag, &pJson); + parseTagDatatoJson(pTag, &pJson, NULL); if (NULL == pJson) { qError("failed to parse tag to json, pJson is NULL"); return terrno; diff --git a/source/libs/executor/src/sysscanoperator.c b/source/libs/executor/src/sysscanoperator.c index af5313297e..00b4de4625 100644 --- a/source/libs/executor/src/sysscanoperator.c +++ b/source/libs/executor/src/sysscanoperator.c @@ -966,7 +966,7 @@ int32_t convertTagDataToStr(char* str, int32_t strBuffLen, int type, void* buf, return TSDB_CODE_TSC_INVALID_VALUE; } - int32_t length = taosUcs4ToMbs((TdUcs4*)buf, bufSize, str); + int32_t length = taosUcs4ToMbs((TdUcs4*)buf, bufSize, str, NULL); if (length <= 0) { return TSDB_CODE_TSC_INVALID_VALUE; } @@ -1117,7 +1117,7 @@ static int32_t sysTableUserTagsFillOneTableTags(const SSysTableScanInfo* pInfo, if (tagData != NULL) { if (tagType == TSDB_DATA_TYPE_JSON) { char* tagJson = NULL; - parseTagDatatoJson(tagData, &tagJson); + parseTagDatatoJson(tagData, &tagJson, NULL); if (tagJson == NULL) { code = terrno; goto _end; diff --git a/source/libs/executor/test/queryPlanTests.cpp b/source/libs/executor/test/queryPlanTests.cpp index 6710435aba..cb6020ad29 100755 --- a/source/libs/executor/test/queryPlanTests.cpp +++ b/source/libs/executor/test/queryPlanTests.cpp @@ -592,7 +592,7 @@ void qptGetRandValue(uint8_t* pType, int32_t* pLen, void** ppVal) { memset(pTmp, 'A' + taosRand() % 26, *pLen); *ppVal = taosMemoryCalloc(1, *pLen * TSDB_NCHAR_SIZE + VARSTR_HEADER_SIZE); assert(*ppVal); - assert(taosMbsToUcs4(pTmp, *pLen, (TdUcs4 *)varDataVal(*ppVal), *pLen * TSDB_NCHAR_SIZE, NULL)); + assert(taosMbsToUcs4(pTmp, *pLen, (TdUcs4 *)varDataVal(*ppVal), *pLen * TSDB_NCHAR_SIZE, NULL, NULL)); *pLen *= TSDB_NCHAR_SIZE; varDataSetLen(*ppVal, *pLen); taosMemoryFree(pTmp); diff --git a/source/libs/executor/test/sortTests.cpp b/source/libs/executor/test/sortTests.cpp index b2313f35a1..ce2da01b66 100644 --- a/source/libs/executor/test/sortTests.cpp +++ b/source/libs/executor/test/sortTests.cpp @@ -93,7 +93,7 @@ SSDataBlock* getSingleColDummyBlock(void* param) { char strOri[128] = {0}; taosRandStr(strOri, size); int32_t len = 0; - bool ret = taosMbsToUcs4(strOri, size, (TdUcs4*)varDataVal(str), size * TSDB_NCHAR_SIZE, &len); + bool ret = taosMbsToUcs4(strOri, size, (TdUcs4*)varDataVal(str), size * TSDB_NCHAR_SIZE, &len, NULL); if (!ret) { (void) printf("error\n"); return NULL; @@ -331,7 +331,7 @@ TEST(testCase, external_mem_sort_Test) { if(pInfo[i].type == TSDB_DATA_TYPE_NCHAR){ char buf[128] = {0}; - int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(v), varDataLen(v), buf); + int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(v), varDataLen(v), buf, NULL); printf("%d: %s\n", row++, buf); }else if(pInfo[i].type == TSDB_DATA_TYPE_BINARY || pInfo[i]->type == TSDB_DATA_TYPE_GEOMETRY){ char buf[128] = {0}; diff --git a/source/libs/function/src/builtins.c b/source/libs/function/src/builtins.c index fe570eb212..acfdf87d33 100644 --- a/source/libs/function/src/builtins.c +++ b/source/libs/function/src/builtins.c @@ -1664,7 +1664,7 @@ static int32_t translateOutVarchar(SFunctionNode* pFunc, char* pErrBuf, int32_t bytes = TSDB_TABLE_FNAME_LEN - 1 + VARSTR_HEADER_SIZE; break; case FUNCTION_TYPE_TIMEZONE: - bytes = timeZoneStrLen(); + bytes = TD_TIMEZONE_LEN + VARSTR_HEADER_SIZE; break; case FUNCTION_TYPE_IRATE_PARTIAL: bytes = getIrateInfoSize((pFunc->hasPk) ? pFunc->pkBytes : 0) + VARSTR_HEADER_SIZE; diff --git a/source/libs/parser/inc/parUtil.h b/source/libs/parser/inc/parUtil.h index db9395ea7f..aec5fb4cd6 100644 --- a/source/libs/parser/inc/parUtil.h +++ b/source/libs/parser/inc/parUtil.h @@ -136,7 +136,7 @@ int32_t trimString(const char* src, int32_t len, char* dst, int32_t dlen); int32_t getVnodeSysTableTargetName(int32_t acctId, SNode* pWhere, SName* pName); int32_t checkAndTrimValue(SToken* pToken, char* tmpTokenBuf, SMsgBuf* pMsgBuf, int8_t type); int32_t parseTagValue(SMsgBuf* pMsgBuf, const char** pSql, uint8_t precision, SSchema* pTagSchema, SToken* pToken, - SArray* pTagName, SArray* pTagVals, STag** pTag, timezone_t tz); + SArray* pTagName, SArray* pTagVals, STag** pTag, timezone_t tz, void *charsetCxt); int32_t parseTbnameToken(SMsgBuf* pMsgBuf, char* tname, SToken* pToken, bool* pFoundCtbName); int32_t buildCatalogReq(const SParseMetaCache* pMetaCache, SCatalogReq* pCatalogReq); diff --git a/source/libs/parser/src/parAstCreater.c b/source/libs/parser/src/parAstCreater.c index 5888bb0021..1204c116a6 100644 --- a/source/libs/parser/src/parAstCreater.c +++ b/source/libs/parser/src/parAstCreater.c @@ -421,6 +421,7 @@ SNode* createValueNode(SAstCreateContext* pCxt, int32_t dataType, const SToken* } val->translate = false; val->tz = pCxt->pQueryCxt->timezone; + val->charsetCxt = pCxt->pQueryCxt->charsetCxt; return (SNode*)val; _err: return NULL; @@ -964,6 +965,8 @@ SNode* createOperatorNode(SAstCreateContext* pCxt, EOperatorType type, SNode* pL op->opType = type; op->pLeft = pLeft; op->pRight = pRight; + op->tz = pCxt->pQueryCxt->timezone; + op->charsetCxt = pCxt->pQueryCxt->charsetCxt; return (SNode*)op; _err: nodesDestroyNode(pLeft); @@ -1043,6 +1046,7 @@ SNode* createFunctionNode(SAstCreateContext* pCxt, const SToken* pFuncName, SNod COPY_STRING_FORM_ID_TOKEN(func->functionName, pFuncName); func->pParameterList = pParameterList; func->tz = pCxt->pQueryCxt->timezone; + func->charsetCxt = pCxt->pQueryCxt->charsetCxt; return (SNode*)func; _err: nodesDestroyList(pParameterList); @@ -1064,6 +1068,8 @@ SNode* createCastFunctionNode(SAstCreateContext* pCxt, SNode* pExpr, SDataType d pCxt->errCode = nodesListMakeAppend(&func->pParameterList, pExpr); CHECK_PARSER_STATUS(pCxt); func->tz = pCxt->pQueryCxt->timezone; + func->charsetCxt = pCxt->pQueryCxt->charsetCxt; + return (SNode*)func; _err: nodesDestroyNode((SNode*)func); @@ -1098,6 +1104,7 @@ SNode* createTrimFunctionNode(SAstCreateContext* pCxt, SNode* pExpr, ETrimType t func->trimType = type; pCxt->errCode = nodesListMakeAppend(&func->pParameterList, pExpr); CHECK_PARSER_STATUS(pCxt); + func->charsetCxt = pCxt->pQueryCxt->charsetCxt; return (SNode*)func; _err: nodesDestroyNode((SNode*)func); @@ -1116,6 +1123,7 @@ SNode* createTrimFunctionNodeExt(SAstCreateContext* pCxt, SNode* pExpr, SNode* p CHECK_PARSER_STATUS(pCxt); pCxt->errCode = nodesListMakeAppend(&func->pParameterList, pExpr2); CHECK_PARSER_STATUS(pCxt); + func->charsetCxt = pCxt->pQueryCxt->charsetCxt; return (SNode*)func; _err: nodesDestroyNode((SNode*)func); @@ -1501,6 +1509,7 @@ SNode* createCaseWhenNode(SAstCreateContext* pCxt, SNode* pCase, SNodeList* pWhe pCaseWhen->pWhenThenList = pWhenThenList; pCaseWhen->pElse = pElse; pCaseWhen->tz = pCxt->pQueryCxt->timezone; + pCaseWhen->charsetCxt = pCxt->pQueryCxt->charsetCxt; return (SNode*)pCaseWhen; _err: nodesDestroyNode(pCase); diff --git a/source/libs/parser/src/parInsertSml.c b/source/libs/parser/src/parInsertSml.c index b5cdf1e4ee..22d1f7edda 100644 --- a/source/libs/parser/src/parInsertSml.c +++ b/source/libs/parser/src/parInsertSml.c @@ -98,7 +98,7 @@ end: * @return int32_t */ static int32_t smlBuildTagRow(SArray* cols, SBoundColInfo* tags, SSchema* pSchema, STag** ppTag, SArray** tagName, - SMsgBuf* msg) { + SMsgBuf* msg, void* charsetCxt) { SArray* pTagArray = taosArrayInit(tags->numOfBound, sizeof(STagVal)); if (!pTagArray) { return terrno; @@ -142,7 +142,7 @@ static int32_t smlBuildTagRow(SArray* cols, SBoundColInfo* tags, SSchema* pSchem code = terrno; goto end; } - if (!taosMbsToUcs4(kv->value, kv->length, (TdUcs4*)(p), kv->length * TSDB_NCHAR_SIZE, &output)) { + if (!taosMbsToUcs4(kv->value, kv->length, (TdUcs4*)(p), kv->length * TSDB_NCHAR_SIZE, &output, charsetCxt)) { if (terrno == TAOS_SYSTEM_ERROR(E2BIG)) { taosMemoryFree(p); code = generateSyntaxErrMsg(msg, TSDB_CODE_PAR_VALUE_TOO_LONG, pTagSchema->name); @@ -221,7 +221,7 @@ int32_t smlBuildRow(STableDataCxt* pTableCxt) { return TSDB_CODE_SUCCESS; } -int32_t smlBuildCol(STableDataCxt* pTableCxt, SSchema* schema, void* data, int32_t index) { +int32_t smlBuildCol(STableDataCxt* pTableCxt, SSchema* schema, void* data, int32_t index, void* charsetCxt) { int ret = TSDB_CODE_SUCCESS; SSchema* pColSchema = schema + index; SColVal* pVal = taosArrayGet(pTableCxt->pValues, index); @@ -256,7 +256,7 @@ int32_t smlBuildCol(STableDataCxt* pTableCxt, SSchema* schema, void* data, int32 ret = terrno; goto end; } - if (!taosMbsToUcs4(kv->value, kv->length, (TdUcs4*)pUcs4, size, &len)) { + if (!taosMbsToUcs4(kv->value, kv->length, (TdUcs4*)pUcs4, size, &len, charsetCxt)) { if (terrno == TAOS_SYSTEM_ERROR(E2BIG)) { taosMemoryFree(pUcs4); ret = TSDB_CODE_PAR_VALUE_TOO_LONG; @@ -291,7 +291,7 @@ end: int32_t smlBindData(SQuery* query, bool dataFormat, SArray* tags, SArray* colsSchema, SArray* cols, STableMeta* pTableMeta, char* tableName, const char* sTableName, int32_t sTableNameLen, int32_t ttl, - char* msgBuf, int32_t msgBufLen) { + char* msgBuf, int32_t msgBufLen, void* charsetCxt) { SMsgBuf pBuf = {.buf = msgBuf, .len = msgBufLen}; SSchema* pTagsSchema = getTableTagSchema(pTableMeta); @@ -313,7 +313,7 @@ int32_t smlBindData(SQuery* query, bool dataFormat, SArray* tags, SArray* colsSc STag* pTag = NULL; - ret = smlBuildTagRow(tags, &bindTags, pTagsSchema, &pTag, &tagName, &pBuf); + ret = smlBuildTagRow(tags, &bindTags, pTagsSchema, &pTag, &tagName, &pBuf, charsetCxt); if (ret != TSDB_CODE_SUCCESS) { goto end; } @@ -411,7 +411,7 @@ int32_t smlBindData(SQuery* query, bool dataFormat, SArray* tags, SArray* colsSc ret = terrno; goto end; } - if (!taosMbsToUcs4(kv->value, kv->length, (TdUcs4*)pUcs4, pColSchema->bytes - VARSTR_HEADER_SIZE, &len)) { + if (!taosMbsToUcs4(kv->value, kv->length, (TdUcs4*)pUcs4, pColSchema->bytes - VARSTR_HEADER_SIZE, &len, charsetCxt)) { if (terrno == TAOS_SYSTEM_ERROR(E2BIG)) { uError("sml bind taosMbsToUcs4 error, kv length:%d, bytes:%d, kv->value:%s", (int)kv->length, pColSchema->bytes, kv->value); diff --git a/source/libs/parser/src/parInsertSql.c b/source/libs/parser/src/parInsertSql.c index d3e53205e7..a7d1351aa0 100644 --- a/source/libs/parser/src/parInsertSql.c +++ b/source/libs/parser/src/parInsertSql.c @@ -477,7 +477,7 @@ static int32_t parseVarbinary(SToken* pToken, uint8_t** pData, uint32_t* nData, } static int32_t parseTagToken(const char** end, SToken* pToken, SSchema* pSchema, int16_t timePrec, STagVal* val, - SMsgBuf* pMsgBuf, timezone_t tz) { + SMsgBuf* pMsgBuf, timezone_t tz, void *charsetCxt) { int64_t iv; uint64_t uv; char* endptr = NULL; @@ -685,7 +685,7 @@ static int32_t parseTagToken(const char** end, SToken* pToken, SSchema* pSchema, if (p == NULL) { return terrno; } - if (!taosMbsToUcs4(pToken->z, pToken->n, (TdUcs4*)(p), realLen, &output)) { + if (!taosMbsToUcs4(pToken->z, pToken->n, (TdUcs4*)(p), realLen, &output, charsetCxt)) { if (terrno == TAOS_SYSTEM_ERROR(E2BIG)) { taosMemoryFree(p); return generateSyntaxErrMsg(pMsgBuf, TSDB_CODE_PAR_VALUE_TOO_LONG, pSchema->name); @@ -732,7 +732,7 @@ static int32_t parseBoundTagsClause(SInsertParseContext* pCxt, SVnodeModifyOpStm } int32_t parseTagValue(SMsgBuf* pMsgBuf, const char** pSql, uint8_t precision, SSchema* pTagSchema, SToken* pToken, - SArray* pTagName, SArray* pTagVals, STag** pTag, timezone_t tz) { + SArray* pTagName, SArray* pTagVals, STag** pTag, timezone_t tz, void *charsetCxt) { bool isNull = isNullValue(pTagSchema->type, pToken); if (!isNull && pTagName) { if (NULL == taosArrayPush(pTagName, pTagSchema->name)) { @@ -748,14 +748,14 @@ int32_t parseTagValue(SMsgBuf* pMsgBuf, const char** pSql, uint8_t precision, SS if (isNull) { return tTagNew(pTagVals, 1, true, pTag); } else { - return parseJsontoTagData(pToken->z, pTagVals, pTag, pMsgBuf); + return parseJsontoTagData(pToken->z, pTagVals, pTag, pMsgBuf, charsetCxt); } } if (isNull) return 0; STagVal val = {0}; - int32_t code = parseTagToken(pSql, pToken, pTagSchema, precision, &val, pMsgBuf, tz); + int32_t code = parseTagToken(pSql, pToken, pTagSchema, precision, &val, pMsgBuf, tz, charsetCxt); if (TSDB_CODE_SUCCESS == code) { if (NULL == taosArrayPush(pTagVals, &val)) { code = terrno; @@ -978,7 +978,7 @@ static int32_t parseTagsClauseImpl(SInsertParseContext* pCxt, SVnodeModifyOpStmt code = buildSyntaxErrMsg(&pCxt->msg, "not expected tags values ", token.z); } if (TSDB_CODE_SUCCESS == code) { - code = parseTagValue(&pCxt->msg, &pStmt->pSql, precision, pTagSchema, &token, pTagName, pTagVals, &pTag, pCxt->pComCxt->timezone); + code = parseTagValue(&pCxt->msg, &pStmt->pSql, precision, pTagSchema, &token, pTagName, pTagVals, &pTag, pCxt->pComCxt->timezone, pCxt->pComCxt->charsetCxt); } } @@ -1620,7 +1620,7 @@ static int32_t parseValueTokenImpl(SInsertParseContext* pCxt, const char** pSql, if (NULL == pUcs4) { return terrno; } - if (!taosMbsToUcs4(pToken->z, pToken->n, (TdUcs4*)pUcs4, realLen, &len)) { + if (!taosMbsToUcs4(pToken->z, pToken->n, (TdUcs4*)pUcs4, realLen, &len, pCxt->pComCxt->charsetCxt)) { taosMemoryFree(pUcs4); if (terrno == TAOS_SYSTEM_ERROR(E2BIG)) { return generateSyntaxErrMsg(&pCxt->msg, TSDB_CODE_PAR_VALUE_TOO_LONG, pSchema->name); @@ -1788,7 +1788,7 @@ static int32_t processCtbTagsAfterCtbName(SInsertParseContext* pCxt, SVnodeModif if (code == TSDB_CODE_SUCCESS) { code = parseTagValue(&pCxt->msg, NULL, precision, pTagSchema, pTagToken, pStbRowsCxt->aTagNames, - pStbRowsCxt->aTagVals, &pStbRowsCxt->pTag, pCxt->pComCxt->timezone); + pStbRowsCxt->aTagVals, &pStbRowsCxt->pTag, pCxt->pComCxt->timezone, pCxt->pComCxt->charsetCxt); } } if (code == TSDB_CODE_SUCCESS && !pStbRowsCxt->isJsonTag) { @@ -1858,7 +1858,7 @@ static int32_t doGetStbRowValues(SInsertParseContext* pCxt, SVnodeModifyOpStmt* } if (code == TSDB_CODE_SUCCESS) { code = parseTagValue(&pCxt->msg, ppSql, precision, (SSchema*)pTagSchema, pToken, pTagNames, pTagVals, - &pStbRowsCxt->pTag, pCxt->pComCxt->timezone); + &pStbRowsCxt->pTag, pCxt->pComCxt->timezone, pCxt->pComCxt->charsetCxt); } } } else if (pCols->pColIndex[i] == tbnameIdx) { diff --git a/source/libs/parser/src/parInsertStmt.c b/source/libs/parser/src/parInsertStmt.c index c6951d229d..6e648a406f 100644 --- a/source/libs/parser/src/parInsertStmt.c +++ b/source/libs/parser/src/parInsertStmt.c @@ -123,7 +123,7 @@ int32_t qBuildStmtOutput(SQuery* pQuery, SHashObj* pVgHash, SHashObj* pBlockHash } int32_t qBindStmtTagsValue(void* pBlock, void* boundTags, int64_t suid, const char* sTableName, char* tName, - TAOS_MULTI_BIND* bind, char* msgBuf, int32_t msgBufLen) { + TAOS_MULTI_BIND* bind, char* msgBuf, int32_t msgBufLen, void* charsetCxt) { STableDataCxt* pDataBlock = (STableDataCxt*)pBlock; SMsgBuf pBuf = {.buf = msgBuf, .len = msgBufLen}; int32_t code = TSDB_CODE_SUCCESS; @@ -183,7 +183,7 @@ int32_t qBindStmtTagsValue(void* pBlock, void* boundTags, int64_t suid, const ch goto end; } memcpy(tmp, bind[c].buffer, colLen); - code = parseJsontoTagData(tmp, pTagArray, &pTag, &pBuf); + code = parseJsontoTagData(tmp, pTagArray, &pTag, &pBuf, charsetCxt); taosMemoryFree(tmp); if (code != TSDB_CODE_SUCCESS) { goto end; @@ -208,7 +208,7 @@ int32_t qBindStmtTagsValue(void* pBlock, void* boundTags, int64_t suid, const ch code = terrno; goto end; } - if (!taosMbsToUcs4(bind[c].buffer, colLen, (TdUcs4*)(p), colLen * TSDB_NCHAR_SIZE, &output)) { + if (!taosMbsToUcs4(bind[c].buffer, colLen, (TdUcs4*)(p), colLen * TSDB_NCHAR_SIZE, &output, charsetCxt)) { if (terrno == TAOS_SYSTEM_ERROR(E2BIG)) { taosMemoryFree(p); code = generateSyntaxErrMsg(&pBuf, TSDB_CODE_PAR_VALUE_TOO_LONG, pTagSchema->name); @@ -266,7 +266,7 @@ end: return code; } -int32_t convertStmtNcharCol(SMsgBuf* pMsgBuf, SSchema* pSchema, TAOS_MULTI_BIND* src, TAOS_MULTI_BIND* dst) { +int32_t convertStmtNcharCol(SMsgBuf* pMsgBuf, SSchema* pSchema, TAOS_MULTI_BIND* src, TAOS_MULTI_BIND* dst, void* charsetCxt) { int32_t output = 0; int32_t newBuflen = (pSchema->bytes - VARSTR_HEADER_SIZE) * src->num; if (dst->buffer_length < newBuflen) { @@ -292,7 +292,7 @@ int32_t convertStmtNcharCol(SMsgBuf* pMsgBuf, SSchema* pSchema, TAOS_MULTI_BIND* } if (!taosMbsToUcs4(((char*)src->buffer) + src->buffer_length * i, src->length[i], - (TdUcs4*)(((char*)dst->buffer) + dst->buffer_length * i), dst->buffer_length, &output)) { + (TdUcs4*)(((char*)dst->buffer) + dst->buffer_length * i), dst->buffer_length, &output, charsetCxt)) { if (terrno == TAOS_SYSTEM_ERROR(E2BIG)) { return generateSyntaxErrMsg(pMsgBuf, TSDB_CODE_PAR_VALUE_TOO_LONG, pSchema->name); } @@ -312,7 +312,7 @@ int32_t convertStmtNcharCol(SMsgBuf* pMsgBuf, SSchema* pSchema, TAOS_MULTI_BIND* } int32_t qBindStmtStbColsValue(void* pBlock, SArray* pCols, TAOS_MULTI_BIND* bind, char* msgBuf, int32_t msgBufLen, - STSchema** pTSchema, SBindInfo* pBindInfos) { + STSchema** pTSchema, SBindInfo* pBindInfos, void* charsetCxt) { STableDataCxt* pDataBlock = (STableDataCxt*)pBlock; SSchema* pSchema = getTableColumnSchema(pDataBlock->pMeta); SBoundColInfo* boundInfo = &pDataBlock->boundColsInfo; @@ -349,7 +349,7 @@ int32_t qBindStmtStbColsValue(void* pBlock, SArray* pCols, TAOS_MULTI_BIND* bind } if (TSDB_DATA_TYPE_NCHAR == pColSchema->type) { - code = convertStmtNcharCol(&pBuf, pColSchema, bind + c, &ncharBind); + code = convertStmtNcharCol(&pBuf, pColSchema, bind + c, &ncharBind, charsetCxt); if (code) { goto _return; } @@ -380,7 +380,7 @@ _return: return code; } -int32_t qBindStmtColsValue(void* pBlock, SArray* pCols, TAOS_MULTI_BIND* bind, char* msgBuf, int32_t msgBufLen) { +int32_t qBindStmtColsValue(void* pBlock, SArray* pCols, TAOS_MULTI_BIND* bind, char* msgBuf, int32_t msgBufLen, void* charsetCxt) { STableDataCxt* pDataBlock = (STableDataCxt*)pBlock; SSchema* pSchema = getTableColumnSchema(pDataBlock->pMeta); SBoundColInfo* boundInfo = &pDataBlock->boundColsInfo; @@ -406,7 +406,7 @@ int32_t qBindStmtColsValue(void* pBlock, SArray* pCols, TAOS_MULTI_BIND* bind, c } if (TSDB_DATA_TYPE_NCHAR == pColSchema->type) { - code = convertStmtNcharCol(&pBuf, pColSchema, bind + c, &ncharBind); + code = convertStmtNcharCol(&pBuf, pColSchema, bind + c, &ncharBind, charsetCxt); if (code) { goto _return; } @@ -434,7 +434,7 @@ _return: } int32_t qBindStmtSingleColValue(void* pBlock, SArray* pCols, TAOS_MULTI_BIND* bind, char* msgBuf, int32_t msgBufLen, - int32_t colIdx, int32_t rowNum) { + int32_t colIdx, int32_t rowNum, void* charsetCxt) { STableDataCxt* pDataBlock = (STableDataCxt*)pBlock; SSchema* pSchema = getTableColumnSchema(pDataBlock->pMeta); SBoundColInfo* boundInfo = &pDataBlock->boundColsInfo; @@ -459,7 +459,7 @@ int32_t qBindStmtSingleColValue(void* pBlock, SArray* pCols, TAOS_MULTI_BIND* bi } if (TSDB_DATA_TYPE_NCHAR == pColSchema->type) { - code = convertStmtNcharCol(&pBuf, pColSchema, bind, &ncharBind); + code = convertStmtNcharCol(&pBuf, pColSchema, bind, &ncharBind, charsetCxt); if (code) { goto _return; } @@ -483,7 +483,7 @@ _return: } int32_t qBindStmtTagsValue2(void* pBlock, void* boundTags, int64_t suid, const char* sTableName, char* tName, - TAOS_STMT2_BIND* bind, char* msgBuf, int32_t msgBufLen) { + TAOS_STMT2_BIND* bind, char* msgBuf, int32_t msgBufLen, void* charsetCxt) { STableDataCxt* pDataBlock = (STableDataCxt*)pBlock; SMsgBuf pBuf = {.buf = msgBuf, .len = msgBufLen}; int32_t code = TSDB_CODE_SUCCESS; @@ -543,7 +543,7 @@ int32_t qBindStmtTagsValue2(void* pBlock, void* boundTags, int64_t suid, const c goto end; } memcpy(tmp, bind[c].buffer, colLen); - code = parseJsontoTagData(tmp, pTagArray, &pTag, &pBuf); + code = parseJsontoTagData(tmp, pTagArray, &pTag, &pBuf, charsetCxt); taosMemoryFree(tmp); if (code != TSDB_CODE_SUCCESS) { goto end; @@ -568,7 +568,7 @@ int32_t qBindStmtTagsValue2(void* pBlock, void* boundTags, int64_t suid, const c code = terrno; goto end; } - if (!taosMbsToUcs4(bind[c].buffer, colLen, (TdUcs4*)(p), colLen * TSDB_NCHAR_SIZE, &output)) { + if (!taosMbsToUcs4(bind[c].buffer, colLen, (TdUcs4*)(p), colLen * TSDB_NCHAR_SIZE, &output, charsetCxt)) { if (terrno == TAOS_SYSTEM_ERROR(E2BIG)) { taosMemoryFree(p); code = generateSyntaxErrMsg(&pBuf, TSDB_CODE_PAR_VALUE_TOO_LONG, pTagSchema->name); @@ -626,7 +626,7 @@ end: return code; } -static int32_t convertStmtStbNcharCol2(SMsgBuf* pMsgBuf, SSchema* pSchema, TAOS_STMT2_BIND* src, TAOS_STMT2_BIND* dst) { +static int32_t convertStmtStbNcharCol2(SMsgBuf* pMsgBuf, SSchema* pSchema, TAOS_STMT2_BIND* src, TAOS_STMT2_BIND* dst, void *charsetCxt) { int32_t output = 0; const int32_t max_buf_len = pSchema->bytes - VARSTR_HEADER_SIZE; @@ -648,7 +648,7 @@ static int32_t convertStmtStbNcharCol2(SMsgBuf* pMsgBuf, SSchema* pSchema, TAOS_ continue; } - if (!taosMbsToUcs4(src_buf, src->length[i], (TdUcs4*)dst_buf, max_buf_len, &output)) { + if (!taosMbsToUcs4(src_buf, src->length[i], (TdUcs4*)dst_buf, max_buf_len, &output, charsetCxt)) { if (terrno == TAOS_SYSTEM_ERROR(E2BIG)) { return generateSyntaxErrMsg(pMsgBuf, TSDB_CODE_PAR_VALUE_TOO_LONG, pSchema->name); } @@ -670,7 +670,7 @@ static int32_t convertStmtStbNcharCol2(SMsgBuf* pMsgBuf, SSchema* pSchema, TAOS_ } int32_t qBindStmtStbColsValue2(void* pBlock, SArray* pCols, TAOS_STMT2_BIND* bind, char* msgBuf, int32_t msgBufLen, - STSchema** pTSchema, SBindInfo2* pBindInfos) { + STSchema** pTSchema, SBindInfo2* pBindInfos, void *charsetCxt) { STableDataCxt* pDataBlock = (STableDataCxt*)pBlock; SSchema* pSchema = getTableColumnSchema(pDataBlock->pMeta); SBoundColInfo* boundInfo = &pDataBlock->boundColsInfo; @@ -720,7 +720,7 @@ int32_t qBindStmtStbColsValue2(void* pBlock, SArray* pCols, TAOS_STMT2_BIND* bin } if (TSDB_DATA_TYPE_NCHAR == pColSchema->type) { - code = convertStmtStbNcharCol2(&pBuf, pColSchema, bind + c, &ncharBind); + code = convertStmtStbNcharCol2(&pBuf, pColSchema, bind + c, &ncharBind, charsetCxt); if (code) { goto _return; } @@ -755,7 +755,7 @@ _return: return code; } -static int32_t convertStmtNcharCol2(SMsgBuf* pMsgBuf, SSchema* pSchema, TAOS_STMT2_BIND* src, TAOS_STMT2_BIND* dst) { +static int32_t convertStmtNcharCol2(SMsgBuf* pMsgBuf, SSchema* pSchema, TAOS_STMT2_BIND* src, TAOS_STMT2_BIND* dst, void *charsetCxt) { int32_t output = 0; const int32_t max_buf_len = pSchema->bytes - VARSTR_HEADER_SIZE; @@ -785,7 +785,7 @@ static int32_t convertStmtNcharCol2(SMsgBuf* pMsgBuf, SSchema* pSchema, TAOS_STM /*if (!taosMbsToUcs4(((char*)src->buffer) + src->buffer_length * i, src->length[i], (TdUcs4*)(((char*)dst->buffer) + dst->buffer_length * i), dst->buffer_length, &output)) {*/ - if (!taosMbsToUcs4(src_buf, src->length[i], (TdUcs4*)dst_buf, max_buf_len, &output)) { + if (!taosMbsToUcs4(src_buf, src->length[i], (TdUcs4*)dst_buf, max_buf_len, &output, charsetCxt)) { if (terrno == TAOS_SYSTEM_ERROR(E2BIG)) { return generateSyntaxErrMsg(pMsgBuf, TSDB_CODE_PAR_VALUE_TOO_LONG, pSchema->name); } @@ -806,7 +806,7 @@ static int32_t convertStmtNcharCol2(SMsgBuf* pMsgBuf, SSchema* pSchema, TAOS_STM return TSDB_CODE_SUCCESS; } -int32_t qBindStmtColsValue2(void* pBlock, SArray* pCols, TAOS_STMT2_BIND* bind, char* msgBuf, int32_t msgBufLen) { +int32_t qBindStmtColsValue2(void* pBlock, SArray* pCols, TAOS_STMT2_BIND* bind, char* msgBuf, int32_t msgBufLen, void *charsetCxt) { STableDataCxt* pDataBlock = (STableDataCxt*)pBlock; SSchema* pSchema = getTableColumnSchema(pDataBlock->pMeta); SBoundColInfo* boundInfo = &pDataBlock->boundColsInfo; @@ -836,7 +836,7 @@ int32_t qBindStmtColsValue2(void* pBlock, SArray* pCols, TAOS_STMT2_BIND* bind, } if (TSDB_DATA_TYPE_NCHAR == pColSchema->type) { - code = convertStmtNcharCol2(&pBuf, pColSchema, bind + c, &ncharBind); + code = convertStmtNcharCol2(&pBuf, pColSchema, bind + c, &ncharBind, charsetCxt); if (code) { goto _return; } @@ -864,7 +864,7 @@ _return: } int32_t qBindStmtSingleColValue2(void* pBlock, SArray* pCols, TAOS_STMT2_BIND* bind, char* msgBuf, int32_t msgBufLen, - int32_t colIdx, int32_t rowNum) { + int32_t colIdx, int32_t rowNum, void *charsetCxt) { STableDataCxt* pDataBlock = (STableDataCxt*)pBlock; SSchema* pSchema = getTableColumnSchema(pDataBlock->pMeta); SBoundColInfo* boundInfo = &pDataBlock->boundColsInfo; @@ -889,7 +889,7 @@ int32_t qBindStmtSingleColValue2(void* pBlock, SArray* pCols, TAOS_STMT2_BIND* b } if (TSDB_DATA_TYPE_NCHAR == pColSchema->type) { - code = convertStmtNcharCol2(&pBuf, pColSchema, bind, &ncharBind); + code = convertStmtNcharCol2(&pBuf, pColSchema, bind, &ncharBind, charsetCxt); if (code) { goto _return; } diff --git a/source/libs/parser/src/parTranslater.c b/source/libs/parser/src/parTranslater.c index c13686dddc..3d75682251 100755 --- a/source/libs/parser/src/parTranslater.c +++ b/source/libs/parser/src/parTranslater.c @@ -2148,7 +2148,7 @@ static EDealRes translateNormalValue(STranslateContext* pCxt, SValueNode* pVal, int32_t len = 0; if (!taosMbsToUcs4(pVal->literal, strlen(pVal->literal), (TdUcs4*)varDataVal(pVal->datum.p), - targetDt.bytes - VARSTR_HEADER_SIZE, &len)) { + targetDt.bytes - VARSTR_HEADER_SIZE, &len, pCxt->pParseCxt->charsetCxt)) { return generateDealNodeErrMsg(pCxt, TSDB_CODE_PAR_WRONG_VALUE_TYPE, pVal->literal); } varDataSetLen(pVal->datum.p, len); @@ -14169,7 +14169,7 @@ static int32_t buildKVRowForBindTags(STranslateContext* pCxt, SCreateSubTableCla if (pSchema->type == TSDB_DATA_TYPE_JSON) { isJson = true; } - code = parseTagValue(&pCxt->msgBuf, &tagStr, precision, pSchema, &token, tagName, pTagArray, ppTag, pCxt->pParseCxt->timezone); + code = parseTagValue(&pCxt->msgBuf, &tagStr, precision, pSchema, &token, tagName, pTagArray, ppTag, pCxt->pParseCxt->timezone, pCxt->pParseCxt->charsetCxt); } if (TSDB_CODE_SUCCESS == code) { @@ -14230,7 +14230,7 @@ static int32_t buildKVRowForAllTags(STranslateContext* pCxt, SCreateSubTableClau if (pTagSchema->type == TSDB_DATA_TYPE_JSON) { isJson = true; } - code = parseTagValue(&pCxt->msgBuf, &tagStr, precision, pTagSchema, &token, tagName, pTagArray, ppTag, pCxt->pParseCxt->timezone); + code = parseTagValue(&pCxt->msgBuf, &tagStr, precision, pTagSchema, &token, tagName, pTagArray, ppTag, pCxt->pParseCxt->timezone, pCxt->pParseCxt->charsetCxt); } if (TSDB_CODE_SUCCESS == code) { @@ -14426,7 +14426,7 @@ static int32_t fillVgroupInfo(SParseContext* pParseCxt, const SName* pName, SVgr return code; } -static int32_t parseOneStbRow(SMsgBuf* pMsgBuf, SParseFileContext* pParFileCxt, timezone_t tz) { +static int32_t parseOneStbRow(SMsgBuf* pMsgBuf, SParseFileContext* pParFileCxt, timezone_t tz, void *charsetCxt) { int32_t code = TSDB_CODE_SUCCESS; int sz = taosArrayGetSize(pParFileCxt->aTagIndexs); int32_t numOfTags = getNumOfTags(pParFileCxt->pStbMeta); @@ -14458,7 +14458,7 @@ static int32_t parseOneStbRow(SMsgBuf* pMsgBuf, SParseFileContext* pParFileCxt, if (TSDB_CODE_SUCCESS == code) { SArray* aTagNames = pParFileCxt->tagNameFilled ? NULL : pParFileCxt->aTagNames; code = parseTagValue(pMsgBuf, &pParFileCxt->pSql, precision, (SSchema*)pTagSchema, &token, aTagNames, - pParFileCxt->aTagVals, &pParFileCxt->pTag, tz); + pParFileCxt->aTagVals, &pParFileCxt->pTag, tz, charsetCxt); } } else { // parse tbname @@ -14524,7 +14524,7 @@ static int32_t parseCsvFile(SMsgBuf* pMsgBuf, SParseContext* pParseCxt, SParseFi (void)strtolower(pLine, pLine); pParFileCxt->pSql = pLine; - code = parseOneStbRow(pMsgBuf, pParFileCxt, pParseCxt->timezone); + code = parseOneStbRow(pMsgBuf, pParFileCxt, pParseCxt->timezone, pParseCxt->charsetCxt); if (TSDB_CODE_SUCCESS == code) { code = fillVgroupInfo(pParseCxt, &pParFileCxt->ctbName, &pParFileCxt->vg); @@ -15225,7 +15225,7 @@ static int32_t buildUpdateTagValReq(STranslateContext* pCxt, SAlterTableStmt* pS if (TSDB_CODE_SUCCESS == code) { code = parseTagValue(&pCxt->msgBuf, &tagStr, pTableMeta->tableInfo.precision, pSchema, &token, NULL, - pReq->pTagArray, &pTag, pCxt->pParseCxt->timezone); + pReq->pTagArray, &pTag, pCxt->pParseCxt->timezone, pCxt->pParseCxt->charsetCxt); if (pSchema->type == TSDB_DATA_TYPE_JSON && token.type == TK_NULL && code == TSDB_CODE_SUCCESS) { pReq->tagFree = true; } diff --git a/source/libs/parser/src/parUtil.c b/source/libs/parser/src/parUtil.c index 44e44982a3..07ea54c44f 100644 --- a/source/libs/parser/src/parUtil.c +++ b/source/libs/parser/src/parUtil.c @@ -402,7 +402,7 @@ static bool isValidateTag(char* input) { return true; } -int32_t parseJsontoTagData(const char* json, SArray* pTagVals, STag** ppTag, void* pMsgBuf) { +int32_t parseJsontoTagData(const char* json, SArray* pTagVals, STag** ppTag, void* pMsgBuf, void *charsetCxt) { int32_t retCode = TSDB_CODE_SUCCESS; cJSON* root = NULL; SHashObj* keyHash = NULL; @@ -471,7 +471,7 @@ int32_t parseJsontoTagData(const char* json, SArray* pTagVals, STag** ppTag, voi goto end; } val.type = TSDB_DATA_TYPE_NCHAR; - if (valLen > 0 && !taosMbsToUcs4(jsonValue, valLen, (TdUcs4*)tmp, (int32_t)(valLen * TSDB_NCHAR_SIZE), &valLen)) { + if (valLen > 0 && !taosMbsToUcs4(jsonValue, valLen, (TdUcs4*)tmp, (int32_t)(valLen * TSDB_NCHAR_SIZE), &valLen, charsetCxt)) { uError("charset:%s to %s. val:%s, errno:%s, convert failed.", DEFAULT_UNICODE_ENCODEC, tsCharset, jsonValue, strerror(terrno)); retCode = buildSyntaxErrMsg(pMsgBuf, "charset convert json error", jsonValue); diff --git a/source/libs/parser/src/parser.c b/source/libs/parser/src/parser.c index e2135bfd63..d9783fb9a3 100644 --- a/source/libs/parser/src/parser.c +++ b/source/libs/parser/src/parser.c @@ -153,7 +153,7 @@ static int32_t parseSqlSyntax(SParseContext* pCxt, SQuery** pQuery, SParseMetaCa return code; } -static int32_t setValueByBindParam(SValueNode* pVal, TAOS_MULTI_BIND* pParam) { +static int32_t setValueByBindParam(SValueNode* pVal, TAOS_MULTI_BIND* pParam, void *charsetCxt) { if (!pParam || IS_NULL_TYPE(pParam->buffer_type)) { return TSDB_CODE_APP_ERROR; } @@ -200,7 +200,7 @@ static int32_t setValueByBindParam(SValueNode* pVal, TAOS_MULTI_BIND* pParam) { int32_t output = 0; if (!taosMbsToUcs4(pParam->buffer, inputSize, (TdUcs4*)varDataVal(pVal->datum.p), pVal->node.resType.bytes, - &output)) { + &output, charsetCxt)) { return terrno; } varDataSetLen(pVal->datum.p, output); @@ -417,19 +417,19 @@ int32_t qInitKeywordsTable() { return taosInitKeywordsTable(); } void qCleanupKeywordsTable() { taosCleanupKeywordsTable(); } -int32_t qStmtBindParams(SQuery* pQuery, TAOS_MULTI_BIND* pParams, int32_t colIdx) { +int32_t qStmtBindParams(SQuery* pQuery, TAOS_MULTI_BIND* pParams, int32_t colIdx, void *charsetCxt) { int32_t code = TSDB_CODE_SUCCESS; if (colIdx < 0) { int32_t size = taosArrayGetSize(pQuery->pPlaceholderValues); for (int32_t i = 0; i < size; ++i) { - code = setValueByBindParam((SValueNode*)taosArrayGetP(pQuery->pPlaceholderValues, i), pParams + i); + code = setValueByBindParam((SValueNode*)taosArrayGetP(pQuery->pPlaceholderValues, i), pParams + i, charsetCxt); if (TSDB_CODE_SUCCESS != code) { return code; } } } else { - code = setValueByBindParam((SValueNode*)taosArrayGetP(pQuery->pPlaceholderValues, colIdx), pParams); + code = setValueByBindParam((SValueNode*)taosArrayGetP(pQuery->pPlaceholderValues, colIdx), pParams, charsetCxt); } if (TSDB_CODE_SUCCESS == code && (colIdx < 0 || colIdx + 1 == pQuery->placeholderNum)) { @@ -443,7 +443,7 @@ int32_t qStmtBindParams(SQuery* pQuery, TAOS_MULTI_BIND* pParams, int32_t colIdx return code; } -static int32_t setValueByBindParam2(SValueNode* pVal, TAOS_STMT2_BIND* pParam) { +static int32_t setValueByBindParam2(SValueNode* pVal, TAOS_STMT2_BIND* pParam, void* charsetCxt) { if (!pParam || IS_NULL_TYPE(pParam->buffer_type)) { return TSDB_CODE_APP_ERROR; } @@ -490,7 +490,7 @@ static int32_t setValueByBindParam2(SValueNode* pVal, TAOS_STMT2_BIND* pParam) { int32_t output = 0; if (!taosMbsToUcs4(pParam->buffer, inputSize, (TdUcs4*)varDataVal(pVal->datum.p), pVal->node.resType.bytes, - &output)) { + &output, charsetCxt)) { return terrno; } varDataSetLen(pVal->datum.p, output); @@ -509,19 +509,19 @@ static int32_t setValueByBindParam2(SValueNode* pVal, TAOS_STMT2_BIND* pParam) { return TSDB_CODE_SUCCESS; } -int32_t qStmtBindParams2(SQuery* pQuery, TAOS_STMT2_BIND* pParams, int32_t colIdx) { +int32_t qStmtBindParams2(SQuery* pQuery, TAOS_STMT2_BIND* pParams, int32_t colIdx, void* charsetCxt) { int32_t code = TSDB_CODE_SUCCESS; if (colIdx < 0) { int32_t size = taosArrayGetSize(pQuery->pPlaceholderValues); for (int32_t i = 0; i < size; ++i) { - code = setValueByBindParam2((SValueNode*)taosArrayGetP(pQuery->pPlaceholderValues, i), pParams + i); + code = setValueByBindParam2((SValueNode*)taosArrayGetP(pQuery->pPlaceholderValues, i), pParams + i, charsetCxt); if (TSDB_CODE_SUCCESS != code) { return code; } } } else { - code = setValueByBindParam2((SValueNode*)taosArrayGetP(pQuery->pPlaceholderValues, colIdx), pParams); + code = setValueByBindParam2((SValueNode*)taosArrayGetP(pQuery->pPlaceholderValues, colIdx), pParams, charsetCxt); } if (TSDB_CODE_SUCCESS == code && (colIdx < 0 || colIdx + 1 == pQuery->placeholderNum)) { diff --git a/source/libs/planner/src/planOptimizer.c b/source/libs/planner/src/planOptimizer.c index a1809ff137..2d060fe9bc 100644 --- a/source/libs/planner/src/planOptimizer.c +++ b/source/libs/planner/src/planOptimizer.c @@ -6725,7 +6725,7 @@ static int32_t fillTSMAOptCtx(STSMAOptCtx* pTsmaOptCtx, SScanLogicNode* pScan) { pTsmaOptCtx->queryInterval->sliding = pWindow->sliding; pTsmaOptCtx->queryInterval->slidingUnit = pWindow->slidingUnit; pTsmaOptCtx->queryInterval->precision = pWindow->node.precision; - pTsmaOptCtx->queryInterval->tz = tsTimezone; + pTsmaOptCtx->queryInterval->tz = taosGetLocalTimezoneOffset(); pTsmaOptCtx->pAggFuncs = pWindow->pFuncs; pTsmaOptCtx->ppParentTsmaSubplans = &pWindow->pTsmaSubplans; } else { diff --git a/source/libs/planner/test/planTestUtil.cpp b/source/libs/planner/test/planTestUtil.cpp index da38a72953..902e254465 100644 --- a/source/libs/planner/test/planTestUtil.cpp +++ b/source/libs/planner/test/planTestUtil.cpp @@ -377,7 +377,7 @@ class PlannerTestBaseImpl { } void doBindParams(SQuery* pQuery, TAOS_MULTI_BIND* pParams, int32_t colIdx) { - DO_WITH_THROW(qStmtBindParams, pQuery, pParams, colIdx); + DO_WITH_THROW(qStmtBindParams, pQuery, pParams, colIdx, NULL); if (colIdx < 0 || pQuery->placeholderNum == colIdx + 1) { res_.boundAst_ = toString(pQuery->pRoot); } diff --git a/source/libs/qcom/src/queryUtil.c b/source/libs/qcom/src/queryUtil.c index 54e92c6a1b..9b59e321a3 100644 --- a/source/libs/qcom/src/queryUtil.c +++ b/source/libs/qcom/src/queryUtil.c @@ -397,7 +397,7 @@ int32_t dataConverToStr(char* str, int64_t capacity, int type, void* buf, int32_ } *str = '"'; - int32_t length = taosUcs4ToMbs((TdUcs4*)buf, bufSize, str + 1); + int32_t length = taosUcs4ToMbs((TdUcs4*)buf, bufSize, str + 1, NULL); if (length <= 0) { return TSDB_CODE_TSC_INVALID_VALUE; } @@ -430,7 +430,7 @@ int32_t dataConverToStr(char* str, int64_t capacity, int type, void* buf, int32_ return TSDB_CODE_SUCCESS; } -void parseTagDatatoJson(void* p, char** jsonStr) { +void parseTagDatatoJson(void* p, char** jsonStr, void *charsetCxt) { if (!p || !jsonStr) { qError("parseTagDatatoJson invalid input, line:%d", __LINE__); return; @@ -475,7 +475,7 @@ void parseTagDatatoJson(void* p, char** jsonStr) { if (tagJsonValue == NULL) { goto end; } - int32_t length = taosUcs4ToMbs((TdUcs4*)pTagVal->pData, pTagVal->nData, tagJsonValue); + int32_t length = taosUcs4ToMbs((TdUcs4*)pTagVal->pData, pTagVal->nData, tagJsonValue, charsetCxt); if (length < 0) { qError("charset:%s to %s. val:%s convert json value failed.", DEFAULT_UNICODE_ENCODEC, tsCharset, pTagVal->pData); diff --git a/source/libs/scalar/src/filter.c b/source/libs/scalar/src/filter.c index cb08851196..f0325dd174 100644 --- a/source/libs/scalar/src/filter.c +++ b/source/libs/scalar/src/filter.c @@ -2266,7 +2266,7 @@ int32_t fltInitValFieldData(SFilterInfo *info) { // match/nmatch for nchar type need convert from ucs4 to mbs if (type == TSDB_DATA_TYPE_NCHAR && (unit->compare.optr == OP_TYPE_MATCH || unit->compare.optr == OP_TYPE_NMATCH)) { char newValData[TSDB_REGEX_STRING_DEFAULT_LEN * TSDB_NCHAR_SIZE + VARSTR_HEADER_SIZE] = {0}; - int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(fi->data), varDataLen(fi->data), varDataVal(newValData)); + int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(fi->data), varDataLen(fi->data), varDataVal(newValData), NULL); if (len < 0) { qError("filterInitValFieldData taosUcs4ToMbs error 1"); return TSDB_CODE_SCALAR_CONVERT_ERROR; @@ -3603,7 +3603,7 @@ int32_t filterExecuteImplMisc(void *pinfo, int32_t numOfRows, SColumnInfoData *p if (newColData == NULL) { FLT_ERR_RET(terrno); } - int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(colData), varDataLen(colData), varDataVal(newColData)); + int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(colData), varDataLen(colData), varDataVal(newColData), NULL); if (len < 0) { qError("castConvert1 taosUcs4ToMbs error"); taosMemoryFreeClear(newColData); @@ -3678,7 +3678,7 @@ int32_t filterExecuteImpl(void *pinfo, int32_t numOfRows, SColumnInfoData *pRes, if (newColData == NULL) { FLT_ERR_RET(terrno); } - int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(colData), varDataLen(colData), varDataVal(newColData)); + int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(colData), varDataLen(colData), varDataVal(newColData), NULL); if (len < 0) { qError("castConvert1 taosUcs4ToMbs error"); taosMemoryFreeClear(newColData); @@ -4614,7 +4614,7 @@ int32_t filterConverNcharColumns(SFilterInfo *info, int32_t rows, bool *gotNchar varDataCopy(dst, src); continue; } - bool ret = taosMbsToUcs4(varDataVal(src), varDataLen(src), (TdUcs4 *)varDataVal(dst), bufSize, &len); + bool ret = taosMbsToUcs4(varDataVal(src), varDataLen(src), (TdUcs4 *)varDataVal(dst), bufSize, &len, NULL); if (!ret) { qError("filterConverNcharColumns taosMbsToUcs4 error"); return TSDB_CODE_SCALAR_CONVERT_ERROR; diff --git a/source/libs/scalar/src/scalar.c b/source/libs/scalar/src/scalar.c index d533eba74e..e871ab031b 100644 --- a/source/libs/scalar/src/scalar.c +++ b/source/libs/scalar/src/scalar.c @@ -24,7 +24,7 @@ int32_t scalarGetOperatorParamNum(EOperatorType type) { int32_t sclConvertToTsValueNode(int8_t precision, SValueNode *valueNode) { char *timeStr = valueNode->datum.p; int64_t value = 0; - int32_t code = convertStringToTimestamp(valueNode->node.resType.type, valueNode->datum.p, precision, &value, valueNode->tz); //todo tz + int32_t code = convertStringToTimestamp(valueNode->node.resType.type, valueNode->datum.p, precision, &value, valueNode->tz, valueNode->charsetCxt); //todo tz if (code != TSDB_CODE_SUCCESS) { return code; } @@ -82,7 +82,9 @@ int32_t sclConvertValueToSclParam(SValueNode *pValueNode, SScalarParam *out, int } in.tz = pValueNode->tz; + in.charsetCxt = pValueNode->charsetCxt; out->tz = pValueNode->tz; + out->charsetCxt = pValueNode->charsetCxt; code = vectorConvertSingleColImpl(&in, out, overflow, -1, -1); _exit: @@ -589,6 +591,7 @@ int32_t sclInitOperatorParams(SScalarParam **pParams, SOperatorNode *node, SScal SCL_ERR_JRET(sclInitParam(node->pLeft, ¶mList[0], ctx, rowNum)); paramList[0].tz = node->tz; + paramList[0].charsetCxt = node->charsetCxt; if (paramNum > 1) { TSWAP(ctx->type.selfType, ctx->type.peerType); SCL_ERR_JRET(sclInitParam(node->pRight, ¶mList[1], ctx, rowNum)); @@ -760,6 +763,7 @@ int32_t sclExecFunction(SFunctionNode *node, SScalarCtx *ctx, SScalarParam *outp int32_t code = 0; SCL_ERR_RET(sclInitParamList(¶ms, node->pParameterList, ctx, ¶mNum, &rowNum)); params->tz = node->tz; + params->charsetCxt = node->charsetCxt; if (fmIsUserDefinedFunc(node->funcId)) { code = callUdfScalarFunc(node->functionName, params, paramNum, output); @@ -947,6 +951,7 @@ int32_t sclExecCaseWhen(SCaseWhenNode *node, SScalarCtx *ctx, SScalarParam *outp SCL_ERR_JRET(sclGetNodeRes(node->pCase, ctx, &pCase)); SCL_ERR_JRET(sclGetNodeRes(node->pElse, ctx, &pElse)); pCase->tz = node->tz; + pCase->charsetCxt = node->charsetCxt; SDataType compType = {0}; compType.type = TSDB_DATA_TYPE_BOOL; compType.bytes = tDataTypes[compType.type].bytes; diff --git a/source/libs/scalar/src/sclfunc.c b/source/libs/scalar/src/sclfunc.c index e51ab1c04b..fb4f0db631 100644 --- a/source/libs/scalar/src/sclfunc.c +++ b/source/libs/scalar/src/sclfunc.c @@ -12,8 +12,8 @@ typedef float (*_float_fn_2)(float, float); typedef double (*_double_fn)(double); typedef double (*_double_fn_2)(double, double); typedef int (*_conv_fn)(int); -typedef void (*_trim_space_fn)(char *, char *, int32_t, int32_t); -typedef int32_t (*_trim_fn)(char *, char *, char *, int32_t, int32_t); +typedef void (*_trim_space_fn)(char *, char *, int32_t, int32_t, void*); +typedef int32_t (*_trim_fn)(char *, char *, char *, int32_t, int32_t, void*); typedef int32_t (*_len_fn)(char *, int32_t, VarDataLenT *); /** Math functions **/ @@ -500,7 +500,7 @@ static int32_t tcharlength(char *input, int32_t type, VarDataLenT *len) { return TSDB_CODE_SUCCESS; } -static void tltrimspace(char *input, char *output, int32_t type, int32_t charLen) { +static void tltrimspace(char *input, char *output, int32_t type, int32_t charLen, void* charsetCxt) { int32_t numOfSpaces = 0; if (type == TSDB_DATA_TYPE_VARCHAR) { for (int32_t i = 0; i < charLen; ++i) { @@ -530,7 +530,7 @@ static void tltrimspace(char *input, char *output, int32_t type, int32_t charLen varDataSetLen(output, resLen); } -static void tlrtrimspace(char *input, char *output, int32_t type, int32_t charLen) { +static void tlrtrimspace(char *input, char *output, int32_t type, int32_t charLen, void* charsetCxt) { int32_t numOfLeftSpaces = 0; int32_t numOfRightSpaces = 0; if (type == TSDB_DATA_TYPE_VARCHAR) { @@ -615,12 +615,12 @@ static int32_t trimHelper(char *orgStr, char* remStr, int32_t orgLen, int32_t re } } -static int32_t convVarcharToNchar(char *input, char **output, int32_t inputLen, int32_t *outputLen) { +static int32_t convVarcharToNchar(char *input, char **output, int32_t inputLen, int32_t *outputLen, void* charsetCxt) { *output = taosMemoryCalloc(inputLen * TSDB_NCHAR_SIZE, 1); if (NULL == *output) { return terrno; } - bool ret = taosMbsToUcs4(input, inputLen, (TdUcs4 *)*output, inputLen * TSDB_NCHAR_SIZE, outputLen); + bool ret = taosMbsToUcs4(input, inputLen, (TdUcs4 *)*output, inputLen * TSDB_NCHAR_SIZE, outputLen, charsetCxt); if (!ret) { taosMemoryFreeClear(*output); return TSDB_CODE_SCALAR_CONVERT_ERROR; @@ -628,12 +628,12 @@ static int32_t convVarcharToNchar(char *input, char **output, int32_t inputLen, return TSDB_CODE_SUCCESS; } -static int32_t convNcharToVarchar(char *input, char **output, int32_t inputLen, int32_t *outputLen) { +static int32_t convNcharToVarchar(char *input, char **output, int32_t inputLen, int32_t *outputLen, void* charsetCxt) { *output = taosMemoryCalloc(inputLen, 1); if (NULL == *output) { return terrno; } - *outputLen = taosUcs4ToMbs((TdUcs4 *)input, inputLen, *output); + *outputLen = taosUcs4ToMbs((TdUcs4 *)input, inputLen, *output, charsetCxt); if (*outputLen < 0) { taosMemoryFree(*output); return TSDB_CODE_SCALAR_CONVERT_ERROR; @@ -641,14 +641,14 @@ static int32_t convNcharToVarchar(char *input, char **output, int32_t inputLen, return TSDB_CODE_SUCCESS; } -static int32_t convBetweenNcharAndVarchar(char *input, char **output, int32_t inputLen, int32_t *outputLen, int32_t wantType) { +static int32_t convBetweenNcharAndVarchar(char *input, char **output, int32_t inputLen, int32_t *outputLen, int32_t wantType, void* charsetCxt) { if (wantType == TSDB_DATA_TYPE_NCHAR) { - return convVarcharToNchar(input, output, inputLen, outputLen); + return convVarcharToNchar(input, output, inputLen, outputLen, charsetCxt); } else { - return convNcharToVarchar(input, output, inputLen, outputLen); + return convNcharToVarchar(input, output, inputLen, outputLen, charsetCxt); } } -static int32_t tltrim(char *input, char *remInput, char *output, int32_t inputType, int32_t remType) { +static int32_t tltrim(char *input, char *remInput, char *output, int32_t inputType, int32_t remType, void* charsetCxt) { int32_t orgLen = varDataLen(input); char *orgStr = varDataVal(input); int32_t remLen = varDataLen(remInput); @@ -661,7 +661,7 @@ static int32_t tltrim(char *input, char *remInput, char *output, int32_t inputTy bool needFree = false; if (inputType != remType) { - SCL_ERR_RET(convBetweenNcharAndVarchar(varDataVal(remInput), &remStr, varDataLen(remInput), &remLen, inputType)); + SCL_ERR_RET(convBetweenNcharAndVarchar(varDataVal(remInput), &remStr, varDataLen(remInput), &remLen, inputType, charsetCxt)); needFree = true; } @@ -683,7 +683,7 @@ static int32_t tltrim(char *input, char *remInput, char *output, int32_t inputTy return TSDB_CODE_SUCCESS; } -static void trtrimspace(char *input, char *output, int32_t type, int32_t charLen) { +static void trtrimspace(char *input, char *output, int32_t type, int32_t charLen, void *charsetCxt) { int32_t numOfSpaces = 0; if (type == TSDB_DATA_TYPE_VARCHAR) { for (int32_t i = charLen - 1; i >= 0; --i) { @@ -712,7 +712,7 @@ static void trtrimspace(char *input, char *output, int32_t type, int32_t charLen varDataSetLen(output, resLen); } -static int32_t trtrim(char *input, char *remInput, char *output, int32_t inputType, int32_t remType) { +static int32_t trtrim(char *input, char *remInput, char *output, int32_t inputType, int32_t remType, void* charsetCxt) { int32_t orgLen = varDataLen(input); char *orgStr = varDataVal(input); int32_t remLen = varDataLen(remInput); @@ -725,7 +725,7 @@ static int32_t trtrim(char *input, char *remInput, char *output, int32_t inputTy bool needFree = false; if (inputType != remType) { - SCL_ERR_RET(convBetweenNcharAndVarchar(varDataVal(remInput), &remStr, varDataLen(remInput), &remLen, inputType)); + SCL_ERR_RET(convBetweenNcharAndVarchar(varDataVal(remInput), &remStr, varDataLen(remInput), &remLen, inputType, charsetCxt)); needFree = true; } @@ -746,7 +746,7 @@ static int32_t trtrim(char *input, char *remInput, char *output, int32_t inputTy return TSDB_CODE_SUCCESS; } -static int32_t tlrtrim(char *input, char *remInput, char *output, int32_t inputType, int32_t remType) { +static int32_t tlrtrim(char *input, char *remInput, char *output, int32_t inputType, int32_t remType, void *charsetCxt) { int32_t orgLen = varDataLen(input); char *orgStr = varDataVal(input); int32_t remLen = varDataLen(remInput); @@ -759,7 +759,7 @@ static int32_t tlrtrim(char *input, char *remInput, char *output, int32_t inputT bool needFree = false; if (inputType != remType) { - SCL_ERR_RET(convBetweenNcharAndVarchar(varDataVal(remInput), &remStr, varDataLen(remInput), &remLen, inputType)); + SCL_ERR_RET(convBetweenNcharAndVarchar(varDataVal(remInput), &remStr, varDataLen(remInput), &remLen, inputType, charsetCxt)); needFree = true; } @@ -810,14 +810,14 @@ static int32_t doLengthFunction(SScalarParam *pInput, int32_t inputNum, SScalarP return TSDB_CODE_SUCCESS; } -static int32_t concatCopyHelper(const char *input, char *output, bool hasNchar, int32_t type, VarDataLenT *dataLen) { +static int32_t concatCopyHelper(const char *input, char *output, bool hasNchar, int32_t type, VarDataLenT *dataLen, void* charsetCxt) { if (hasNchar && type == TSDB_DATA_TYPE_VARCHAR) { TdUcs4 *newBuf = taosMemoryCalloc((varDataLen(input) + 1) * TSDB_NCHAR_SIZE, 1); if (NULL == newBuf) { return terrno; } int32_t len = varDataLen(input); - bool ret = taosMbsToUcs4(varDataVal(input), len, newBuf, (varDataLen(input) + 1) * TSDB_NCHAR_SIZE, &len); + bool ret = taosMbsToUcs4(varDataVal(input), len, newBuf, (varDataLen(input) + 1) * TSDB_NCHAR_SIZE, &len, charsetCxt); if (!ret) { taosMemoryFree(newBuf); return TSDB_CODE_SCALAR_CONVERT_ERROR; @@ -905,7 +905,7 @@ int32_t concatFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOu int32_t rowIdx = (pInput[i].numOfRows == 1) ? 0 : k; input[i] = colDataGetData(pInputData[i], rowIdx); - SCL_ERR_JRET(concatCopyHelper(input[i], output, hasNchar, GET_PARAM_TYPE(&pInput[i]), &dataLen)); + SCL_ERR_JRET(concatCopyHelper(input[i], output, hasNchar, GET_PARAM_TYPE(&pInput[i]), &dataLen, pInput->charsetCxt)); } varDataSetLen(output, dataLen); SCL_ERR_JRET(colDataSetVal(pOutputData, k, outputBuf, false)); @@ -980,12 +980,12 @@ int32_t concatWsFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *p int32_t rowIdx = (pInput[i].numOfRows == 1) ? 0 : k; - SCL_ERR_JRET(concatCopyHelper(colDataGetData(pInputData[i], rowIdx), output, hasNchar, GET_PARAM_TYPE(&pInput[i]), &dataLen)); + SCL_ERR_JRET(concatCopyHelper(colDataGetData(pInputData[i], rowIdx), output, hasNchar, GET_PARAM_TYPE(&pInput[i]), &dataLen, pInput->charsetCxt)); if (i < inputNum - 1) { // insert the separator char *sep = (pInput[0].numOfRows == 1) ? colDataGetData(pInputData[0], 0) : colDataGetData(pInputData[0], k); - SCL_ERR_JRET(concatCopyHelper(sep, output, hasNchar, GET_PARAM_TYPE(&pInput[0]), &dataLen)); + SCL_ERR_JRET(concatCopyHelper(sep, output, hasNchar, GET_PARAM_TYPE(&pInput[0]), &dataLen, pInput->charsetCxt)); } } @@ -1092,7 +1092,7 @@ static int32_t doTrimFunction(SScalarParam *pInput, int32_t inputNum, SScalarPar continue; } SCL_ERR_JRET(trimFn(colDataGetData(pInputData[1], colIdx2), colDataGetData(pInputData[0], colIdx1), - output, GET_PARAM_TYPE(&pInput[1]), GET_PARAM_TYPE(&pInput[0]))); + output, GET_PARAM_TYPE(&pInput[1]), GET_PARAM_TYPE(&pInput[0]), pInput->charsetCxt)); SCL_ERR_JRET(colDataSetVal(pOutputData, i, output, false)); } } else { @@ -1105,7 +1105,7 @@ static int32_t doTrimFunction(SScalarParam *pInput, int32_t inputNum, SScalarPar char *input = colDataGetData(pInputData[0], i); int32_t len = varDataLen(input); int32_t charLen = (type == TSDB_DATA_TYPE_VARCHAR) ? len : len / TSDB_NCHAR_SIZE; - trimSpaceFn(input, output, type, charLen); + trimSpaceFn(input, output, type, charLen, pInput->charsetCxt); SCL_ERR_JRET(colDataSetVal(pOutputData, i, output, false)); } } @@ -1343,7 +1343,7 @@ int32_t charFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutp if (convBuf == NULL) { SCL_ERR_RET(terrno); } - int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(colDataGetData(pInput[j].columnData, colIdx)), varDataLen(colDataGetData(pInput[j].columnData, colIdx)), convBuf); + int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(colDataGetData(pInput[j].columnData, colIdx)), varDataLen(colDataGetData(pInput[j].columnData, colIdx)), convBuf, pInput->charsetCxt); if (len < 0) { taosMemoryFree(convBuf); code = TSDB_CODE_SCALAR_CONVERT_ERROR; @@ -1386,7 +1386,7 @@ int32_t asciiFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOut int32_t inLen = varDataLen(colDataGetData(pInputData, i)); SCL_ERR_RET(convBetweenNcharAndVarchar(varDataVal(colDataGetData(pInputData, i)), &in, varDataLen(colDataGetData(pInputData, i)), &inLen, - TSDB_DATA_TYPE_VARBINARY)); + TSDB_DATA_TYPE_VARBINARY, pInput->charsetCxt)); out[i] = (uint8_t)(in)[0]; taosMemoryFree(in); } else { @@ -1454,7 +1454,9 @@ int32_t positionFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *p int32_t orgLen = varDataLen(colDataGetData(pInputData[1], colIdx2)); bool needFreeSub = false; if (GET_PARAM_TYPE(&pInput[1]) != GET_PARAM_TYPE(&pInput[0])) { - SCL_ERR_RET(convBetweenNcharAndVarchar(varDataVal(colDataGetData(pInputData[0], colIdx1)), &substr, varDataLen(colDataGetData(pInputData[0], colIdx1)), &subLen, GET_PARAM_TYPE(&pInput[1]))); + SCL_ERR_RET(convBetweenNcharAndVarchar(varDataVal(colDataGetData(pInputData[0], colIdx1)), &substr, + varDataLen(colDataGetData(pInputData[0], colIdx1)), &subLen, + GET_PARAM_TYPE(&pInput[1]), pInput->charsetCxt)); needFreeSub = true; } @@ -1562,13 +1564,13 @@ int32_t replaceFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pO if (GET_PARAM_TYPE(&pInput[1]) != GET_PARAM_TYPE(&pInput[0])) { SCL_ERR_JRET(convBetweenNcharAndVarchar(varDataVal(colDataGetData(pInputData[1], colIdx2)), &fromStr, varDataLen(colDataGetData(pInputData[1], colIdx2)), &fromLen, - GET_PARAM_TYPE(&pInput[0]))); + GET_PARAM_TYPE(&pInput[0]), pInput->charsetCxt)); needFreeFrom = true; } if (GET_PARAM_TYPE(&pInput[2]) != GET_PARAM_TYPE(&pInput[0])) { code = convBetweenNcharAndVarchar(varDataVal(colDataGetData(pInputData[2], colIdx3)), &toStr, varDataLen(colDataGetData(pInputData[2], colIdx3)), &toLen, - GET_PARAM_TYPE(&pInput[0])); + GET_PARAM_TYPE(&pInput[0]), pInput->charsetCxt); if (TSDB_CODE_SUCCESS != code) { if (needFreeFrom) { taosMemoryFree(fromStr); @@ -1674,7 +1676,7 @@ int32_t substrIdxFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam * if (GET_PARAM_TYPE(&pInput[0]) != GET_PARAM_TYPE(&pInput[1])) { SCL_ERR_JRET(convBetweenNcharAndVarchar(varDataVal(colDataGetData(pInputData[1], colIdx2)), &delimStr, varDataLen(colDataGetData(pInputData[1], colIdx2)), &delimLen, - GET_PARAM_TYPE(&pInput[0]))); + GET_PARAM_TYPE(&pInput[0]), pInput->charsetCxt)); needFreeDelim = true; } @@ -1853,7 +1855,7 @@ int32_t castFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutp buf[varDataLen(input)] = 0; *(int8_t *)output = taosStr2Int8(buf, NULL, 10); } else if (inputType == TSDB_DATA_TYPE_NCHAR) { - int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(input), varDataLen(input), convBuf); + int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(input), varDataLen(input), convBuf, pInput->charsetCxt); if (len < 0) { code = TSDB_CODE_SCALAR_CONVERT_ERROR; goto _end; @@ -1872,7 +1874,7 @@ int32_t castFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutp buf[varDataLen(input)] = 0; *(int16_t *)output = taosStr2Int16(buf, NULL, 10); } else if (inputType == TSDB_DATA_TYPE_NCHAR) { - int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(input), varDataLen(input), convBuf); + int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(input), varDataLen(input), convBuf, pInput->charsetCxt); if (len < 0) { code = TSDB_CODE_SCALAR_CONVERT_ERROR; goto _end; @@ -1890,7 +1892,7 @@ int32_t castFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutp buf[varDataLen(input)] = 0; *(int32_t *)output = taosStr2Int32(buf, NULL, 10); } else if (inputType == TSDB_DATA_TYPE_NCHAR) { - int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(input), varDataLen(input), convBuf); + int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(input), varDataLen(input), convBuf, pInput->charsetCxt); if (len < 0) { code = TSDB_CODE_SCALAR_CONVERT_ERROR; goto _end; @@ -1909,7 +1911,7 @@ int32_t castFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutp buf[varDataLen(input)] = 0; *(int64_t *)output = taosStr2Int64(buf, NULL, 10); } else if (inputType == TSDB_DATA_TYPE_NCHAR) { - int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(input), varDataLen(input), convBuf); + int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(input), varDataLen(input), convBuf, pInput->charsetCxt); if (len < 0) { code = TSDB_CODE_SCALAR_CONVERT_ERROR; goto _end; @@ -1927,7 +1929,7 @@ int32_t castFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutp buf[varDataLen(input)] = 0; *(uint8_t *)output = taosStr2UInt8(buf, NULL, 10); } else if (inputType == TSDB_DATA_TYPE_NCHAR) { - int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(input), varDataLen(input), convBuf); + int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(input), varDataLen(input), convBuf, pInput->charsetCxt); if (len < 0) { code = TSDB_CODE_SCALAR_CONVERT_ERROR; goto _end; @@ -1945,7 +1947,7 @@ int32_t castFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutp buf[varDataLen(input)] = 0; *(uint16_t *)output = taosStr2UInt16(buf, NULL, 10); } else if (inputType == TSDB_DATA_TYPE_NCHAR) { - int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(input), varDataLen(input), convBuf); + int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(input), varDataLen(input), convBuf, pInput->charsetCxt); if (len < 0) { code = TSDB_CODE_SCALAR_CONVERT_ERROR; goto _end; @@ -1963,7 +1965,7 @@ int32_t castFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutp buf[varDataLen(input)] = 0; *(uint32_t *)output = taosStr2UInt32(buf, NULL, 10); } else if (inputType == TSDB_DATA_TYPE_NCHAR) { - int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(input), varDataLen(input), convBuf); + int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(input), varDataLen(input), convBuf, pInput->charsetCxt); if (len < 0) { code = TSDB_CODE_SCALAR_CONVERT_ERROR; goto _end; @@ -1981,7 +1983,7 @@ int32_t castFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutp buf[varDataLen(input)] = 0; *(uint64_t *)output = taosStr2UInt64(buf, NULL, 10); } else if (inputType == TSDB_DATA_TYPE_NCHAR) { - int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(input), varDataLen(input), convBuf); + int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(input), varDataLen(input), convBuf, pInput->charsetCxt); if (len < 0) { code = TSDB_CODE_SCALAR_CONVERT_ERROR; goto _end; @@ -2000,7 +2002,7 @@ int32_t castFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutp buf[varDataLen(input)] = 0; *(float *)output = taosStr2Float(buf, NULL); } else if (inputType == TSDB_DATA_TYPE_NCHAR) { - int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(input), varDataLen(input), convBuf); + int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(input), varDataLen(input), convBuf, pInput->charsetCxt); if (len < 0) { code = TSDB_CODE_SCALAR_CONVERT_ERROR; goto _end; @@ -2018,7 +2020,7 @@ int32_t castFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutp buf[varDataLen(input)] = 0; *(double *)output = taosStr2Double(buf, NULL); } else if (inputType == TSDB_DATA_TYPE_NCHAR) { - int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(input), varDataLen(input), convBuf); + int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(input), varDataLen(input), convBuf, pInput->charsetCxt); if (len < 0) { code = TSDB_CODE_SCALAR_CONVERT_ERROR; goto _end; @@ -2036,7 +2038,7 @@ int32_t castFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutp buf[varDataLen(input)] = 0; *(bool *)output = taosStr2Int8(buf, NULL, 10); } else if (inputType == TSDB_DATA_TYPE_NCHAR) { - int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(input), varDataLen(input), convBuf); + int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(input), varDataLen(input), convBuf, pInput->charsetCxt); if (len < 0) { code = TSDB_CODE_SCALAR_CONVERT_ERROR; goto _end; @@ -2053,7 +2055,7 @@ int32_t castFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutp if (inputType == TSDB_DATA_TYPE_BINARY || inputType == TSDB_DATA_TYPE_NCHAR) { int64_t timePrec; GET_TYPED_DATA(timePrec, int64_t, GET_PARAM_TYPE(&pInput[1]), pInput[1].columnData->pData); - int32_t ret = convertStringToTimestamp(inputType, input, timePrec, &timeVal, pInput->tz); + int32_t ret = convertStringToTimestamp(inputType, input, timePrec, &timeVal, pInput->tz, pInput->charsetCxt); if (ret != TSDB_CODE_SUCCESS) { *(int64_t *)output = 0; } else { @@ -2076,7 +2078,7 @@ int32_t castFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutp (void)memcpy(varDataVal(output), varDataVal(input), len); varDataSetLen(output, len); } else if (inputType == TSDB_DATA_TYPE_NCHAR) { - int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(input), varDataLen(input), convBuf); + int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(input), varDataLen(input), convBuf, pInput->charsetCxt); if (len < 0) { code = TSDB_CODE_SCALAR_CONVERT_ERROR; goto _end; @@ -2111,7 +2113,7 @@ int32_t castFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutp if (inputType == TSDB_DATA_TYPE_BOOL) { char tmp[8] = {0}; len = tsnprintf(tmp, sizeof(tmp), "%.*s", outputCharLen, *(int8_t *)input ? "true" : "false"); - bool ret = taosMbsToUcs4(tmp, len, (TdUcs4 *)varDataVal(output), outputLen - VARSTR_HEADER_SIZE, &len); + bool ret = taosMbsToUcs4(tmp, len, (TdUcs4 *)varDataVal(output), outputLen - VARSTR_HEADER_SIZE, &len, pInput->charsetCxt); if (!ret) { code = TSDB_CODE_SCALAR_CONVERT_ERROR; goto _end; @@ -2121,7 +2123,7 @@ int32_t castFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutp } else if (inputType == TSDB_DATA_TYPE_BINARY) { len = outputCharLen > varDataLen(input) ? varDataLen(input) : outputCharLen; bool ret = taosMbsToUcs4(input + VARSTR_HEADER_SIZE, len, (TdUcs4 *)varDataVal(output), - outputLen - VARSTR_HEADER_SIZE, &len); + outputLen - VARSTR_HEADER_SIZE, &len, pInput->charsetCxt); if (!ret) { code = TSDB_CODE_SCALAR_CONVERT_ERROR; goto _end; @@ -2135,7 +2137,7 @@ int32_t castFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutp NUM_TO_STRING(inputType, input, bufSize, buf); len = (int32_t)strlen(buf); len = outputCharLen > len ? len : outputCharLen; - bool ret = taosMbsToUcs4(buf, len, (TdUcs4 *)varDataVal(output), outputLen - VARSTR_HEADER_SIZE, &len); + bool ret = taosMbsToUcs4(buf, len, (TdUcs4 *)varDataVal(output), outputLen - VARSTR_HEADER_SIZE, &len, pInput->charsetCxt); if (!ret) { code = TSDB_CODE_SCALAR_CONVERT_ERROR; goto _end; @@ -2283,7 +2285,7 @@ int32_t toUnixtimestampFunction(SScalarParam *pInput, int32_t inputNum, SScalarP char *input = colDataGetData(pInput[0].columnData, i); int64_t timeVal = 0; - int32_t ret = convertStringToTimestamp(type, input, timePrec, &timeVal, pInput->tz); + int32_t ret = convertStringToTimestamp(type, input, timePrec, &timeVal, pInput->tz, pInput->charsetCxt); if (ret != TSDB_CODE_SUCCESS) { colDataSetNULL(pOutput->columnData, i); } else { @@ -2323,7 +2325,7 @@ int32_t toJsonFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOu } (void)memcpy(tmp, varDataVal(input), varDataLen(input)); tmp[varDataLen(input)] = 0; - if (parseJsontoTagData(tmp, pTagVals, &pTag, NULL)) { + if (parseJsontoTagData(tmp, pTagVals, &pTag, NULL, pInput->charsetCxt)) { code = tTagNew(pTagVals, 1, true, &pTag); if (TSDB_CODE_SUCCESS != code) { tTagFree(pTag); @@ -2473,7 +2475,7 @@ int32_t timeTruncateFunction(SScalarParam *pInput, int32_t inputNum, SScalarPara char *input = colDataGetData(pInput[0].columnData, i); if (IS_VAR_DATA_TYPE(type)) { /* datetime format strings */ - int32_t ret = convertStringToTimestamp(type, input, timePrec, &timeVal, pInput->tz); + int32_t ret = convertStringToTimestamp(type, input, timePrec, &timeVal, pInput->tz, pInput->charsetCxt); if (ret != TSDB_CODE_SUCCESS) { colDataSetNULL(pOutput->columnData, i); continue; @@ -2533,7 +2535,7 @@ int32_t timeDiffFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *p int32_t type = GET_PARAM_TYPE(&pInput[k]); if (IS_VAR_DATA_TYPE(type)) { /* datetime format strings */ - int32_t ret = convertStringToTimestamp(type, input[k], TSDB_TIME_PRECISION_NANO, &timeVal[k], pInput->tz); + int32_t ret = convertStringToTimestamp(type, input[k], TSDB_TIME_PRECISION_NANO, &timeVal[k], pInput->tz, pInput->charsetCxt); if (ret != TSDB_CODE_SUCCESS) { hasNull = true; break; @@ -2660,10 +2662,6 @@ int32_t todayFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOut return TSDB_CODE_SUCCESS; } -int32_t timeZoneStrLen() { - return sizeof(VarDataLenT) + strlen(tsTimezoneStr); -} - int32_t timezoneFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput) { char output[TD_TIMEZONE_LEN + VARSTR_HEADER_SIZE] = {0}; // pInput->tz todo tz @@ -2699,7 +2697,7 @@ int32_t weekdayFunctionImpl(SScalarParam *pInput, int32_t inputNum, SScalarParam char *input = colDataGetData(pInput[0].columnData, i); if (IS_VAR_DATA_TYPE(type)) { /* datetime format strings */ - int32_t ret = convertStringToTimestamp(type, input, timePrec, &timeVal, pInput->tz); + int32_t ret = convertStringToTimestamp(type, input, timePrec, &timeVal, pInput->tz, pInput->charsetCxt); if (ret != TSDB_CODE_SUCCESS) { colDataSetNULL(pOutput->columnData, i); continue; @@ -2809,7 +2807,7 @@ int32_t weekFunctionImpl(SScalarParam *pInput, int32_t inputNum, SScalarParam *p char *input = colDataGetData(pInput[0].columnData, i); if (IS_VAR_DATA_TYPE(type)) { /* datetime format strings */ - int32_t ret = convertStringToTimestamp(type, input, prec, &timeVal, pInput->tz); + int32_t ret = convertStringToTimestamp(type, input, prec, &timeVal, pInput->tz, pInput->charsetCxt); if (ret != TSDB_CODE_SUCCESS) { colDataSetNULL(pOutput->columnData, i); continue; diff --git a/source/libs/scalar/src/sclvector.c b/source/libs/scalar/src/sclvector.c index c69501d50b..85bbd40573 100644 --- a/source/libs/scalar/src/sclvector.c +++ b/source/libs/scalar/src/sclvector.c @@ -103,7 +103,7 @@ int32_t convertNcharToDouble(const void *inData, void *outData) { if (NULL == tmp) { SCL_ERR_RET(terrno); } - int len = taosUcs4ToMbs((TdUcs4 *)varDataVal(inData), varDataLen(inData), tmp); + int len = taosUcs4ToMbs((TdUcs4 *)varDataVal(inData), varDataLen(inData), tmp, NULL); if (len < 0) { sclError("castConvert taosUcs4ToMbs error 1"); SCL_ERR_JRET(TSDB_CODE_SCALAR_CONVERT_ERROR); @@ -404,7 +404,7 @@ static FORCE_INLINE int32_t varToNchar(char *buf, SScalarParam *pOut, int32_t ro SCL_ERR_RET(terrno); } int32_t ret = - taosMbsToUcs4(varDataVal(buf), inputLen, (TdUcs4 *)varDataVal(t), outputMaxLen - VARSTR_HEADER_SIZE, &len); + taosMbsToUcs4(varDataVal(buf), inputLen, (TdUcs4 *)varDataVal(t), outputMaxLen - VARSTR_HEADER_SIZE, &len, pOut->charsetCxt); if (!ret) { sclError("failed to convert to NCHAR"); SCL_ERR_JRET(TSDB_CODE_SCALAR_CONVERT_ERROR); @@ -426,7 +426,7 @@ static FORCE_INLINE int32_t ncharToVar(char *buf, SScalarParam *pOut, int32_t ro if (NULL == t) { SCL_ERR_RET(terrno); } - int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(buf), varDataLen(buf), varDataVal(t)); + int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(buf), varDataLen(buf), varDataVal(t), pOut->charsetCxt); if (len < 0) { SCL_ERR_JRET(TSDB_CODE_SCALAR_CONVERT_ERROR); } @@ -557,7 +557,7 @@ int32_t vectorConvertFromVarData(SSclVectorConvCtx *pCtx, int32_t *overflow) { SCL_ERR_JRET(TSDB_CODE_APP_ERROR); } - int len = taosUcs4ToMbs((TdUcs4 *)varDataVal(data), varDataLen(data), tmp); + int len = taosUcs4ToMbs((TdUcs4 *)varDataVal(data), varDataLen(data), tmp, pCtx->pIn->charsetCxt); if (len < 0) { sclError("castConvert taosUcs4ToMbs error 1"); SCL_ERR_JRET(TSDB_CODE_SCALAR_CONVERT_ERROR); @@ -592,7 +592,7 @@ int32_t getVectorDoubleValue_JSON(void *src, int32_t index, double *out) { SCL_RET(TSDB_CODE_SUCCESS); } -int32_t ncharTobinary(void *buf, void **out) { // todo need to remove , if tobinary is nchar +int32_t ncharTobinary(void *buf, void **out, void* charsetCxt) { // todo need to remove , if tobinary is nchar int32_t inputLen = varDataTLen(buf); *out = taosMemoryCalloc(1, inputLen); @@ -601,7 +601,7 @@ int32_t ncharTobinary(void *buf, void **out) { // todo need to remove , if tobi DEFAULT_UNICODE_ENCODEC, tsCharset, (char *)varDataVal(buf)); SCL_ERR_RET(terrno); } - int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(buf), varDataLen(buf), varDataVal(*out)); + int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(buf), varDataLen(buf), varDataVal(*out), charsetCxt); if (len < 0) { sclError("charset:%s to %s. val:%s convert ncharTobinary failed.", DEFAULT_UNICODE_ENCODEC, tsCharset, (char *)varDataVal(buf)); @@ -614,7 +614,7 @@ int32_t ncharTobinary(void *buf, void **out) { // todo need to remove , if tobi int32_t convertJsonValue(__compar_fn_t *fp, int32_t optr, int8_t typeLeft, int8_t typeRight, char **pLeftData, char **pRightData, void *pLeftOut, void *pRightOut, bool *isNull, bool *freeLeft, - bool *freeRight, bool *result) { + bool *freeRight, bool *result, void* charsetCxt) { *result = false; if (optr == OP_TYPE_JSON_CONTAINS) { *result = true; @@ -700,13 +700,13 @@ int32_t convertJsonValue(__compar_fn_t *fp, int32_t optr, int8_t typeLeft, int8_ type == TSDB_DATA_TYPE_GEOMETRY) { if (typeLeft == TSDB_DATA_TYPE_NCHAR) { char *tmpLeft = NULL; - SCL_ERR_RET(ncharTobinary(*pLeftData, (void *)&tmpLeft)); + SCL_ERR_RET(ncharTobinary(*pLeftData, (void *)&tmpLeft, charsetCxt)); *pLeftData = tmpLeft; *freeLeft = true; } if (typeRight == TSDB_DATA_TYPE_NCHAR) { char *tmpRight = NULL; - SCL_ERR_RET(ncharTobinary(*pRightData, (void *)&tmpRight)); + SCL_ERR_RET(ncharTobinary(*pRightData, (void *)&tmpRight, charsetCxt)); *pRightData = tmpRight; *freeRight = true; } @@ -1958,7 +1958,7 @@ int32_t doVectorCompareImpl(SScalarParam *pLeft, SScalarParam *pRight, SScalarPa bool result = false; SCL_ERR_RET(convertJsonValue(&fp, optr, GET_PARAM_TYPE(pLeft), GET_PARAM_TYPE(pRight), &pLeftData, &pRightData, - &leftOut, &rightOut, &isJsonnull, &freeLeft, &freeRight, &result)); + &leftOut, &rightOut, &isJsonnull, &freeLeft, &freeRight, &result, pLeft->charsetCxt)); if (isJsonnull) { sclError("doVectorCompareImpl: invalid json null value"); @@ -2043,8 +2043,11 @@ int32_t vectorCompareImpl(SScalarParam *pLeft, SScalarParam *pRight, SScalarPara SScalarParam *param2 = NULL; int32_t code = TSDB_CODE_SUCCESS; pRight->tz = pLeft->tz; + pRight->charsetCxt = pLeft->charsetCxt; pLeftOut.tz = pLeft->tz; + pLeftOut.charsetCxt = pLeft->charsetCxt; pRightOut.tz = pRight->tz; + pRightOut.charsetCxt = pRight->charsetCxt; if (noConvertBeforeCompare(GET_PARAM_TYPE(pLeft), GET_PARAM_TYPE(pRight), optr)) { param1 = pLeft; param2 = pRight; @@ -2130,6 +2133,7 @@ int32_t vectorIsNull(SScalarParam *pLeft, SScalarParam *pRight, SScalarParam *pO int32_t vectorNotNull(SScalarParam *pLeft, SScalarParam *pRight, SScalarParam *pOut, int32_t _ord) { if (pRight != NULL) { pRight->tz = pLeft->tz; + pRight->charsetCxt = pLeft->charsetCxt; } for (int32_t i = 0; i < pLeft->numOfRows; ++i) { int8_t v = IS_HELPER_NULL(pLeft->columnData, i) ? 0 : 1; @@ -2146,6 +2150,8 @@ int32_t vectorNotNull(SScalarParam *pLeft, SScalarParam *pRight, SScalarParam *p int32_t vectorIsTrue(SScalarParam *pLeft, SScalarParam *pRight, SScalarParam *pOut, int32_t _ord) { if (pRight != NULL) { pRight->tz = pLeft->tz; + pRight->charsetCxt = pLeft->charsetCxt; + } SCL_ERR_RET(vectorConvertSingleColImpl(pLeft, pOut, NULL, -1, -1)); for (int32_t i = 0; i < pOut->numOfRows; ++i) { diff --git a/source/libs/scalar/test/scalar/scalarTests.cpp b/source/libs/scalar/test/scalar/scalarTests.cpp index 4cab644582..6a5188f208 100644 --- a/source/libs/scalar/test/scalar/scalarTests.cpp +++ b/source/libs/scalar/test/scalar/scalarTests.cpp @@ -1450,7 +1450,7 @@ TEST(columnTest, json_column_arith_op) { SArray *tags = taosArrayInit(1, sizeof(STagVal)); ASSERT_NE(tags, nullptr); STag *row = NULL; - int32_t code = parseJsontoTagData(rightv, tags, &row, NULL); + int32_t code = parseJsontoTagData(rightv, tags, &row, NULL, NULL); ASSERT_EQ(code, TSDB_CODE_SUCCESS); const int32_t len = 8; @@ -1606,7 +1606,7 @@ void *prepareNchar(char *rightData) { int32_t inputLen = strlen(rightData); char *t = (char *)taosMemoryCalloc(1, (inputLen + 1) * TSDB_NCHAR_SIZE + VARSTR_HEADER_SIZE); - taosMbsToUcs4(rightData, inputLen, (TdUcs4 *)varDataVal(t), inputLen * TSDB_NCHAR_SIZE, &len); + taosMbsToUcs4(rightData, inputLen, (TdUcs4 *)varDataVal(t), inputLen * TSDB_NCHAR_SIZE, &len, NULL); varDataSetLen(t, len); return t; } @@ -1623,7 +1623,7 @@ TEST(columnTest, json_column_logic_op) { SArray *tags = taosArrayInit(1, sizeof(STagVal)); ASSERT_NE(tags, nullptr); STag *row = NULL; - code = parseJsontoTagData(rightv, tags, &row, NULL); + code = parseJsontoTagData(rightv, tags, &row, NULL, NULL); ASSERT_EQ(code, TSDB_CODE_SUCCESS); const int32_t len0 = 6; diff --git a/source/os/src/osEnv.c b/source/os/src/osEnv.c index eb13560929..ac303092b3 100644 --- a/source/os/src/osEnv.c +++ b/source/os/src/osEnv.c @@ -25,9 +25,9 @@ SDiskSpace tsLogSpace = {0}; SDiskSpace tsTempSpace = {0}; char tsOsName[16] = {0}; char tsTimezoneStr[TD_TIMEZONE_LEN] = {0}; -enum TdTimezone tsTimezone = TdZeroZone; char tsLocale[TD_LOCALE_LEN] = {0}; char tsCharset[TD_CHARSET_LEN] = {0}; +void *tsCharsetCxt = NULL; bool tsEnableCoreFile = 1; int64_t tsPageSizeKB = 0; int64_t tsOpenMax = 0; @@ -48,6 +48,7 @@ int32_t osDefaultInit() { taosSeedRand(taosSafeRand()); taosGetSystemLocale(tsLocale, tsCharset); + (void)taosGetSystemTimezone(tsTimezoneStr); taosGetSystemInfo(); @@ -117,11 +118,6 @@ bool osTempSpaceSufficient() { return tsTempSpace.size.avail > tsTempSpace.reser int32_t osSetTimezone(const char *tz) { return taosSetGlobalTimezone(tz); } -void osSetSystemLocale(const char *inLocale, const char *inCharSet) { - if (inLocale) (void)memcpy(tsLocale, inLocale, strlen(inLocale) + 1); - if (inCharSet) (void)memcpy(tsCharset, inCharSet, strlen(inCharSet) + 1); -} - void osSetProcPath(int32_t argc, char **argv) { if (argv == NULL || argc < 1) { return; // no command line arguments diff --git a/source/os/src/osLocale.c b/source/os/src/osLocale.c index 21f781c7e4..cb0a2646fd 100644 --- a/source/os/src/osLocale.c +++ b/source/os/src/osLocale.c @@ -78,19 +78,17 @@ char *taosCharsetReplace(char *charsetstr) { * * In case that the setLocale failed to be executed, the right charset needs to be set. */ -int32_t taosSetSystemLocale(const char *inLocale, const char *inCharSet) { - OS_PARAM_CHECK(inLocale); - OS_PARAM_CHECK(inCharSet); - if (!taosValidateEncodec(inCharSet)) { - return terrno; - } +int32_t taosSetSystemLocale(const char *inLocale) { char *locale = setlocale(LC_CTYPE, inLocale); if (NULL == locale) { terrno = TSDB_CODE_INVALID_PARA; + uError("failed to set locale:%s", inLocale); return terrno; } + (void)memcpy(tsLocale, inLocale, strlen(inLocale) + 1); + return 0; } @@ -102,59 +100,6 @@ void taosGetSystemLocale(char *outLocale, char *outCharset) { tstrncpy(outLocale, locale, TD_LOCALE_LEN); } tstrncpy(outCharset, "UTF-8", TD_CHARSET_LEN); - -#elif defined(_TD_DARWIN_64) - /* - * originally from src/os/src/detail/osSysinfo.c - * POSIX format locale string: - * (Language Strings)_(Country/Region Strings).(code_page) - * - * example: en_US.UTF-8, zh_CN.GB18030, zh_CN.UTF-8, - * - * if user does not specify the locale in taos.cfg the program use default LC_CTYPE as system locale. - * - * In case of some CentOS systems, their default locale is "en_US.utf8", which is not valid code_page - * for libiconv that is employed to convert string in this system. This program will automatically use - * UTF-8 instead as the charset. - * - * In case of windows client, the locale string is not valid POSIX format, user needs to set the - * correct code_page for libiconv. Usually, the code_page of windows system with simple chinese is - * CP936, CP437 for English charset. - * - */ - - char sep = '.'; - char *locale = NULL; - - locale = setlocale(LC_CTYPE, ""); - if (locale == NULL) { - // printf("can't get locale from system, set it to en_US.UTF-8 since error:%d:%s", errno, strerror(errno)); - tstrncpy(outLocale, "en_US.UTF-8", TD_LOCALE_LEN); - } else { - tstrncpy(outLocale, locale, TD_LOCALE_LEN); - // printf("locale not configured, set to system default:%s", outLocale); - } - - /* if user does not specify the charset, extract it from locale */ - char *str = strrchr(outLocale, sep); - if (str != NULL) { - str++; - - char *revisedCharset = taosCharsetReplace(str); - - if (NULL == revisedCharset) { - tstrncpy(outCharset, "UTF-8", TD_CHARSET_LEN); - } else { - tstrncpy(outCharset, revisedCharset, TD_CHARSET_LEN); - - taosMemoryFree(revisedCharset); - } - // printf("charset not configured, set to system default:%s", outCharset); - } else { - tstrncpy(outCharset, "UTF-8", TD_CHARSET_LEN); - // printf("can't get locale and charset from system, set it to UTF-8"); - } - #else /* * POSIX format locale string: diff --git a/source/os/src/osString.c b/source/os/src/osString.c index 0ee4f1c496..4506248eec 100644 --- a/source/os/src/osString.c +++ b/source/os/src/osString.c @@ -229,80 +229,25 @@ int32_t tasoUcs4Copy(TdUcs4 *target_ucs4, TdUcs4 *source_ucs4, int32_t len_ucs4) return TSDB_CODE_SUCCESS; } -typedef struct { - iconv_t conv; - int8_t inUse; -} SConv; - -// 0: Mbs --> Ucs4 -// 1: Ucs4--> Mbs -SConv *gConv[2] = {NULL, NULL}; -int32_t convUsed[2] = {0, 0}; -int32_t gConvMaxNum[2] = {0, 0}; - -int32_t taosConvInit(void) { - int8_t M2C = 0; - gConvMaxNum[M2C] = 512; - gConvMaxNum[1 - M2C] = 512; - - gConv[M2C] = taosMemoryCalloc(gConvMaxNum[M2C], sizeof(SConv)); - if (gConv[M2C] == NULL) { - return terrno; - } - - gConv[1 - M2C] = taosMemoryCalloc(gConvMaxNum[1 - M2C], sizeof(SConv)); - if (gConv[1 - M2C] == NULL) { - taosMemoryFree(gConv[M2C]); - return terrno; - } - - for (int32_t i = 0; i < gConvMaxNum[M2C]; ++i) { - gConv[M2C][i].conv = iconv_open(DEFAULT_UNICODE_ENCODEC, tsCharset); - if ((iconv_t)-1 == gConv[M2C][i].conv) { - terrno = TAOS_SYSTEM_ERROR(errno); - return terrno; - } - } - for (int32_t i = 0; i < gConvMaxNum[1 - M2C]; ++i) { - gConv[1 - M2C][i].conv = iconv_open(tsCharset, DEFAULT_UNICODE_ENCODEC); - if ((iconv_t)-1 == gConv[1 - M2C][i].conv) { - terrno = TAOS_SYSTEM_ERROR(errno); - return terrno; - } - } - - return 0; -} - -void taosConvDestroy() { - int8_t M2C = 0; - for (int32_t i = 0; i < gConvMaxNum[M2C]; ++i) { - (void)iconv_close(gConv[M2C][i].conv); - } - for (int32_t i = 0; i < gConvMaxNum[1 - M2C]; ++i) { - (void)iconv_close(gConv[1 - M2C][i].conv); - } - taosMemoryFreeClear(gConv[M2C]); - taosMemoryFreeClear(gConv[1 - M2C]); - gConvMaxNum[M2C] = -1; - gConvMaxNum[1 - M2C] = -1; -} - -iconv_t taosAcquireConv(int32_t *idx, ConvType type) { +iconv_t taosAcquireConv(int32_t *idx, ConvType type, void* charsetCxt) { if(idx == NULL) { terrno = TSDB_CODE_INVALID_PARA; return (iconv_t)-1; } - if (gConvMaxNum[type] <= 0) { + if (charsetCxt == NULL){ + charsetCxt = tsCharsetCxt; + } + SConvInfo *info = (SConvInfo *)charsetCxt; + if (info->gConvMaxNum[type] <= 0) { *idx = -1; if (type == M2C) { - iconv_t c = iconv_open(DEFAULT_UNICODE_ENCODEC, tsCharset); + iconv_t c = iconv_open(DEFAULT_UNICODE_ENCODEC, info->charset); if ((iconv_t)-1 == c) { terrno = TAOS_SYSTEM_ERROR(errno); } return c; } else { - iconv_t c = iconv_open(tsCharset, DEFAULT_UNICODE_ENCODEC); + iconv_t c = iconv_open(info->charset, DEFAULT_UNICODE_ENCODEC); if ((iconv_t)-1 == c) { terrno = TAOS_SYSTEM_ERROR(errno); } @@ -311,9 +256,9 @@ iconv_t taosAcquireConv(int32_t *idx, ConvType type) { } while (true) { - int32_t used = atomic_add_fetch_32(&convUsed[type], 1); - if (used > gConvMaxNum[type]) { - used = atomic_sub_fetch_32(&convUsed[type], 1); + int32_t used = atomic_add_fetch_32(&info->convUsed[type], 1); + if (used > info->gConvMaxNum[type]) { + (void)atomic_sub_fetch_32(&info->convUsed[type], 1); (void)sched_yield(); continue; } @@ -321,38 +266,43 @@ iconv_t taosAcquireConv(int32_t *idx, ConvType type) { break; } - int32_t startId = taosGetSelfPthreadId() % gConvMaxNum[type]; + int32_t startId = taosGetSelfPthreadId() % info->gConvMaxNum[type]; while (true) { - if (gConv[type][startId].inUse) { - startId = (startId + 1) % gConvMaxNum[type]; + if (info->gConv[type][startId].inUse) { + startId = (startId + 1) % info->gConvMaxNum[type]; continue; } - int8_t old = atomic_val_compare_exchange_8(&gConv[type][startId].inUse, 0, 1); + int8_t old = atomic_val_compare_exchange_8(&info->gConv[type][startId].inUse, 0, 1); if (0 == old) { break; } } *idx = startId; - if ((iconv_t)0 == gConv[type][startId].conv) { + if ((iconv_t)0 == info->gConv[type][startId].conv) { return (iconv_t)-1; } else { - return gConv[type][startId].conv; + return info->gConv[type][startId].conv; } } -void taosReleaseConv(int32_t idx, iconv_t conv, ConvType type) { +void taosReleaseConv(int32_t idx, iconv_t conv, ConvType type, void* charsetCxt) { if (idx < 0) { (void)iconv_close(conv); return; } - atomic_store_8(&gConv[type][idx].inUse, 0); - (void)atomic_sub_fetch_32(&convUsed[type], 1); + if (charsetCxt == NULL){ + charsetCxt = tsCharsetCxt; + } + SConvInfo *info = (SConvInfo *)charsetCxt; + + atomic_store_8(&info->gConv[type][idx].inUse, 0); + (void)atomic_sub_fetch_32(&info->convUsed[type], 1); } -bool taosMbsToUcs4(const char *mbs, size_t mbsLength, TdUcs4 *ucs4, int32_t ucs4_max_len, int32_t *len) { +bool taosMbsToUcs4(const char *mbs, size_t mbsLength, TdUcs4 *ucs4, int32_t ucs4_max_len, int32_t *len, void* charsetCxt) { if (ucs4_max_len == 0) { return true; } @@ -368,20 +318,20 @@ bool taosMbsToUcs4(const char *mbs, size_t mbsLength, TdUcs4 *ucs4, int32_t ucs4 (void)memset(ucs4, 0, ucs4_max_len); int32_t idx = -1; - iconv_t conv = taosAcquireConv(&idx, M2C); + iconv_t conv = taosAcquireConv(&idx, M2C, charsetCxt); if ((iconv_t)-1 == conv) { return false; } - + size_t ucs4_input_len = mbsLength; size_t outLeft = ucs4_max_len; if (iconv(conv, (char **)&mbs, &ucs4_input_len, (char **)&ucs4, &outLeft) == -1) { terrno = TAOS_SYSTEM_ERROR(errno); - taosReleaseConv(idx, conv, M2C); + taosReleaseConv(idx, conv, M2C, charsetCxt); return false; } - taosReleaseConv(idx, conv, M2C); + taosReleaseConv(idx, conv, M2C, charsetCxt); if (len != NULL) { *len = (int32_t)(ucs4_max_len - outLeft); if (*len < 0) { @@ -397,7 +347,7 @@ bool taosMbsToUcs4(const char *mbs, size_t mbsLength, TdUcs4 *ucs4, int32_t ucs4 // if success, return the number of bytes written to mbs ( >= 0) // otherwise return error code ( < 0) -int32_t taosUcs4ToMbs(TdUcs4 *ucs4, int32_t ucs4_max_len, char *mbs) { +int32_t taosUcs4ToMbs(TdUcs4 *ucs4, int32_t ucs4_max_len, char *mbs, void* charsetCxt) { if (ucs4_max_len == 0) { return 0; } @@ -413,22 +363,22 @@ int32_t taosUcs4ToMbs(TdUcs4 *ucs4, int32_t ucs4_max_len, char *mbs) { int32_t idx = -1; int32_t code = 0; - iconv_t conv = taosAcquireConv(&idx, C2M); + iconv_t conv = taosAcquireConv(&idx, C2M, charsetCxt); if ((iconv_t)-1 == conv) { return terrno; } - + size_t ucs4_input_len = ucs4_max_len; size_t outLen = ucs4_max_len; if (iconv(conv, (char **)&ucs4, &ucs4_input_len, &mbs, &outLen) == -1) { code = TAOS_SYSTEM_ERROR(errno); - taosReleaseConv(idx, conv, C2M); - terrno = code; + taosReleaseConv(idx, conv, C2M, charsetCxt); + terrno = code; return code; } - - taosReleaseConv(idx, conv, C2M); - + + taosReleaseConv(idx, conv, C2M, charsetCxt); + return (int32_t)(ucs4_max_len - outLen); #endif } diff --git a/source/os/src/osTimezone.c b/source/os/src/osTimezone.c index 1f2210fbe1..183b319dcb 100644 --- a/source/os/src/osTimezone.c +++ b/source/os/src/osTimezone.c @@ -828,6 +828,16 @@ int32_t taosSetGlobalTimezone(const char *tz) { } +int32_t taosGetLocalTimezoneOffset() { + time_t tx1 = taosGetTimestampSec(); + struct tm tm1; + if (taosLocalTime(&tx1, &tm1, NULL, 0, NULL) == NULL) { + uError("%s failed to get local time: code:%d", __FUNCTION__, errno); + return TSDB_CODE_TIME_ERROR; + } + return (int32_t)(tm1.tm_gmtoff); +} + int32_t taosFormatTimezoneStr(time_t t, const char* tz, timezone_t sp, char *outTimezoneStr){ struct tm tm1; if (taosLocalTime(&t, &tm1, NULL, 0, sp) == NULL) { diff --git a/source/util/src/tcompare.c b/source/util/src/tcompare.c index b1f4ed0ed3..e3a526b2b4 100644 --- a/source/util/src/tcompare.c +++ b/source/util/src/tcompare.c @@ -1508,7 +1508,7 @@ int32_t comparewcsRegexMatch(const void *pString, const void *pPattern) { return 1; // terrno has been set } - int convertLen = taosUcs4ToMbs((TdUcs4 *)varDataVal(pPattern), len, pattern); + int convertLen = taosUcs4ToMbs((TdUcs4 *)varDataVal(pPattern), len, pattern, NULL); if (convertLen < 0) { taosMemoryFree(pattern); return 1; // terrno has been set @@ -1523,7 +1523,7 @@ int32_t comparewcsRegexMatch(const void *pString, const void *pPattern) { return 1; // terrno has been set } - convertLen = taosUcs4ToMbs((TdUcs4 *)varDataVal(pString), len, str); + convertLen = taosUcs4ToMbs((TdUcs4 *)varDataVal(pString), len, str, NULL); if (convertLen < 0) { taosMemoryFree(str); taosMemoryFree(pattern); diff --git a/source/util/src/tconfig.c b/source/util/src/tconfig.c index 126addc29f..57895d0cca 100644 --- a/source/util/src/tconfig.c +++ b/source/util/src/tconfig.c @@ -24,6 +24,7 @@ #include "tlog.h" #include "tunit.h" #include "tutil.h" +#include "tconv.h" #define CFG_NAME_PRINT_LEN 32 #define CFG_SRC_PRINT_LEN 12 @@ -254,10 +255,10 @@ static int32_t cfgSetTimezone(SConfigItem *pItem, const char *value, ECfgSrcType uError("invalid timezone:%s", value); TAOS_RETURN(TSDB_CODE_INVALID_TIMEZONE); } - if (strlen(value) == 0) { + if (value == NULL || strlen(value) == 0) { uError("cfg:%s, type:%s src:%s, value:%s, skip to set timezone", pItem->name, cfgDtypeStr(pItem->dtype), cfgStypeStr(stype), value); - TAOS_RETURN(TSDB_CODE_SUCCESS); + TAOS_RETURN(TSDB_CODE_INVALID_CFG); } TAOS_CHECK_RETURN(osSetTimezone(value)); @@ -266,6 +267,49 @@ static int32_t cfgSetTimezone(SConfigItem *pItem, const char *value, ECfgSrcType TAOS_RETURN(TSDB_CODE_SUCCESS); } +static int32_t cfgSetCharset(SConfigItem *pItem, const char *value, ECfgSrcType stype) { + if (stype == CFG_STYPE_ALTER_SERVER_CMD || stype == CFG_STYPE_ALTER_CLIENT_CMD){ + uError("failed to config charset, not support"); + TAOS_RETURN(TSDB_CODE_INVALID_CFG); + } + + if (value == NULL || strlen(value) == 0) { + uError("cfg:%s, type:%s src:%s, value:%s, skip to set charset", pItem->name, cfgDtypeStr(pItem->dtype), + cfgStypeStr(stype), value); + TAOS_RETURN(TSDB_CODE_INVALID_CFG); + } + + if (!taosValidateEncodec(value)) { + uError("invalid charset:%s", value); + TAOS_RETURN(terrno); + } + + if ((tsCharsetCxt = taosConvInit(value)) == NULL) { + TAOS_RETURN(terrno); + } + (void)memcpy(tsCharset, value, strlen(value) + 1); + TAOS_CHECK_RETURN(doSetConf(pItem, value, stype)); + + TAOS_RETURN(TSDB_CODE_SUCCESS); +} + +static int32_t cfgSetLocale(SConfigItem *pItem, const char *value, ECfgSrcType stype) { + if (stype == CFG_STYPE_ALTER_SERVER_CMD || (pItem->dynScope & CFG_DYN_CLIENT) == 0){ + uError("failed to config locale, not support"); + TAOS_RETURN(TSDB_CODE_INVALID_CFG); + } + + if (value == NULL || strlen(value) == 0 || taosSetSystemLocale(value) != 0) { + uError("cfg:%s, type:%s src:%s, value:%s, skip to set locale", pItem->name, cfgDtypeStr(pItem->dtype), + cfgStypeStr(stype), value); + TAOS_RETURN(TSDB_CODE_INVALID_CFG); + } + + TAOS_CHECK_RETURN(doSetConf(pItem, value, stype)); + + TAOS_RETURN(TSDB_CODE_SUCCESS); +} + static int32_t cfgSetTfsItem(SConfig *pCfg, const char *name, const char *value, const char *level, const char *primary, const char *disable, ECfgSrcType stype) { (void)taosThreadMutexLock(&pCfg->lock); @@ -378,11 +422,11 @@ int32_t cfgSetItem(SConfig *pCfg, const char *name, const char *value, ECfgSrcTy break; } case CFG_DTYPE_CHARSET: { - code = doSetConf(pItem, value, stype); + code = cfgSetCharset(pItem, value, stype); break; } case CFG_DTYPE_LOCALE: { - code = doSetConf(pItem, value, stype); + code = cfgSetLocale(pItem, value, stype); break; } case CFG_DTYPE_NONE: diff --git a/source/util/src/tconv.c b/source/util/src/tconv.c new file mode 100644 index 0000000000..d9932489f8 --- /dev/null +++ b/source/util/src/tconv.c @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2019 TAOS Data, Inc. + * + * This program is free software: you can use, redistribute, and/or modify + * it under the terms of the GNU Affero General Public License, version 3 + * or later ("AGPL"), as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#define ALLOW_FORBID_FUNC + +#include "tconv.h" +#include "thash.h" +#include "osString.h" + +#define CONV_MAX_NUM 32 +SHashObj *gConvInfo = NULL; + +// M2C: Mbs --> Ucs4 +// C2M: Ucs4--> Mbs + +static void taosConvDestroyInner(void *arg) { + SConvInfo *info = (SConvInfo *)arg; + if (info == NULL) { + return; + } + for (int32_t i = 0; i < info->gConvMaxNum[M2C]; ++i) { + (void)iconv_close(info->gConv[M2C][i].conv); + } + for (int32_t i = 0; i < info->gConvMaxNum[C2M]; ++i) { + (void)iconv_close(info->gConv[C2M][i].conv); + } + taosMemoryFreeClear(info->gConv[M2C]); + taosMemoryFreeClear(info->gConv[C2M]); + + info->gConvMaxNum[M2C] = -1; + info->gConvMaxNum[C2M] = -1; +} + +void* taosConvInit(const char* charset) { + if (charset == NULL){ + terrno = TSDB_CODE_INVALID_PARA; + return NULL; + } + + void* conv = NULL; + static int32_t lock_c = 0; + + for (int i = 1; atomic_val_compare_exchange_32(&lock_c, 0, 1) != 0; ++i) { + if (i % 1000 == 0) { + uInfo("haven't acquire lock after spin %d times.", i); + (void)sched_yield(); + } + } + + if (gConvInfo == NULL){ + gConvInfo = taosHashInit(0, MurmurHash3_32, false, HASH_ENTRY_LOCK); + if (gConvInfo == NULL) { + atomic_store_32(&lock_c, 0); + goto END; + } + taosHashSetFreeFp(gConvInfo, taosConvDestroyInner); + } + + conv = taosHashGet(gConvInfo, charset, strlen(charset)); + if (conv != NULL){ + goto END; + } + + SConvInfo info = {0}; + info.gConvMaxNum[M2C] = CONV_MAX_NUM; + info.gConvMaxNum[C2M] = CONV_MAX_NUM; + tstrncpy(info.charset, charset, sizeof(info.charset)); + + info.gConv[M2C] = taosMemoryCalloc(info.gConvMaxNum[M2C], sizeof(SConv)); + if (info.gConv[M2C] == NULL) { + goto FAILED; + } + + info.gConv[C2M] = taosMemoryCalloc(info.gConvMaxNum[C2M], sizeof(SConv)); + if (info.gConv[C2M] == NULL) { + goto FAILED; + } + + for (int32_t i = 0; i < info.gConvMaxNum[M2C]; ++i) { + info.gConv[M2C][i].conv = iconv_open(DEFAULT_UNICODE_ENCODEC, charset); + if ((iconv_t)-1 == info.gConv[M2C][i].conv) { + terrno = TAOS_SYSTEM_ERROR(errno); + goto FAILED; + } + } + for (int32_t i = 0; i < info.gConvMaxNum[C2M]; ++i) { + info.gConv[C2M][i].conv = iconv_open(charset, DEFAULT_UNICODE_ENCODEC); + if ((iconv_t)-1 == info.gConv[C2M][i].conv) { + terrno = TAOS_SYSTEM_ERROR(errno); + goto FAILED; + } + } + + int32_t code = taosHashPut(gConvInfo, charset, strlen(charset), &info, sizeof(info)); + if (code != 0){ + goto FAILED; + } + conv = taosHashGet(gConvInfo, charset, strlen(charset)); + goto END; + +FAILED: + taosConvDestroyInner(&info); + +END: + atomic_store_32(&lock_c, 0); + return conv; +} + +void taosConvDestroy() { + taosHashCleanup(gConvInfo); +} From 00dc3cd8c839e70e178f677adea990b01e8cd06e Mon Sep 17 00:00:00 2001 From: yihaoDeng Date: Wed, 27 Nov 2024 21:45:38 +0800 Subject: [PATCH 08/45] feat:[TD-32642] add charset for connection support --- include/os/osString.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/os/osString.h b/include/os/osString.h index 810c59131e..1fe83b7e3b 100644 --- a/include/os/osString.h +++ b/include/os/osString.h @@ -22,7 +22,7 @@ extern "C" { typedef wchar_t TdWchar; typedef int32_t TdUcs4; -#if !defined(DISALLOW_NCHAR_WITHOUT_ICONV) && defined(DARWIN) +#if !defined(DISALLOW_NCHAR_WITHOUT_ICONV) #include "iconv.h" #else typedef void *iconv_t; From 2c3c01cfff5216463d09d76240304c611f06e3d2 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Wed, 27 Nov 2024 22:57:48 +0800 Subject: [PATCH 09/45] feat:[TD-32642] add charset for connection support --- contrib/CMakeLists.txt | 2 +- include/os/osString.h | 2 +- source/client/test/CMakeLists.txt | 21 +- source/client/test/clientTests.cpp | 525 -------------------------- source/client/test/timezoneTest.cpp | 563 ++++++++++++++++++++++++++++ source/os/src/timezone/localtime.c | 2 +- source/os/src/timezone/private.h | 2 +- 7 files changed, 586 insertions(+), 531 deletions(-) create mode 100644 source/client/test/timezoneTest.cpp diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index c368a92429..6253d258a8 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -654,7 +654,7 @@ if(${TD_LINUX} AND ${BUILD_WITH_S3}) endif() execute_process( - COMMAND make TZDIR=${TZ_OUTPUT_PATH}/ clean all posix_only + COMMAND make TZDIR=${TZ_OUTPUT_PATH}/ clean zic posix_only WORKING_DIRECTORY "${TD_CONTRIB_DIR}/tz" ) diff --git a/include/os/osString.h b/include/os/osString.h index 810c59131e..1fe83b7e3b 100644 --- a/include/os/osString.h +++ b/include/os/osString.h @@ -22,7 +22,7 @@ extern "C" { typedef wchar_t TdWchar; typedef int32_t TdUcs4; -#if !defined(DISALLOW_NCHAR_WITHOUT_ICONV) && defined(DARWIN) +#if !defined(DISALLOW_NCHAR_WITHOUT_ICONV) #include "iconv.h" #else typedef void *iconv_t; diff --git a/source/client/test/CMakeLists.txt b/source/client/test/CMakeLists.txt index 8f0bc81d6b..f869bedef6 100644 --- a/source/client/test/CMakeLists.txt +++ b/source/client/test/CMakeLists.txt @@ -11,6 +11,12 @@ TARGET_LINK_LIBRARIES( os util common transport parser catalog scheduler gtest ${TAOS_LIB_STATIC} qcom executor function ) +ADD_EXECUTABLE(timezoneTest timezoneTest.cpp) +TARGET_LINK_LIBRARIES( + timezoneTest + os util common transport parser catalog scheduler gtest ${TAOS_LIB_STATIC} qcom executor function +) + ADD_EXECUTABLE(tmqTest tmqTest.cpp) TARGET_LINK_LIBRARIES( tmqTest @@ -41,12 +47,18 @@ TARGET_INCLUDE_DIRECTORIES( PRIVATE "${TD_SOURCE_DIR}/source/client/inc" ) -#IF(${TD_LINUX}) +TARGET_INCLUDE_DIRECTORIES( + timezoneTest + PUBLIC "${TD_SOURCE_DIR}/include/client/" + PRIVATE "${TD_SOURCE_DIR}/source/client/inc" +) + +IF(${TD_LINUX}) add_test( NAME clientTest COMMAND clientTest ) -#ENDIF () +ENDIF () TARGET_INCLUDE_DIRECTORIES( tmqTest @@ -80,3 +92,8 @@ add_test( NAME userOperTest COMMAND userOperTest ) + +add_test( + NAME timezoneTest + COMMAND timezoneTest +) diff --git a/source/client/test/clientTests.cpp b/source/client/test/clientTests.cpp index 1e4faa3eae..f4317f07f7 100644 --- a/source/client/test/clientTests.cpp +++ b/source/client/test/clientTests.cpp @@ -1489,529 +1489,4 @@ TEST(clientCase, sub_tb_mt_test) { } } -TAOS* getConnWithGlobalOption(const char *tz){ - int code = taos_options(TSDB_OPTION_TIMEZONE, tz); - ASSERT(code == 0); - TAOS* pConn = taos_connect("localhost", "root", "taosdata", NULL, 0); - ASSERT(pConn != nullptr); - return pConn; -} - -TAOS* getConnWithOption(const char *tz){ - TAOS* pConn = taos_connect("localhost", "root", "taosdata", NULL, 0); - ASSERT(pConn != nullptr); - if (tz != NULL){ - int code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_TIMEZONE, tz); - ASSERT(code == 0); - } - return pConn; -} - -void execQuery(TAOS* pConn, const char *sql){ - TAOS_RES* pRes = taos_query(pConn, sql); - ASSERT(taos_errno(pRes) == TSDB_CODE_SUCCESS); - taos_free_result(pRes); -} - -void execQueryFail(TAOS* pConn, const char *sql){ - TAOS_RES* pRes = taos_query(pConn, sql); - ASSERT(taos_errno(pRes) != TSDB_CODE_SUCCESS); - taos_free_result(pRes); -} - -void checkRows(TAOS* pConn, const char *sql, int32_t expectedRows){ - TAOS_RES* pRes = taos_query(pConn, sql); - ASSERT(taos_errno(pRes) == TSDB_CODE_SUCCESS); - TAOS_ROW pRow = NULL; - int rows = 0; - while ((pRow = taos_fetch_row(pRes)) != NULL) { - rows++; - } - ASSERT(rows == expectedRows); - taos_free_result(pRes); -} - -void check_timezone(TAOS* pConn, const char *sql, const char* tz){ - TAOS_RES *pRes = taos_query(pConn, sql); - ASSERT(taos_errno(pRes) == 0); - TAOS_ROW row = NULL; - while ((row = taos_fetch_row(pRes)) != NULL) { - if (strcmp((const char*)row[0], "timezone") == 0){ - ASSERT(strstr((const char*)row[1], tz) != NULL); - } - } - taos_free_result(pRes); -} - -void check_sql_result_partial(TAOS* pConn, const char *sql, const char* result){ - TAOS_RES *pRes = taos_query(pConn, sql); - ASSERT(taos_errno(pRes) == 0); - TAOS_ROW row = NULL; - while ((row = taos_fetch_row(pRes)) != NULL) { - ASSERT(strstr((const char*)row[0], result) != NULL); - } - taos_free_result(pRes); -} - -int64_t get_sql_result(TAOS* pConn, const char *sql){ - int64_t ts = 0; - TAOS_RES *pRes = taos_query(pConn, sql); - ASSERT(taos_errno(pRes) == 0); - TAOS_ROW row = NULL; - while ((row = taos_fetch_row(pRes)) != NULL) { - ts = *(int64_t*)row[0]; - } - taos_free_result(pRes); - return ts; -} - -void check_sql_result(TAOS* pConn, const char *sql, const char* result){ - TAOS_RES *pRes = taos_query(pConn, sql); - ASSERT(taos_errno(pRes) == 0); - TAOS_ROW row = NULL; - while ((row = taos_fetch_row(pRes)) != NULL) { - ASSERT (strcmp((const char*)row[0], result) == 0); - } - taos_free_result(pRes); -} - -void check_sql_result_integer(TAOS* pConn, const char *sql, int64_t result){ - TAOS_RES *pRes = taos_query(pConn, sql); - ASSERT(taos_errno(pRes) == 0); - TAOS_ROW row = NULL; - while ((row = taos_fetch_row(pRes)) != NULL) { - ASSERT (*(int64_t*)row[0] == result); - } - taos_free_result(pRes); -} - -void check_set_timezone(TAOS* optionFunc(const char *tz)){ - { - TAOS* pConn = optionFunc("UTC-8"); - check_timezone(pConn, "show local variables", "UTC-8"); - - execQuery(pConn, "drop database if exists db1"); - execQuery(pConn, "create database db1"); - execQuery(pConn, "create table db1.t1 (ts timestamp, v int)"); - - execQuery(pConn, "insert into db1.t1 values('2023-09-16 17:00:00', 1)"); - checkRows(pConn, "select * from db1.t1 where ts == '2023-09-16 17:00:00'", 1); - - taos_close(pConn); - } - - { - TAOS* pConn = optionFunc("UTC+8"); - check_timezone(pConn, "show local variables", "UTC+8"); - checkRows(pConn, "select * from db1.t1 where ts == '2023-09-16 01:00:00'", 1); - execQuery(pConn, "insert into db1.t1 values('2023-09-16 17:00:01', 1)"); - - taos_close(pConn); - } - - { - TAOS* pConn = optionFunc("UTC+0"); - check_timezone(pConn, "show local variables", "UTC+0"); - checkRows(pConn, "select * from db1.t1 where ts == '2023-09-16 09:00:00'", 1); - checkRows(pConn, "select * from db1.t1 where ts == '2023-09-17 01:00:01'", 1); - - taos_close(pConn); - } -} - -TEST(clientCase, set_timezone_Test) { - check_set_timezone(getConnWithGlobalOption); - check_set_timezone(getConnWithOption); -} - -TEST(clientCase, alter_timezone_Test) { - TAOS* pConn = getConnWithGlobalOption("UTC-8"); - check_timezone(pConn, "show local variables", "UTC-8"); - - execQuery(pConn, "alter local 'timezone Asia/Kolkata'"); - check_timezone(pConn, "show local variables", "Asia/Kolkata"); - - execQueryFail(pConn, "alter dnode 1 'timezone Asia/Kolkata'"); -} - -struct insert_params -{ - const char *tz; - const char *tbname; - const char *t1; - const char *t2; -}; - -struct insert_params params1[] = { - {"UTC", "ntb", "2023-09-16 17:00:00", "2023-09-16 17:00:00+08:00"}, - {"UTC", "ctb1", "2023-09-16 17:00:00", "2023-09-16 17:00:00+08:00"}, -}; - -struct insert_params params2[] = { - {"UTC+9", "ntb", "2023-09-16 08:00:00", "2023-09-16 08:00:00-01:00"}, - {"UTC+9", "ctb1", "2023-09-16 08:00:00", "2023-09-16 11:00:00+02:00"}, -}; - -void do_insert(struct insert_params params){ - TAOS* pConn = getConnWithOption(params.tz); - char sql[1024] = {0}; - (void)snprintf(sql, sizeof(sql), "insert into db1.%s values('%s', '%s', 1)", params.tbname, params.t1, params.t2); - execQuery(pConn, sql); - taos_close(pConn); -} - -void do_select(struct insert_params params){ - TAOS* pConn = getConnWithOption(params.tz); - char sql[1024] = {0}; - (void)snprintf(sql, sizeof(sql), "select * from db1.%s where ts == '%s' and c1 == '%s'", params.tbname, params.t1, params.t2); - checkRows(pConn, sql, 1); - taos_close(pConn); -} - -// test insert string and integer to timestamp both normal table and child table(and tag) -TEST(clientCase, insert_with_timezone_Test) { - /* - * 1. prepare data, create db and tables - */ - TAOS* pConn1 = getConnWithOption("UTC+2"); - execQuery(pConn1, "drop database if exists db1"); - execQuery(pConn1, "create database db1"); - execQuery(pConn1, "create table db1.ntb (ts timestamp, c1 timestamp, c2 int)"); - execQuery(pConn1, "create table db1.stb (ts timestamp, c1 timestamp, c2 int) tags(t1 timestamp, t2 timestamp, t3 int)"); - execQuery(pConn1, "create table db1.ctb1 using db1.stb tags(\"2023-09-16 17:00:00+05:00\", \"2023-09-16 17:00:00\", 1)"); - execQuery(pConn1, "create table db1.ctb2 using db1.stb tags(1732178775000, 1732178775000, 1)"); - execQuery(pConn1, "insert into db1.ntb values(1732178775133, 1732178775133, 1)"); - execQuery(pConn1, "insert into db1.ctb1 values(1732178775133, 1732178775133, 1)"); - execQuery(pConn1, "insert into db1.ctb2 values(1732178775133, 1732178775133, 1)"); - - /* - * 2. test tag and timestamp with integer format - */ - TAOS* pConn2 = getConnWithOption("UTC-2"); - checkRows(pConn2, "select * from db1.stb where t1 == '2023-09-16 17:00:00+05:00' and t2 == '2023-09-16 21:00:00'", 1); - checkRows(pConn2, "select * from db1.stb where t1 == '2024-11-21 16:46:15+08:00' and t2 == '2024-11-21 09:46:15+01:00'", 1); - checkRows(pConn2, "select * from db1.ntb where ts == '2024-11-21 09:46:15.133+01:00' and c1 == '2024-11-21 10:46:15.133'", 1); - checkRows(pConn2, "select * from db1.ctb1 where ts == '2024-11-21 09:46:15.133+01:00' and c1 == '2024-11-21 10:46:15.133'", 1); - - check_sql_result(pConn2, "select TO_ISO8601(ts) from db1.ctb1", "2024-11-21T10:46:15.133+0200"); // 2024-01-01 23:00:00+0200 - - - /* - * 3. test timestamp with string format - */ - for (unsigned int i = 0; i < sizeof (params1) / sizeof (params1[0]); ++i){ - do_insert(params1[i]); - do_select(params1[i]); - do_select(params2[i]); - } - - /* - * 4. test NULL timezone, use default timezone UTC-8 - */ - TAOS* pConn3 = getConnWithOption(NULL); - checkRows(pConn3, "select * from db1.stb where t1 == '2023-09-16 20:00:00' and t2 == '2023-09-17 03:00:00'", 2); - checkRows(pConn3, "select * from db1.stb where t1 == 1732178775000 and t2 == 1732178775000", 1); - checkRows(pConn3, "select * from db1.ntb where ts == '2024-11-21 16:46:15.133' and c1 == '2024-11-21 16:46:15.133'", 1); - checkRows(pConn3, "select * from db1.ctb1 where ts == '2023-09-17 01:00:00' and c1 == '2023-09-16 17:00:00'", 1); - - /* - * 5. test multi connection with different timezone - */ - checkRows(pConn2, "select * from db1.ctb1 where ts == '2024-11-21 09:46:15.133+01:00' and c1 == '2024-11-21 10:46:15.133'", 1); - checkRows(pConn1, "select * from db1.ctb1 where ts == '2024-11-21 09:46:15.133+01:00' and c1 == '2024-11-21 06:46:15.133'", 1); - - taos_close(pConn1); - taos_close(pConn2); - taos_close(pConn3); -} - -TEST(clientCase, func_timezone_Test) { - TAOS* pConn = getConnWithGlobalOption("UTC+8"); - check_sql_result(pConn, "select timezone()", "UTC+8 (UTC, -0800)"); - taos_close(pConn); - - pConn = getConnWithOption("UTC-2"); - - execQuery(pConn, "drop database if exists db1"); - execQuery(pConn, "create database db1"); - execQuery(pConn, "create table db1.ntb (ts timestamp, c1 binary(32), c2 int)"); - execQuery(pConn, "insert into db1.ntb values(1704142800000, '2024-01-01 23:00:00', 1)"); // 2024-01-01 23:00:00+0200 - - // test timezone - check_sql_result(pConn, "select timezone()", "UTC-test (UTC, +0200)"); - - // test timetruncate - check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 23:00:00', 1d, 0))", "2024-01-01T02:00:00.000+0200"); - check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 01:00:00', 1d, 0))", "2023-12-31T02:00:00.000+0200"); - check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 01:00:00+0300', 1d, 0))", "2023-12-31T02:00:00.000+0200"); - check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 01:00:00-0300', 1d, 0))", "2024-01-01T02:00:00.000+0200"); - - check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-04 23:00:00', 1w, 0))", "2024-01-04T02:00:00.000+0200"); - check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-04 01:00:00', 1w, 0))", "2023-12-28T02:00:00.000+0200"); - check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-04 01:00:00+0300', 1w, 0))", "2023-12-28T02:00:00.000+0200"); - check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-04 01:00:00-0300', 1w, 0))", "2024-01-04T02:00:00.000+0200"); - - check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 23:00:00', 1d, 1))", "2024-01-01T00:00:00.000+0200"); - check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 01:00:00', 1d, 1))", "2024-01-01T00:00:00.000+0200"); - check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 01:00:00+0500', 1d, 1))", "2023-12-31T00:00:00.000+0200"); - check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 01:00:00-0300', 1d, 1))", "2024-01-01T00:00:00.000+0200"); - - check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-04 23:00:00', 1w, 1))", "2024-01-04T00:00:00.000+0200"); - check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-04 01:00:00', 1w, 1))", "2024-01-04T00:00:00.000+0200"); - check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-04 01:00:00+0500', 1w, 1))", "2023-12-28T00:00:00.000+0200"); - check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-04 01:00:00-0300', 1w, 1))", "2024-01-04T00:00:00.000+0200"); - - check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE(1704142800000, 1d, 0))", "2024-01-01T02:00:00.000+0200"); // 2024-01-01 23:00:00+0200 - check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE(ts, 1w, 1)) from db1.ntb", "2023-12-28T00:00:00.000+0200"); // 2024-01-01 23:00:00+0200 - - // TODAY - check_sql_result_partial(pConn, "select TO_ISO8601(today())", "T00:00:00.000+0200"); - - // NOW - check_sql_result_partial(pConn, "select TO_ISO8601(now())", "+0200"); - - // WEEKDAY - check_sql_result_integer(pConn, "select WEEKDAY('2024-01-01')", 0); - check_sql_result_integer(pConn, "select WEEKDAY('2024-01-01 03:00:00')", 0); - check_sql_result_integer(pConn, "select WEEKDAY('2024-01-01 23:00:00+0200')", 0); - check_sql_result_integer(pConn, "select WEEKDAY('2024-01-01 23:00:00-1100')", 1); - check_sql_result_integer(pConn, "select WEEKDAY(1704142800000)", 0); - check_sql_result_integer(pConn, "select WEEKDAY(ts) from db1.ntb", 1); - - // DAYOFWEEK - check_sql_result_integer(pConn, "select DAYOFWEEK('2024-01-01')", 2); - check_sql_result_integer(pConn, "select DAYOFWEEK('2024-01-01 03:00:00')", 2); - check_sql_result_integer(pConn, "select DAYOFWEEK('2024-01-01 23:00:00+0200')", 2); - check_sql_result_integer(pConn, "select DAYOFWEEK('2024-01-01 23:00:00-1100')", 3); - check_sql_result_integer(pConn, "select DAYOFWEEK(1704142800000)", 2); - check_sql_result_integer(pConn, "select DAYOFWEEK(ts) from db1.ntb", 3); - - // WEEK - check_sql_result_integer(pConn, "select WEEK('2024-01-07')", 1); - check_sql_result_integer(pConn, "select WEEK('2024-01-07 02:00:00')", 1); - check_sql_result_integer(pConn, "select WEEK('2024-01-07 02:00:00+0200')", 1); - check_sql_result_integer(pConn, "select WEEK('2024-01-07 02:00:00+1100')", 0); - check_sql_result_integer(pConn, "select WEEK(1704142800000)", 0); // 2024-01-01 23:00:00+0200 - check_sql_result_integer(pConn, "select WEEK(ts) from db1.ntb", 0); // 2024-01-01 23:00:00+0200 - - check_sql_result_integer(pConn, "select WEEK('2024-01-07', 3)", 1); - check_sql_result_integer(pConn, "select WEEK('2024-01-07 02:00:00', 3)", 1); - check_sql_result_integer(pConn, "select WEEK('2024-01-07 02:00:00+0200', 3)", 1); - check_sql_result_integer(pConn, "select WEEK('2024-01-01 02:00:00+1100', 3)", 52); - check_sql_result_integer(pConn, "select WEEK(1704142800000, 3)", 1); // 2024-01-01 23:00:00+0200 - check_sql_result_integer(pConn, "select WEEK(ts, 3) from db1.ntb", 1); // 2024-01-01 23:00:00+0200 - - // WEEKOFYEAR - check_sql_result_integer(pConn, "select WEEKOFYEAR('2024-01-07')", 1); - check_sql_result_integer(pConn, "select WEEKOFYEAR('2024-01-07 02:00:00')", 1); - check_sql_result_integer(pConn, "select WEEKOFYEAR('2024-01-07 02:00:00+0200')", 1); - check_sql_result_integer(pConn, "select WEEKOFYEAR('2024-01-01 02:00:00+1100')", 52); - check_sql_result_integer(pConn, "select WEEKOFYEAR(1704142800000)", 1); // 2024-01-01 23:00:00+0200 - check_sql_result_integer(pConn, "select WEEKOFYEAR(ts) from db1.ntb", 1); // 2024-01-01 23:00:00+0200 - - // TO_ISO8601 - check_sql_result(pConn, "select TO_ISO8601(ts) from db1.ntb", "2024-01-01T23:00:00.000+0200"); - check_sql_result(pConn, "select TO_ISO8601(ts,'-08') from db1.ntb", "2024-01-01T13:00:00.000-08"); - check_sql_result(pConn, "select TO_ISO8601(1)", "1970-01-01T02:00:00.001+0200"); - check_sql_result(pConn, "select TO_ISO8601(1,'+0800')", "1970-01-01T08:00:00.001+0800"); - - // TO_UNIXTIMESTAMP - check_sql_result_integer(pConn, "select TO_UNIXTIMESTAMP(c1) from db1.ntb", 1704121200000); // use timezone in server UTC-8 - check_sql_result_integer(pConn, "select TO_UNIXTIMESTAMP('2024-01-01T23:00:00.000+0200')", 1704142800000); - check_sql_result_integer(pConn, "select TO_UNIXTIMESTAMP('2024-01-01T13:00:00.000-08')", 1704142800000); - check_sql_result_integer(pConn, "select TO_UNIXTIMESTAMP('2024-01-01T23:00:00.001')", 1704142800001); - - // TO_TIMESTAMP - check_sql_result_integer(pConn, "select TO_TIMESTAMP(c1,'yyyy-mm-dd hh24:mi:ss') from db1.ntb", 1704121200000); // use timezone in server UTC-8 - check_sql_result_integer(pConn, "select TO_TIMESTAMP('2024-01-01 23:00:00+02:00', 'yyyy-mm-dd hh24:mi:ss tzh')", 1704142800000); - check_sql_result_integer(pConn, "select TO_TIMESTAMP('2024-01-01T13:00:00-08', 'yyyy-mm-ddThh24:mi:ss tzh')", 1704142800000); - check_sql_result_integer(pConn, "select TO_TIMESTAMP('2024/01/01 23:00:00', 'yyyy/mm/dd hh24:mi:ss')", 1704142800000); - - // TO_CHAR - check_sql_result(pConn, "select TO_CHAR(ts,'yyyy-mm-dd hh24:mi:ss') from db1.ntb", "2024-01-02 05:00:00"); // use timezone in server UTC-8 - check_sql_result(pConn, "select TO_CHAR(cast(1704142800000 as timestamp), 'yyyy-mm-dd hh24:mi:ss tzh')", "2024-01-01 23:00:00 +02"); - check_sql_result(pConn, "select TO_CHAR(cast(1704142800000 as timestamp), 'yyyy-mm-dd hh24:mi:ss')", "2024-01-01 23:00:00"); - - // TIMEDIFF - check_sql_result_integer(pConn, "select TIMEDIFF(c1, '2024-01-01T23:00:00.001+02') from db1.ntb", -21600001); // use timezone in server UTC-8 - check_sql_result_integer(pConn, "select TIMEDIFF(c1, '2024-01-01T23:00:00.001') from db1.ntb", -1); // use timezone in server UTC-8 - check_sql_result_integer(pConn, "select TIMEDIFF('2024-01-01T23:00:00.001', '2024-01-01T13:00:00.000-08')", 1); - - // CAST - check_sql_result_integer(pConn, "select CAST(c1 as timestamp) from db1.ntb", 1704121200000); - check_sql_result_integer(pConn, "select CAST('2024-01-01T23:00:00.000+02' as timestamp)", 1704142800000); - check_sql_result_integer(pConn, "select CAST('2024-01-01T23:00:00.000' as timestamp)", 1704142800000); - - taos_close(pConn); - - // hash join - pConn = getConnWithOption("UTC+1"); - - execQuery(pConn, "drop database if exists db1"); - execQuery(pConn, "create database db1"); - execQuery(pConn, "create table db1.ntb (ts timestamp, c1 binary(32), c2 int)"); - execQuery(pConn, "create table db1.ntb1 (ts timestamp, c1 binary(32), c2 int)"); - execQuery(pConn, "insert into db1.ntb values(1703987400000, '2023-12-31 00:50:00', 1)"); // 2023-12-31 00:50:00-0100 - execQuery(pConn, "insert into db1.ntb1 values(1704070200000, '2023-12-31 23:50:00', 11)"); // 2023-12-31 23:50:00-0100 - checkRows(pConn, "select a.ts,b.ts from db1.ntb a join db1.ntb1 b on timetruncate(a.ts, 1d) = timetruncate(b.ts, 1d)", 1); - - taos_close(pConn); - -} - -time_t time_winter = 1731323281; // 2024-11-11 19:08:01+0800 -time_t time_summer = 1731323281 - 120 * 24 * 60 * 60; - -struct test_times -{ - const char *name; - time_t t; - const char *timezone; -} test_tz[] = { - {"", time_winter, " (UTC, +0000)"}, - {"America/New_York", time_winter, "America/New_York (EST, -0500)"}, // 2024-11-11 19:08:01+0800 - {"America/New_York", time_summer, "America/New_York (EDT, -0400)"}, - {"Asia/Kolkata", time_winter, "Asia/Kolkata (IST, +0530)"}, - {"Asia/Shanghai", time_winter, "Asia/Shanghai (CST, +0800)"}, - {"Europe/London", time_winter, "Europe/London (GMT, +0000)"}, - {"Europe/London", time_summer, "Europe/London (BST, +0100)"} -}; - -void timezone_str_test(const char* tz, time_t t, const char* tzStr) { - int code = setenv("TZ", tz, 1); - ASSERT(-1 != code); - tzset(); - - char str1[TD_TIMEZONE_LEN] = {0}; - ASSERT(taosFormatTimezoneStr(t, tz, NULL, str1) == 0); - ASSERT_STREQ(str1, tzStr); -} - -void timezone_rz_str_test(const char* tz, time_t t, const char* tzStr) { - timezone_t sp = tzalloc(tz); - ASSERT(sp); - - char str1[TD_TIMEZONE_LEN] = {0}; - ASSERT(taosFormatTimezoneStr(t, tz, sp, str1) == 0); - ASSERT_STREQ(str1, tzStr); - tzfree(sp); -} - -TEST(clientCase, format_timezone_Test) { - for (unsigned int i = 0; i < sizeof (test_tz) / sizeof (test_tz[0]); ++i){ - timezone_str_test(test_tz[i].name, test_tz[i].t, test_tz[i].timezone); - timezone_str_test(test_tz[i].name, test_tz[i].t, test_tz[i].timezone); - } -} - -TEST(clientCase, get_tz_Test) { - { - char tz[TD_TIMEZONE_LEN] = {0}; - getTimezoneStr(tz); - ASSERT_STREQ(tz, "Asia/Shanghai"); - -// getTimezoneStr(tz); -// ASSERT_STREQ(tz, "Asia/Shanghai"); -// -// getTimezoneStr(tz); -// ASSERT_STREQ(tz, "n/a"); - } -} - -struct { - const char * env; - time_t expected; -} test_mk[] = { - {"MST", 832935315}, - {"", 832910115}, - {":UTC", 832910115}, - {"UTC", 832910115}, - {"UTC0", 832910115} -}; - - -TEST(clientCase, mktime_Test){ - struct tm tm; - time_t t; - - memset (&tm, 0, sizeof (tm)); - tm.tm_isdst = 0; - tm.tm_year = 96; /* years since 1900 */ - tm.tm_mon = 4; - tm.tm_mday = 24; - tm.tm_hour = 3; - tm.tm_min = 55; - tm.tm_sec = 15; - - for (unsigned int i = 0; i < sizeof (test_mk) / sizeof (test_mk[0]); ++i) - { - setenv ("TZ", test_mk[i].env, 1); - t = taosMktime (&tm, NULL); - ASSERT (t == test_mk[i].expected); - } -} - -TEST(clientCase, mktime_rz_Test){ - struct tm tm; - time_t t; - - memset (&tm, 0, sizeof (tm)); - tm.tm_isdst = 0; - tm.tm_year = 96; /* years since 1900 */ - tm.tm_mon = 4; - tm.tm_mday = 24; - tm.tm_hour = 3; - tm.tm_min = 55; - tm.tm_sec = 15; - - for (unsigned int i = 0; i < sizeof (test_mk) / sizeof (test_mk[0]); ++i) - { - timezone_t tz = tzalloc(test_mk[i].env); - ASSERT(tz); - t = taosMktime(&tm, tz); - ASSERT (t == test_mk[i].expected); - tzfree(tz); - } -} - -TEST(testCase, localtime_performance_Test) { - timezone_t sp = tzalloc("Asia/Shanghai"); - ASSERT(sp); - - int cnt = 10000000; - int times = 10; - int64_t time_localtime = 0; - int64_t time_localtime_rz = 0; -// int cnt = 1000000; - for (int i = 0; i < times; ++i) { - int64_t t1 = taosGetTimestampNs(); - for (int j = 0; j < cnt; ++j) { - time_t t = time_winter - j; - struct tm tm1; - ASSERT (taosLocalTime(&t, &tm1, NULL, 0, NULL)); - } - int64_t tmp = taosGetTimestampNs() - t1; - printf("localtime cost:%" PRId64 " ns, run %" PRId64 " times", tmp, cnt); - time_localtime += tmp/cnt; - - printf("\n"); - - - - int64_t t2 = taosGetTimestampNs(); - for (int j = 0; j < cnt; ++j) { - time_t t = time_winter - j; - struct tm tm1; - ASSERT (taosLocalTime(&t, &tm1, NULL, 0, sp)); - } - tmp = taosGetTimestampNs() - t2; - printf("localtime_rz cost:%" PRId64 " ns, run %" PRId64 " times", tmp, cnt); - time_localtime_rz += tmp/cnt; - printf("\n\n"); - } - printf("average: localtime cost:%" PRId64 " ns, localtime_rz cost:%" PRId64 " ns", time_localtime/times, time_localtime_rz/times); - tzfree(sp); -} - - #pragma GCC diagnostic pop diff --git a/source/client/test/timezoneTest.cpp b/source/client/test/timezoneTest.cpp new file mode 100644 index 0000000000..ff555540bd --- /dev/null +++ b/source/client/test/timezoneTest.cpp @@ -0,0 +1,563 @@ +/* + * Copyright (c) 2019 TAOS Data, Inc. + * + * This program is free software: you can use, redistribute, and/or modify + * it under the terms of the GNU Affero General Public License, version 3 + * or later ("AGPL"), as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include +#include +#include "taoserror.h" +#include "tglobal.h" +#include "thash.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wwrite-strings" +#pragma GCC diagnostic ignored "-Wunused-function" +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic ignored "-Wsign-compare" + +#include "executor.h" +#include "taos.h" + + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +} + +TAOS* getConnWithGlobalOption(const char *tz){ + int code = taos_options(TSDB_OPTION_TIMEZONE, tz); + ASSERT(code == 0); + TAOS* pConn = taos_connect("localhost", "root", "taosdata", NULL, 0); + ASSERT(pConn != nullptr); + return pConn; +} + +TAOS* getConnWithOption(const char *tz){ + TAOS* pConn = taos_connect("localhost", "root", "taosdata", NULL, 0); + ASSERT(pConn != nullptr); + if (tz != NULL){ + int code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_TIMEZONE, tz); + ASSERT(code == 0); + } + return pConn; +} + +void execQuery(TAOS* pConn, const char *sql){ + TAOS_RES* pRes = taos_query(pConn, sql); + ASSERT(taos_errno(pRes) == TSDB_CODE_SUCCESS); + taos_free_result(pRes); +} + +void execQueryFail(TAOS* pConn, const char *sql){ + TAOS_RES* pRes = taos_query(pConn, sql); + ASSERT(taos_errno(pRes) != TSDB_CODE_SUCCESS); + taos_free_result(pRes); +} + +void checkRows(TAOS* pConn, const char *sql, int32_t expectedRows){ + TAOS_RES* pRes = taos_query(pConn, sql); + ASSERT(taos_errno(pRes) == TSDB_CODE_SUCCESS); + TAOS_ROW pRow = NULL; + int rows = 0; + while ((pRow = taos_fetch_row(pRes)) != NULL) { + rows++; + } + ASSERT(rows == expectedRows); + taos_free_result(pRes); +} + +void check_timezone(TAOS* pConn, const char *sql, const char* tz){ + TAOS_RES *pRes = taos_query(pConn, sql); + ASSERT(taos_errno(pRes) == 0); + TAOS_ROW row = NULL; + while ((row = taos_fetch_row(pRes)) != NULL) { + if (strcmp((const char*)row[0], "timezone") == 0){ + ASSERT(strstr((const char*)row[1], tz) != NULL); + } + } + taos_free_result(pRes); +} + +void check_sql_result_partial(TAOS* pConn, const char *sql, const char* result){ + TAOS_RES *pRes = taos_query(pConn, sql); + ASSERT(taos_errno(pRes) == 0); + TAOS_ROW row = NULL; + while ((row = taos_fetch_row(pRes)) != NULL) { + ASSERT(strstr((const char*)row[0], result) != NULL); + } + taos_free_result(pRes); +} + +int64_t get_sql_result(TAOS* pConn, const char *sql){ + int64_t ts = 0; + TAOS_RES *pRes = taos_query(pConn, sql); + ASSERT(taos_errno(pRes) == 0); + TAOS_ROW row = NULL; + while ((row = taos_fetch_row(pRes)) != NULL) { + ts = *(int64_t*)row[0]; + } + taos_free_result(pRes); + return ts; +} + +void check_sql_result(TAOS* pConn, const char *sql, const char* result){ + TAOS_RES *pRes = taos_query(pConn, sql); + ASSERT(taos_errno(pRes) == 0); + TAOS_ROW row = NULL; + while ((row = taos_fetch_row(pRes)) != NULL) { + ASSERT (strcmp((const char*)row[0], result) == 0); + } + taos_free_result(pRes); +} + +void check_sql_result_integer(TAOS* pConn, const char *sql, int64_t result){ + TAOS_RES *pRes = taos_query(pConn, sql); + ASSERT(taos_errno(pRes) == 0); + TAOS_ROW row = NULL; + while ((row = taos_fetch_row(pRes)) != NULL) { + ASSERT (*(int64_t*)row[0] == result); + } + taos_free_result(pRes); +} + +void check_set_timezone(TAOS* optionFunc(const char *tz)){ + { + TAOS* pConn = optionFunc("UTC-8"); + check_timezone(pConn, "show local variables", "UTC-8"); + + execQuery(pConn, "drop database if exists db1"); + execQuery(pConn, "create database db1"); + execQuery(pConn, "create table db1.t1 (ts timestamp, v int)"); + + execQuery(pConn, "insert into db1.t1 values('2023-09-16 17:00:00', 1)"); + checkRows(pConn, "select * from db1.t1 where ts == '2023-09-16 17:00:00'", 1); + + taos_close(pConn); + } + + { + TAOS* pConn = optionFunc("UTC+8"); + check_timezone(pConn, "show local variables", "UTC+8"); + checkRows(pConn, "select * from db1.t1 where ts == '2023-09-16 01:00:00'", 1); + execQuery(pConn, "insert into db1.t1 values('2023-09-16 17:00:01', 1)"); + + taos_close(pConn); + } + + { + TAOS* pConn = optionFunc("UTC+0"); + check_timezone(pConn, "show local variables", "UTC+0"); + checkRows(pConn, "select * from db1.t1 where ts == '2023-09-16 09:00:00'", 1); + checkRows(pConn, "select * from db1.t1 where ts == '2023-09-17 01:00:01'", 1); + + taos_close(pConn); + } +} + +TEST(timezoneCase, set_timezone_Test) { + check_set_timezone(getConnWithGlobalOption); + check_set_timezone(getConnWithOption); +} + +TEST(timezoneCase, alter_timezone_Test) { + TAOS* pConn = getConnWithGlobalOption("UTC-8"); + check_timezone(pConn, "show local variables", "UTC-8"); + + execQuery(pConn, "alter local 'timezone Asia/Kolkata'"); + check_timezone(pConn, "show local variables", "Asia/Kolkata"); + + execQueryFail(pConn, "alter dnode 1 'timezone Asia/Kolkata'"); +} + +struct insert_params +{ + const char *tz; + const char *tbname; + const char *t1; + const char *t2; +}; + +struct insert_params params1[] = { + {"UTC", "ntb", "2023-09-16 17:00:00", "2023-09-16 17:00:00+08:00"}, + {"UTC", "ctb1", "2023-09-16 17:00:00", "2023-09-16 17:00:00+08:00"}, +}; + +struct insert_params params2[] = { + {"UTC+9", "ntb", "2023-09-16 08:00:00", "2023-09-16 08:00:00-01:00"}, + {"UTC+9", "ctb1", "2023-09-16 08:00:00", "2023-09-16 11:00:00+02:00"}, +}; + +void do_insert(struct insert_params params){ + TAOS* pConn = getConnWithOption(params.tz); + char sql[1024] = {0}; + (void)snprintf(sql, sizeof(sql), "insert into db1.%s values('%s', '%s', 1)", params.tbname, params.t1, params.t2); + execQuery(pConn, sql); + taos_close(pConn); +} + +void do_select(struct insert_params params){ + TAOS* pConn = getConnWithOption(params.tz); + char sql[1024] = {0}; + (void)snprintf(sql, sizeof(sql), "select * from db1.%s where ts == '%s' and c1 == '%s'", params.tbname, params.t1, params.t2); + checkRows(pConn, sql, 1); + taos_close(pConn); +} + +// test insert string and integer to timestamp both normal table and child table(and tag) +TEST(timezoneCase, insert_with_timezone_Test) { + /* + * 1. prepare data, create db and tables + */ + TAOS* pConn1 = getConnWithOption("UTC+2"); + execQuery(pConn1, "drop database if exists db1"); + execQuery(pConn1, "create database db1"); + execQuery(pConn1, "create table db1.ntb (ts timestamp, c1 timestamp, c2 int)"); + execQuery(pConn1, "create table db1.stb (ts timestamp, c1 timestamp, c2 int) tags(t1 timestamp, t2 timestamp, t3 int)"); + execQuery(pConn1, "create table db1.ctb1 using db1.stb tags(\"2023-09-16 17:00:00+05:00\", \"2023-09-16 17:00:00\", 1)"); + execQuery(pConn1, "create table db1.ctb2 using db1.stb tags(1732178775000, 1732178775000, 1)"); + execQuery(pConn1, "insert into db1.ntb values(1732178775133, 1732178775133, 1)"); + execQuery(pConn1, "insert into db1.ctb1 values(1732178775133, 1732178775133, 1)"); + execQuery(pConn1, "insert into db1.ctb2 values(1732178775133, 1732178775133, 1)"); + + /* + * 2. test tag and timestamp with integer format + */ + TAOS* pConn2 = getConnWithOption("UTC-2"); + checkRows(pConn2, "select * from db1.stb where t1 == '2023-09-16 17:00:00+05:00' and t2 == '2023-09-16 21:00:00'", 1); + checkRows(pConn2, "select * from db1.stb where t1 == '2024-11-21 16:46:15+08:00' and t2 == '2024-11-21 09:46:15+01:00'", 1); + checkRows(pConn2, "select * from db1.ntb where ts == '2024-11-21 09:46:15.133+01:00' and c1 == '2024-11-21 10:46:15.133'", 1); + checkRows(pConn2, "select * from db1.ctb1 where ts == '2024-11-21 09:46:15.133+01:00' and c1 == '2024-11-21 10:46:15.133'", 1); + + check_sql_result(pConn2, "select TO_ISO8601(ts) from db1.ctb1", "2024-11-21T10:46:15.133+0200"); // 2024-01-01 23:00:00+0200 + + + /* + * 3. test timestamp with string format + */ + for (unsigned int i = 0; i < sizeof (params1) / sizeof (params1[0]); ++i){ + do_insert(params1[i]); + do_select(params1[i]); + do_select(params2[i]); + } + + /* + * 4. test NULL timezone, use default timezone UTC-8 + */ + TAOS* pConn3 = getConnWithOption(NULL); + checkRows(pConn3, "select * from db1.stb where t1 == '2023-09-16 20:00:00' and t2 == '2023-09-17 03:00:00'", 2); + checkRows(pConn3, "select * from db1.stb where t1 == 1732178775000 and t2 == 1732178775000", 1); + checkRows(pConn3, "select * from db1.ntb where ts == '2024-11-21 16:46:15.133' and c1 == '2024-11-21 16:46:15.133'", 1); + checkRows(pConn3, "select * from db1.ctb1 where ts == '2023-09-17 01:00:00' and c1 == '2023-09-16 17:00:00'", 1); + + /* + * 5. test multi connection with different timezone + */ + checkRows(pConn2, "select * from db1.ctb1 where ts == '2024-11-21 09:46:15.133+01:00' and c1 == '2024-11-21 10:46:15.133'", 1); + checkRows(pConn1, "select * from db1.ctb1 where ts == '2024-11-21 09:46:15.133+01:00' and c1 == '2024-11-21 06:46:15.133'", 1); + + taos_close(pConn1); + taos_close(pConn2); + taos_close(pConn3); +} + +TEST(timezoneCase, func_timezone_Test) { + TAOS* pConn = getConnWithGlobalOption("UTC+8"); + check_sql_result(pConn, "select timezone()", "UTC+8 (UTC, -0800)"); + taos_close(pConn); + + pConn = getConnWithOption("UTC-2"); + + execQuery(pConn, "drop database if exists db1"); + execQuery(pConn, "create database db1"); + execQuery(pConn, "create table db1.ntb (ts timestamp, c1 binary(32), c2 int)"); + execQuery(pConn, "insert into db1.ntb values(1704142800000, '2024-01-01 23:00:00', 1)"); // 2024-01-01 23:00:00+0200 + + // test timezone + check_sql_result(pConn, "select timezone()", "UTC-test (UTC, +0200)"); + + // test timetruncate + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 23:00:00', 1d, 0))", "2024-01-01T02:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 01:00:00', 1d, 0))", "2023-12-31T02:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 01:00:00+0300', 1d, 0))", "2023-12-31T02:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 01:00:00-0300', 1d, 0))", "2024-01-01T02:00:00.000+0200"); + + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-04 23:00:00', 1w, 0))", "2024-01-04T02:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-04 01:00:00', 1w, 0))", "2023-12-28T02:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-04 01:00:00+0300', 1w, 0))", "2023-12-28T02:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-04 01:00:00-0300', 1w, 0))", "2024-01-04T02:00:00.000+0200"); + + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 23:00:00', 1d, 1))", "2024-01-01T00:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 01:00:00', 1d, 1))", "2024-01-01T00:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 01:00:00+0500', 1d, 1))", "2023-12-31T00:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 01:00:00-0300', 1d, 1))", "2024-01-01T00:00:00.000+0200"); + + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-04 23:00:00', 1w, 1))", "2024-01-04T00:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-04 01:00:00', 1w, 1))", "2024-01-04T00:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-04 01:00:00+0500', 1w, 1))", "2023-12-28T00:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-04 01:00:00-0300', 1w, 1))", "2024-01-04T00:00:00.000+0200"); + + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE(1704142800000, 1d, 0))", "2024-01-01T02:00:00.000+0200"); // 2024-01-01 23:00:00+0200 + check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE(ts, 1w, 1)) from db1.ntb", "2023-12-28T00:00:00.000+0200"); // 2024-01-01 23:00:00+0200 + + // TODAY + check_sql_result_partial(pConn, "select TO_ISO8601(today())", "T00:00:00.000+0200"); + + // NOW + check_sql_result_partial(pConn, "select TO_ISO8601(now())", "+0200"); + + // WEEKDAY + check_sql_result_integer(pConn, "select WEEKDAY('2024-01-01')", 0); + check_sql_result_integer(pConn, "select WEEKDAY('2024-01-01 03:00:00')", 0); + check_sql_result_integer(pConn, "select WEEKDAY('2024-01-01 23:00:00+0200')", 0); + check_sql_result_integer(pConn, "select WEEKDAY('2024-01-01 23:00:00-1100')", 1); + check_sql_result_integer(pConn, "select WEEKDAY(1704142800000)", 0); + check_sql_result_integer(pConn, "select WEEKDAY(ts) from db1.ntb", 1); + + // DAYOFWEEK + check_sql_result_integer(pConn, "select DAYOFWEEK('2024-01-01')", 2); + check_sql_result_integer(pConn, "select DAYOFWEEK('2024-01-01 03:00:00')", 2); + check_sql_result_integer(pConn, "select DAYOFWEEK('2024-01-01 23:00:00+0200')", 2); + check_sql_result_integer(pConn, "select DAYOFWEEK('2024-01-01 23:00:00-1100')", 3); + check_sql_result_integer(pConn, "select DAYOFWEEK(1704142800000)", 2); + check_sql_result_integer(pConn, "select DAYOFWEEK(ts) from db1.ntb", 3); + + // WEEK + check_sql_result_integer(pConn, "select WEEK('2024-01-07')", 1); + check_sql_result_integer(pConn, "select WEEK('2024-01-07 02:00:00')", 1); + check_sql_result_integer(pConn, "select WEEK('2024-01-07 02:00:00+0200')", 1); + check_sql_result_integer(pConn, "select WEEK('2024-01-07 02:00:00+1100')", 0); + check_sql_result_integer(pConn, "select WEEK(1704142800000)", 0); // 2024-01-01 23:00:00+0200 + check_sql_result_integer(pConn, "select WEEK(ts) from db1.ntb", 0); // 2024-01-01 23:00:00+0200 + + check_sql_result_integer(pConn, "select WEEK('2024-01-07', 3)", 1); + check_sql_result_integer(pConn, "select WEEK('2024-01-07 02:00:00', 3)", 1); + check_sql_result_integer(pConn, "select WEEK('2024-01-07 02:00:00+0200', 3)", 1); + check_sql_result_integer(pConn, "select WEEK('2024-01-01 02:00:00+1100', 3)", 52); + check_sql_result_integer(pConn, "select WEEK(1704142800000, 3)", 1); // 2024-01-01 23:00:00+0200 + check_sql_result_integer(pConn, "select WEEK(ts, 3) from db1.ntb", 1); // 2024-01-01 23:00:00+0200 + + // WEEKOFYEAR + check_sql_result_integer(pConn, "select WEEKOFYEAR('2024-01-07')", 1); + check_sql_result_integer(pConn, "select WEEKOFYEAR('2024-01-07 02:00:00')", 1); + check_sql_result_integer(pConn, "select WEEKOFYEAR('2024-01-07 02:00:00+0200')", 1); + check_sql_result_integer(pConn, "select WEEKOFYEAR('2024-01-01 02:00:00+1100')", 52); + check_sql_result_integer(pConn, "select WEEKOFYEAR(1704142800000)", 1); // 2024-01-01 23:00:00+0200 + check_sql_result_integer(pConn, "select WEEKOFYEAR(ts) from db1.ntb", 1); // 2024-01-01 23:00:00+0200 + + // TO_ISO8601 + check_sql_result(pConn, "select TO_ISO8601(ts) from db1.ntb", "2024-01-01T23:00:00.000+0200"); + check_sql_result(pConn, "select TO_ISO8601(ts,'-08') from db1.ntb", "2024-01-01T13:00:00.000-08"); + check_sql_result(pConn, "select TO_ISO8601(1)", "1970-01-01T02:00:00.001+0200"); + check_sql_result(pConn, "select TO_ISO8601(1,'+0800')", "1970-01-01T08:00:00.001+0800"); + + // TO_UNIXTIMESTAMP + check_sql_result_integer(pConn, "select TO_UNIXTIMESTAMP(c1) from db1.ntb", 1704121200000); // use timezone in server UTC-8 + check_sql_result_integer(pConn, "select TO_UNIXTIMESTAMP('2024-01-01T23:00:00.000+0200')", 1704142800000); + check_sql_result_integer(pConn, "select TO_UNIXTIMESTAMP('2024-01-01T13:00:00.000-08')", 1704142800000); + check_sql_result_integer(pConn, "select TO_UNIXTIMESTAMP('2024-01-01T23:00:00.001')", 1704142800001); + + // TO_TIMESTAMP + check_sql_result_integer(pConn, "select TO_TIMESTAMP(c1,'yyyy-mm-dd hh24:mi:ss') from db1.ntb", 1704121200000); // use timezone in server UTC-8 + check_sql_result_integer(pConn, "select TO_TIMESTAMP('2024-01-01 23:00:00+02:00', 'yyyy-mm-dd hh24:mi:ss tzh')", 1704142800000); + check_sql_result_integer(pConn, "select TO_TIMESTAMP('2024-01-01T13:00:00-08', 'yyyy-mm-ddThh24:mi:ss tzh')", 1704142800000); + check_sql_result_integer(pConn, "select TO_TIMESTAMP('2024/01/01 23:00:00', 'yyyy/mm/dd hh24:mi:ss')", 1704142800000); + + // TO_CHAR + check_sql_result(pConn, "select TO_CHAR(ts,'yyyy-mm-dd hh24:mi:ss') from db1.ntb", "2024-01-02 05:00:00"); // use timezone in server UTC-8 + check_sql_result(pConn, "select TO_CHAR(cast(1704142800000 as timestamp), 'yyyy-mm-dd hh24:mi:ss tzh')", "2024-01-01 23:00:00 +02"); + check_sql_result(pConn, "select TO_CHAR(cast(1704142800000 as timestamp), 'yyyy-mm-dd hh24:mi:ss')", "2024-01-01 23:00:00"); + + // TIMEDIFF + check_sql_result_integer(pConn, "select TIMEDIFF(c1, '2024-01-01T23:00:00.001+02') from db1.ntb", -21600001); // use timezone in server UTC-8 + check_sql_result_integer(pConn, "select TIMEDIFF(c1, '2024-01-01T23:00:00.001') from db1.ntb", -1); // use timezone in server UTC-8 + check_sql_result_integer(pConn, "select TIMEDIFF('2024-01-01T23:00:00.001', '2024-01-01T13:00:00.000-08')", 1); + + // CAST + check_sql_result_integer(pConn, "select CAST(c1 as timestamp) from db1.ntb", 1704121200000); + check_sql_result_integer(pConn, "select CAST('2024-01-01T23:00:00.000+02' as timestamp)", 1704142800000); + check_sql_result_integer(pConn, "select CAST('2024-01-01T23:00:00.000' as timestamp)", 1704142800000); + + taos_close(pConn); + + // hash join + pConn = getConnWithOption("UTC+1"); + + execQuery(pConn, "drop database if exists db1"); + execQuery(pConn, "create database db1"); + execQuery(pConn, "create table db1.ntb (ts timestamp, c1 binary(32), c2 int)"); + execQuery(pConn, "create table db1.ntb1 (ts timestamp, c1 binary(32), c2 int)"); + execQuery(pConn, "insert into db1.ntb values(1703987400000, '2023-12-31 00:50:00', 1)"); // 2023-12-31 00:50:00-0100 + execQuery(pConn, "insert into db1.ntb1 values(1704070200000, '2023-12-31 23:50:00', 11)"); // 2023-12-31 23:50:00-0100 + checkRows(pConn, "select a.ts,b.ts from db1.ntb a join db1.ntb1 b on timetruncate(a.ts, 1d) = timetruncate(b.ts, 1d)", 1); + + taos_close(pConn); + +} + +time_t time_winter = 1731323281; // 2024-11-11 19:08:01+0800 +time_t time_summer = 1731323281 - 120 * 24 * 60 * 60; + +struct test_times +{ + const char *name; + time_t t; + const char *timezone; +} test_tz[] = { + {"", time_winter, " (UTC, +0000)"}, + {"America/New_York", time_winter, "America/New_York (EST, -0500)"}, // 2024-11-11 19:08:01+0800 + {"America/New_York", time_summer, "America/New_York (EDT, -0400)"}, + {"Asia/Kolkata", time_winter, "Asia/Kolkata (IST, +0530)"}, + {"Asia/Shanghai", time_winter, "Asia/Shanghai (CST, +0800)"}, + {"Europe/London", time_winter, "Europe/London (GMT, +0000)"}, + {"Europe/London", time_summer, "Europe/London (BST, +0100)"} +}; + +void timezone_str_test(const char* tz, time_t t, const char* tzStr) { + int code = setenv("TZ", tz, 1); + ASSERT(-1 != code); + tzset(); + + char str1[TD_TIMEZONE_LEN] = {0}; + ASSERT(taosFormatTimezoneStr(t, tz, NULL, str1) == 0); + ASSERT_STREQ(str1, tzStr); +} + +void timezone_rz_str_test(const char* tz, time_t t, const char* tzStr) { + timezone_t sp = tzalloc(tz); + ASSERT(sp); + + char str1[TD_TIMEZONE_LEN] = {0}; + ASSERT(taosFormatTimezoneStr(t, tz, sp, str1) == 0); + ASSERT_STREQ(str1, tzStr); + tzfree(sp); +} + +TEST(timezoneCase, format_timezone_Test) { + for (unsigned int i = 0; i < sizeof (test_tz) / sizeof (test_tz[0]); ++i){ + timezone_str_test(test_tz[i].name, test_tz[i].t, test_tz[i].timezone); + timezone_str_test(test_tz[i].name, test_tz[i].t, test_tz[i].timezone); + } +} + +TEST(timezoneCase, get_tz_Test) { + { + char tz[TD_TIMEZONE_LEN] = {0}; + getTimezoneStr(tz); + ASSERT_STREQ(tz, "Asia/Shanghai"); + +// getTimezoneStr(tz); +// ASSERT_STREQ(tz, "Asia/Shanghai"); +// +// getTimezoneStr(tz); +// ASSERT_STREQ(tz, "n/a"); + } +} + +struct { + const char * env; + time_t expected; +} test_mk[] = { + {"MST", 832935315}, + {"", 832910115}, + {":UTC", 832910115}, + {"UTC", 832910115}, + {"UTC0", 832910115} +}; + + +TEST(timezoneCase, mktime_Test){ + struct tm tm; + time_t t; + + memset (&tm, 0, sizeof (tm)); + tm.tm_isdst = 0; + tm.tm_year = 96; /* years since 1900 */ + tm.tm_mon = 4; + tm.tm_mday = 24; + tm.tm_hour = 3; + tm.tm_min = 55; + tm.tm_sec = 15; + + for (unsigned int i = 0; i < sizeof (test_mk) / sizeof (test_mk[0]); ++i) + { + setenv ("TZ", test_mk[i].env, 1); + t = taosMktime (&tm, NULL); + ASSERT (t == test_mk[i].expected); + } +} + +TEST(timezoneCase, mktime_rz_Test){ + struct tm tm; + time_t t; + + memset (&tm, 0, sizeof (tm)); + tm.tm_isdst = 0; + tm.tm_year = 96; /* years since 1900 */ + tm.tm_mon = 4; + tm.tm_mday = 24; + tm.tm_hour = 3; + tm.tm_min = 55; + tm.tm_sec = 15; + + for (unsigned int i = 0; i < sizeof (test_mk) / sizeof (test_mk[0]); ++i) + { + timezone_t tz = tzalloc(test_mk[i].env); + ASSERT(tz); + t = taosMktime(&tm, tz); + ASSERT (t == test_mk[i].expected); + tzfree(tz); + } +} + +TEST(timezoneCase, localtime_performance_Test) { + timezone_t sp = tzalloc("Asia/Shanghai"); + ASSERT(sp); + + int cnt = 10000000; + int times = 10; + int64_t time_localtime = 0; + int64_t time_localtime_rz = 0; +// int cnt = 1000000; + for (int i = 0; i < times; ++i) { + int64_t t1 = taosGetTimestampNs(); + for (int j = 0; j < cnt; ++j) { + time_t t = time_winter - j; + struct tm tm1; + ASSERT (taosLocalTime(&t, &tm1, NULL, 0, NULL)); + } + int64_t tmp = taosGetTimestampNs() - t1; + printf("localtime cost:%" PRId64 " ns, run %d times", tmp, cnt); + time_localtime += tmp/cnt; + + printf("\n"); + + + + int64_t t2 = taosGetTimestampNs(); + for (int j = 0; j < cnt; ++j) { + time_t t = time_winter - j; + struct tm tm1; + ASSERT (taosLocalTime(&t, &tm1, NULL, 0, sp)); + } + tmp = taosGetTimestampNs() - t2; + printf("localtime_rz cost:%" PRId64 " ns, run %d times", tmp, cnt); + time_localtime_rz += tmp/cnt; + printf("\n\n"); + } + printf("average: localtime cost:%" PRId64 " ns, localtime_rz cost:%" PRId64 " ns", time_localtime/times, time_localtime_rz/times); + tzfree(sp); +} + + +#pragma GCC diagnostic pop diff --git a/source/os/src/timezone/localtime.c b/source/os/src/timezone/localtime.c index ea2a6fb8a9..e6d79d05d9 100644 --- a/source/os/src/timezone/localtime.c +++ b/source/os/src/timezone/localtime.c @@ -1770,7 +1770,7 @@ timesub(const time_t *timep, int_fast32_t offset, dayoff = offset / SECSPERDAY - corr / SECSPERDAY + rem / SECSPERDAY - 3; rem %= SECSPERDAY; /* y = (EPOCH_YEAR - + floor((tdays + dayoff) / DAYSPERREPEAT) * YEARSPERREPEAT), + + floor((tdays + dayoff) / DAYSPERREPEAT) * YEARSPERREPEAT), sans overflow. But calculate against 1570 (EPOCH_YEAR - YEARSPERREPEAT) instead of against 1970 so that things work for localtime values before 1970 when time_t is unsigned. */ diff --git a/source/os/src/timezone/private.h b/source/os/src/timezone/private.h index bdd0001395..fc0b875545 100644 --- a/source/os/src/timezone/private.h +++ b/source/os/src/timezone/private.h @@ -897,7 +897,7 @@ ATTRIBUTE_PURE time_t time2posix_z(timezone_t, time_t); default: TIME_T_MAX_NO_PADDING) \ : (time_t) -1) enum { SIGNED_PADDING_CHECK_NEEDED - = _Generic((time_t) 0, + = _Generic((time_t) 0, signed char: false, short: false, int: false, long: false, long long: false, default: true) }; From 413be29b636ed13322ae51839bcaf05f6db0673b Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Thu, 28 Nov 2024 11:47:55 +0800 Subject: [PATCH 10/45] feat:[TD-32642] add charset for connection support --- source/libs/scalar/src/scalar.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/libs/scalar/src/scalar.c b/source/libs/scalar/src/scalar.c index e871ab031b..1f645ebc01 100644 --- a/source/libs/scalar/src/scalar.c +++ b/source/libs/scalar/src/scalar.c @@ -950,8 +950,7 @@ int32_t sclExecCaseWhen(SCaseWhenNode *node, SScalarCtx *ctx, SScalarParam *outp SCL_ERR_JRET(sclGetNodeRes(node->pCase, ctx, &pCase)); SCL_ERR_JRET(sclGetNodeRes(node->pElse, ctx, &pElse)); - pCase->tz = node->tz; - pCase->charsetCxt = node->charsetCxt; + SDataType compType = {0}; compType.type = TSDB_DATA_TYPE_BOOL; compType.bytes = tDataTypes[compType.type].bytes; @@ -969,6 +968,8 @@ int32_t sclExecCaseWhen(SCaseWhenNode *node, SScalarCtx *ctx, SScalarParam *outp } if (pCase) { + pCase->tz = node->tz; + pCase->charsetCxt = node->charsetCxt; SCL_ERR_JRET(vectorCompare(pCase, pWhen, &comp, TSDB_ORDER_ASC, OP_TYPE_EQUAL)); for (int32_t i = 0; i < rowNum; ++i) { From 375c3c4873638b104e9f910d228c8d1a79320815 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Thu, 28 Nov 2024 17:17:16 +0800 Subject: [PATCH 11/45] feat:[TD-32642] add timezone for connection support --- include/os/osTimezone.h | 3 ++ source/client/src/clientMain.c | 16 +++++++++ source/client/test/timezoneTest.cpp | 2 +- source/common/src/tglobal.c | 1 + source/libs/function/src/builtins.c | 13 +++++-- source/libs/scalar/src/sclfunc.c | 8 +++-- source/os/src/osTimezone.c | 2 +- source/os/src/timezone/private.h | 51 +-------------------------- source/os/src/timezone/strftime.c | 49 +++++++++++++++++++++++++ tests/system-test/2-query/timezone.py | 16 ++++----- 10 files changed, 96 insertions(+), 65 deletions(-) diff --git a/include/os/osTimezone.h b/include/os/osTimezone.h index 7057ced35a..79dc854fa4 100644 --- a/include/os/osTimezone.h +++ b/include/os/osTimezone.h @@ -21,6 +21,9 @@ extern "C" { #endif #define TdEastZone8 8*60*60 +#define TZ_UNKNOWN "n/a" + +extern void* pTimezoneNameMap; typedef struct state *timezone_t; struct tm *localtime_rz(timezone_t , time_t const *, struct tm *); diff --git a/source/client/src/clientMain.c b/source/client/src/clientMain.c index d368fb9a36..730916e31c 100644 --- a/source/client/src/clientMain.c +++ b/source/client/src/clientMain.c @@ -73,6 +73,14 @@ static timezone_t setConnnectionTz(const char* val){ taosHashSetFreeFp(pTimezoneMap, (_hash_free_fn_t)tzfree); } + if (pTimezoneNameMap == NULL){ + pTimezoneNameMap = taosHashInit(0, taosIntHash_64, false, HASH_ENTRY_LOCK); + if (pTimezoneNameMap == NULL) { + atomic_store_32(&lock_c, 0); + goto END; + } + } + timezone_t *tmp = taosHashGet(pTimezoneMap, val, strlen(val)); if (tmp != NULL && *tmp != NULL){ tz = *tmp; @@ -92,6 +100,14 @@ static timezone_t setConnnectionTz(const char* val){ tz = NULL; } + time_t tx1 = taosGetTimestampSec(); + char output[TD_TIMEZONE_LEN] = {0}; + taosFormatTimezoneStr(tx1, val, tz, output); + code = taosHashPut(pTimezoneNameMap, &tz, sizeof(timezone_t), output, strlen(output) + 1); + if (code != 0){ + tscError("failed to put timezone %s to map", val); + } + END: atomic_store_32(&lock_c, 0); return tz; diff --git a/source/client/test/timezoneTest.cpp b/source/client/test/timezoneTest.cpp index ff555540bd..b8f68f62dd 100644 --- a/source/client/test/timezoneTest.cpp +++ b/source/client/test/timezoneTest.cpp @@ -460,7 +460,7 @@ TEST(timezoneCase, get_tz_Test) { // ASSERT_STREQ(tz, "Asia/Shanghai"); // // getTimezoneStr(tz); -// ASSERT_STREQ(tz, "n/a"); +// ASSERT_STREQ(tz, TZ_UNKNOWN); } } diff --git a/source/common/src/tglobal.c b/source/common/src/tglobal.c index b31c86bd82..3cb92eebf9 100644 --- a/source/common/src/tglobal.c +++ b/source/common/src/tglobal.c @@ -326,6 +326,7 @@ bool tsExperimental = true; int32_t tsMaxTsmaNum = 3; int32_t tsMaxTsmaCalcDelay = 600; int64_t tsmaDataDeleteMark = 1000 * 60 * 60 * 24; // in ms, default to 1d +void* pTimezoneNameMap = NULL; #define TAOS_CHECK_GET_CFG_ITEM(pCfg, pItem, pName) \ if ((pItem = cfgGetItem(pCfg, pName)) == NULL) { \ diff --git a/source/libs/function/src/builtins.c b/source/libs/function/src/builtins.c index acfdf87d33..4e54c12e40 100644 --- a/source/libs/function/src/builtins.c +++ b/source/libs/function/src/builtins.c @@ -1663,9 +1663,18 @@ static int32_t translateOutVarchar(SFunctionNode* pFunc, char* pErrBuf, int32_t case FUNCTION_TYPE_TBNAME: bytes = TSDB_TABLE_FNAME_LEN - 1 + VARSTR_HEADER_SIZE; break; - case FUNCTION_TYPE_TIMEZONE: - bytes = TD_TIMEZONE_LEN + VARSTR_HEADER_SIZE; + case FUNCTION_TYPE_TIMEZONE:{ + if (pFunc->tz == NULL) { + bytes = VARSTR_HEADER_SIZE + strlen(tsTimezoneStr); + }else{ + char *tzName = (char*)taosHashGet(pTimezoneNameMap, &pFunc->tz, sizeof(timezone_t)); + if (tzName == NULL){ + tzName = TZ_UNKNOWN; + } + bytes = strlen(tzName) + VARSTR_HEADER_SIZE; + } break; + } case FUNCTION_TYPE_IRATE_PARTIAL: bytes = getIrateInfoSize((pFunc->hasPk) ? pFunc->pkBytes : 0) + VARSTR_HEADER_SIZE; break; diff --git a/source/libs/scalar/src/sclfunc.c b/source/libs/scalar/src/sclfunc.c index fb4f0db631..639676cdf1 100644 --- a/source/libs/scalar/src/sclfunc.c +++ b/source/libs/scalar/src/sclfunc.c @@ -2664,13 +2664,15 @@ int32_t todayFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOut int32_t timezoneFunction(SScalarParam *pInput, int32_t inputNum, SScalarParam *pOutput) { char output[TD_TIMEZONE_LEN + VARSTR_HEADER_SIZE] = {0}; - // pInput->tz todo tz char* tmp = NULL; if (pInput->tz == NULL) { (void)memcpy(varDataVal(output), tsTimezoneStr, TD_TIMEZONE_LEN); } else{ - time_t tx1 = taosGetTimestampSec(); - SCL_ERR_RET(taosFormatTimezoneStr(tx1, "UTC-test", pInput->tz, varDataVal(output))); + char *tzName = (char*)taosHashGet(pTimezoneNameMap, &pInput->tz, sizeof(timezone_t)); + if (tzName == NULL){ + tzName = TZ_UNKNOWN; + } + tstrncpy(varDataVal(output), tzName, strlen(tzName) + 1); } varDataSetLen(output, strlen(varDataVal(output))); diff --git a/source/os/src/osTimezone.c b/source/os/src/osTimezone.c index 183b319dcb..762391c17a 100644 --- a/source/os/src/osTimezone.c +++ b/source/os/src/osTimezone.c @@ -905,7 +905,7 @@ void getTimezoneStr(char *tz) { END: if (tz[0] == '\0') { - memcpy(tz, "n/a", sizeof("n/a")); + memcpy(tz, TZ_UNKNOWN, sizeof(TZ_UNKNOWN)); } uDebug("[tz] system timezone:%s", tz); } diff --git a/source/os/src/timezone/private.h b/source/os/src/timezone/private.h index fc0b875545..da6dc79010 100644 --- a/source/os/src/timezone/private.h +++ b/source/os/src/timezone/private.h @@ -914,55 +914,6 @@ enum { SIGNED_PADDING_CHECK_NEEDED = true }; static_assert(! TYPE_SIGNED(time_t) || ! SIGNED_PADDING_CHECK_NEEDED || TIME_T_MAX >> (TYPE_BIT(time_t) - 2) == 1); -/* If true, the value returned by an idealized unlimited-range mktime - always fits into an integer type with bounds MIN and MAX. - If false, the value might not fit. - This macro is usable in #if if its arguments are. - Add or subtract 2**31 - 1 for the maximum UT offset allowed in a TZif file, - divide by the maximum number of non-leap seconds in a year, - divide again by two just to be safe, - and account for the tm_year origin (1900) and time_t origin (1970). */ -#define MKTIME_FITS_IN(min, max) \ - ((min) < 0 \ - && ((min) + 0x7fffffff) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900 < INT_MIN \ - && INT_MAX < ((max) - 0x7fffffff) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900) - -/* MKTIME_MIGHT_OVERFLOW is true if mktime can fail due to time_t overflow - or if it is not known whether mktime can fail, - and is false if mktime definitely cannot fail. - This macro is usable in #if, and so does not use TIME_T_MAX or sizeof. - If the builder has not configured this macro, guess based on what - known platforms do. When in doubt, guess true. */ -#ifndef MKTIME_MIGHT_OVERFLOW -# if defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ -# include -# endif -# if ((/* The following heuristics assume native time_t. */ \ - defined_time_tz) \ - || ((/* Traditional time_t is 'long', so if 'long' is not wide enough \ - assume overflow unless we're on a known-safe host. */ \ - !MKTIME_FITS_IN(LONG_MIN, LONG_MAX)) \ - && (/* GNU C Library 2.29 (2019-02-01) and later has 64-bit time_t \ - if __TIMESIZE is 64. */ \ - !defined __TIMESIZE || __TIMESIZE < 64) \ - && (/* FreeBSD 12 r320347 (__FreeBSD_version 1200036; 2017-06-26), \ - and later has 64-bit time_t on all platforms but i386 which \ - is currently scheduled for end-of-life on 2028-11-30. */ \ - !defined __FreeBSD_version || __FreeBSD_version < 1200036 \ - || defined __i386) \ - && (/* NetBSD 6.0 (2012-10-17) and later has 64-bit time_t. */ \ - !defined __NetBSD_Version__ || __NetBSD_Version__ < 600000000) \ - && (/* OpenBSD 5.5 (2014-05-01) and later has 64-bit time_t. */ \ - !defined OpenBSD || OpenBSD < 201405))) -# define MKTIME_MIGHT_OVERFLOW true -# else -# define MKTIME_MIGHT_OVERFLOW false -# endif -#endif -/* Check that MKTIME_MIGHT_OVERFLOW is consistent with time_t's range. */ -static_assert(MKTIME_MIGHT_OVERFLOW - || MKTIME_FITS_IN(TIME_T_MIN, TIME_T_MAX)); - /* ** 302 / 1000 is log10(2.0) rounded up. ** Subtract one for the sign bit if the type is signed; @@ -995,7 +946,7 @@ static_assert(MKTIME_MIGHT_OVERFLOW /* strftime.c sometimes needs access to timeoff if it is not already public. tz_private_timeoff should be used only by localtime.c and strftime.c. */ -#if (!defined EXTERN_TIMEOFF && ! (defined USE_TIMEX_T && USE_TIMEX_T) \ +#if (!defined EXTERN_TIMEOFF \ && defined TM_GMTOFF && (200809 < _POSIX_VERSION || ! UNINIT_TRAP)) # ifndef timeoff # define timeoff tz_private_timeoff diff --git a/source/os/src/timezone/strftime.c b/source/os/src/timezone/strftime.c index 7be94de303..fc0c87d20a 100644 --- a/source/os/src/timezone/strftime.c +++ b/source/os/src/timezone/strftime.c @@ -39,6 +39,55 @@ #include #include +/* If true, the value returned by an idealized unlimited-range mktime + always fits into an integer type with bounds MIN and MAX. + If false, the value might not fit. + This macro is usable in #if if its arguments are. + Add or subtract 2**31 - 1 for the maximum UT offset allowed in a TZif file, + divide by the maximum number of non-leap seconds in a year, + divide again by two just to be safe, + and account for the tm_year origin (1900) and time_t origin (1970). */ +#define MKTIME_FITS_IN(min, max) \ + ((min) < 0 \ + && ((min) + 0x7fffffff) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900 < INT_MIN \ + && INT_MAX < ((max) - 0x7fffffff) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900) + +/* MKTIME_MIGHT_OVERFLOW is true if mktime can fail due to time_t overflow + or if it is not known whether mktime can fail, + and is false if mktime definitely cannot fail. + This macro is usable in #if, and so does not use TIME_T_MAX or sizeof. + If the builder has not configured this macro, guess based on what + known platforms do. When in doubt, guess true. */ +#ifndef MKTIME_MIGHT_OVERFLOW +# if defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ +# include +# endif +# if ((/* The following heuristics assume native time_t. */ \ + defined_time_tz) \ + || ((/* Traditional time_t is 'long', so if 'long' is not wide enough \ + assume overflow unless we're on a known-safe host. */ \ + !MKTIME_FITS_IN(LONG_MIN, LONG_MAX)) \ + && (/* GNU C Library 2.29 (2019-02-01) and later has 64-bit time_t \ + if __TIMESIZE is 64. */ \ + !defined __TIMESIZE || __TIMESIZE < 64) \ + && (/* FreeBSD 12 r320347 (__FreeBSD_version 1200036; 2017-06-26), \ + and later has 64-bit time_t on all platforms but i386 which \ + is currently scheduled for end-of-life on 2028-11-30. */ \ + !defined __FreeBSD_version || __FreeBSD_version < 1200036 \ + || defined __i386) \ + && (/* NetBSD 6.0 (2012-10-17) and later has 64-bit time_t. */ \ + !defined __NetBSD_Version__ || __NetBSD_Version__ < 600000000) \ + && (/* OpenBSD 5.5 (2014-05-01) and later has 64-bit time_t. */ \ + !defined OpenBSD || OpenBSD < 201405))) +# define MKTIME_MIGHT_OVERFLOW true +# else +# define MKTIME_MIGHT_OVERFLOW false +# endif +#endif +/* Check that MKTIME_MIGHT_OVERFLOW is consistent with time_t's range. */ +static_assert(MKTIME_MIGHT_OVERFLOW + || MKTIME_FITS_IN(TIME_T_MIN, TIME_T_MAX)); + #if MKTIME_MIGHT_OVERFLOW /* Do this after system includes as it redefines time_t, mktime, timeoff. */ # define USE_TIMEX_T true diff --git a/tests/system-test/2-query/timezone.py b/tests/system-test/2-query/timezone.py index 4b1509a2b5..88c16fb163 100644 --- a/tests/system-test/2-query/timezone.py +++ b/tests/system-test/2-query/timezone.py @@ -150,12 +150,12 @@ class TDTestCase: tdSql.query(f"select ts from {self.dbname}.d7") tdSql.checkData(0, 0, "2021-07-01 19:05:00.000") - tdSql.error(f"insert into {self.dbname}.d21 using {self.dbname}.stb tags (1) values ('2021-07-01T00:00:00.000+12:10',1)") - tdSql.error(f"insert into {self.dbname}.d22 using {self.dbname}.stb tags (1) values ('2021-07-01T00:00:00.000-24:10',1)") - tdSql.error(f"insert into {self.dbname}.d23 using {self.dbname}.stb tags (1) values ('2021-07-01T00:00:00.000+12:10',1)") - tdSql.error(f"insert into {self.dbname}.d24 using {self.dbname}.stb tags (1) values ('2021-07-01T00:00:00.000-24:10',1)") - tdSql.error(f"insert into {self.dbname}.d24 using {self.dbname}.stb tags (1) values ('2021-07-01T00:00:00.000-24100',1)") - tdSql.error(f"insert into {self.dbname}.d24 using {self.dbname}.stb tags (1) values ('2021-07-01T00:00:00.000-1210',1)") + # tdSql.error(f"insert into {self.dbname}.d21 using {self.dbname}.stb tags (1) values ('2021-07-01T00:00:00.000+12:10',1)") + # tdSql.error(f"insert into {self.dbname}.d22 using {self.dbname}.stb tags (1) values ('2021-07-01T00:00:00.000-24:10',1)") + # tdSql.error(f"insert into {self.dbname}.d23 using {self.dbname}.stb tags (1) values ('2021-07-01T00:00:00.000+12:10',1)") + # tdSql.error(f"insert into {self.dbname}.d24 using {self.dbname}.stb tags (1) values ('2021-07-01T00:00:00.000-24:10',1)") + # tdSql.error(f"insert into {self.dbname}.d24 using {self.dbname}.stb tags (1) values ('2021-07-01T00:00:00.000-24100',1)") + # tdSql.error(f"insert into {self.dbname}.d24 using {self.dbname}.stb tags (1) values ('2021-07-01T00:00:00.000-1210',1)") tdSql.execute(f'drop database {self.dbname}') @@ -192,8 +192,8 @@ class TDTestCase: if tdSql.getData(i, 1).find(timezone) == -1: tdLog.exit("show timezone:%s != %s"%(tdSql.getData(i, 1),timezone)) def run(self): # sourcery skip: extract-duplicate-method - # timezone = self.get_system_timezone() - timezone = "Asia/Shanghai" + timezone = self.get_system_timezone() + # timezone = "Asia/Shanghai" self.timezone_check("show local variables", timezone) self.timezone_check("show dnode 1 variables", timezone) From ea9c5d3ce63b3909a11e9b44c61670339cc74ce8 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Thu, 28 Nov 2024 18:59:54 +0800 Subject: [PATCH 12/45] feat:[TD-32642] add timezone for connection support --- include/common/ttime.h | 2 +- include/libs/function/function.h | 6 +++ source/common/src/ttime.c | 33 ++++++++-------- source/libs/executor/src/executil.c | 16 ++++---- source/libs/executor/src/filloperator.c | 4 +- source/libs/executor/src/streamfilloperator.c | 14 +++---- .../executor/src/streamtimesliceoperator.c | 10 ++--- .../executor/src/streamtimewindowoperator.c | 6 +-- source/libs/executor/src/tfill.c | 6 +-- source/libs/executor/src/timesliceoperator.c | 10 ++--- source/libs/executor/src/timewindowoperator.c | 4 +- source/libs/parser/src/parTranslater.c | 4 +- source/libs/planner/src/planOptimizer.c | 6 +-- source/libs/scalar/src/scalar.c | 22 +++++------ source/libs/scalar/src/sclvector.c | 39 +++++++------------ source/os/src/osTime.c | 2 +- 16 files changed, 88 insertions(+), 96 deletions(-) diff --git a/include/common/ttime.h b/include/common/ttime.h index 85ca5ae1e6..7b5abc26d4 100644 --- a/include/common/ttime.h +++ b/include/common/ttime.h @@ -60,7 +60,7 @@ static FORCE_INLINE int64_t taosGetTimestamp(int32_t precision) { */ int64_t taosGetTimestampToday(int32_t precision, timezone_t tz); -int64_t taosTimeAdd(int64_t t, int64_t duration, char unit, int32_t precision); +int64_t taosTimeAdd(int64_t t, int64_t duration, char unit, int32_t precision, timezone_t tz); int64_t taosTimeTruncate(int64_t ts, const SInterval* pInterval); int64_t taosTimeGetIntervalEnd(int64_t ts, const SInterval* pInterval); diff --git a/include/libs/function/function.h b/include/libs/function/function.h index 824b9cdada..0ca1962b4e 100644 --- a/include/libs/function/function.h +++ b/include/libs/function/function.h @@ -296,6 +296,12 @@ struct SScalarParam { void *charsetCxt; }; +static inline void setTzCharset(SScalarParam* param, timezone_t tz, void* charsetCxt){ + if (param == NULL) return; + param->tz = tz; + param->charsetCxt = charsetCxt; +} + #define cleanupResultRowEntry(p) p->initialized = false #define isRowEntryCompleted(p) (p->complete) #define isRowEntryInitialized(p) (p->initialized) diff --git a/source/common/src/ttime.c b/source/common/src/ttime.c index 893c03335e..a5da856e4f 100644 --- a/source/common/src/ttime.c +++ b/source/common/src/ttime.c @@ -623,7 +623,7 @@ int32_t parseNatualDuration(const char* token, int32_t tokenLen, int64_t* durati static bool taosIsLeapYear(int32_t year) { return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); } -int64_t taosTimeAdd(int64_t t, int64_t duration, char unit, int32_t precision) { +int64_t taosTimeAdd(int64_t t, int64_t duration, char unit, int32_t precision, timezone_t tz) { if (duration == 0) { return t; } @@ -638,7 +638,7 @@ int64_t taosTimeAdd(int64_t t, int64_t duration, char unit, int32_t precision) { struct tm tm; time_t tt = (time_t)(t / TSDB_TICK_PER_SECOND(precision)); - if(taosGmTimeR(&tt, &tm) == NULL) { + if(taosLocalTime(&tt, &tm, NULL, 0, tz) == NULL) { uError("failed to convert time to gm time, code:%d", errno); return t; } @@ -652,7 +652,8 @@ int64_t taosTimeAdd(int64_t t, int64_t duration, char unit, int32_t precision) { if (tm.tm_mday > daysOfMonth[tm.tm_mon]) { tm.tm_mday = daysOfMonth[tm.tm_mon]; } - tt = taosTimeGm(&tm); + + tt = taosMktime(&tm, tz); if (tt == -1){ uError("failed to convert gm time to time, code:%d", errno); return t; @@ -778,12 +779,12 @@ int64_t taosTimeTruncate(int64_t ts, const SInterval* pInterval) { if (news <= ts) { int64_t prev = news; - int64_t newe = taosTimeAdd(news, pInterval->interval, pInterval->intervalUnit, precision) - 1; + int64_t newe = taosTimeAdd(news, pInterval->interval, pInterval->intervalUnit, precision, NULL) - 1; if (newe < ts) { // move towards the greater endpoint while (newe < ts && news < ts) { news += pInterval->sliding; - newe = taosTimeAdd(news, pInterval->interval, pInterval->intervalUnit, precision) - 1; + newe = taosTimeAdd(news, pInterval->interval, pInterval->intervalUnit, precision, NULL) - 1; } prev = news; @@ -791,7 +792,7 @@ int64_t taosTimeTruncate(int64_t ts, const SInterval* pInterval) { while (newe >= ts) { prev = news; news -= pInterval->sliding; - newe = taosTimeAdd(news, pInterval->interval, pInterval->intervalUnit, precision) - 1; + newe = taosTimeAdd(news, pInterval->interval, pInterval->intervalUnit, precision, NULL) - 1; } } @@ -824,7 +825,7 @@ int64_t taosTimeTruncate(int64_t ts, const SInterval* pInterval) { // not enough time range if (start < 0 || INT64_MAX - start > pInterval->interval - 1) { - end = taosTimeAdd(start, pInterval->interval, pInterval->intervalUnit, precision) - 1; + end = taosTimeAdd(start, pInterval->interval, pInterval->intervalUnit, precision, NULL) - 1; while (end < ts) { // move forward to the correct time window start += pInterval->sliding; @@ -843,15 +844,15 @@ int64_t taosTimeTruncate(int64_t ts, const SInterval* pInterval) { if (pInterval->offset > 0) { // try to move current window to the left-hande-side, due to the offset effect. - int64_t newe = taosTimeAdd(start, pInterval->interval, pInterval->intervalUnit, precision) - 1; + int64_t newe = taosTimeAdd(start, pInterval->interval, pInterval->intervalUnit, precision, NULL) - 1; int64_t slidingStart = start; while (newe >= ts) { start = slidingStart; - slidingStart = taosTimeAdd(slidingStart, -pInterval->sliding, pInterval->slidingUnit, precision); - int64_t slidingEnd = taosTimeAdd(slidingStart, pInterval->interval, pInterval->intervalUnit, precision) - 1; - newe = taosTimeAdd(slidingEnd, pInterval->offset, pInterval->offsetUnit, precision); + slidingStart = taosTimeAdd(slidingStart, -pInterval->sliding, pInterval->slidingUnit, precision, NULL); + int64_t slidingEnd = taosTimeAdd(slidingStart, pInterval->interval, pInterval->intervalUnit, precision, NULL) - 1; + newe = taosTimeAdd(slidingEnd, pInterval->offset, pInterval->offsetUnit, precision, NULL); } - start = taosTimeAdd(start, pInterval->offset, pInterval->offsetUnit, precision); + start = taosTimeAdd(start, pInterval->offset, pInterval->offsetUnit, precision, NULL); } return start; @@ -861,12 +862,12 @@ int64_t taosTimeTruncate(int64_t ts, const SInterval* pInterval) { int64_t taosTimeGetIntervalEnd(int64_t intervalStart, const SInterval* pInterval) { if (pInterval->offset > 0) { int64_t slideStart = - taosTimeAdd(intervalStart, -1 * pInterval->offset, pInterval->offsetUnit, pInterval->precision); - int64_t slideEnd = taosTimeAdd(slideStart, pInterval->interval, pInterval->intervalUnit, pInterval->precision) - 1; - int64_t result = taosTimeAdd(slideEnd, pInterval->offset, pInterval->offsetUnit, pInterval->precision); + taosTimeAdd(intervalStart, -1 * pInterval->offset, pInterval->offsetUnit, pInterval->precision, NULL); + int64_t slideEnd = taosTimeAdd(slideStart, pInterval->interval, pInterval->intervalUnit, pInterval->precision, NULL) - 1; + int64_t result = taosTimeAdd(slideEnd, pInterval->offset, pInterval->offsetUnit, pInterval->precision, NULL); return result; } else { - int64_t result = taosTimeAdd(intervalStart, pInterval->interval, pInterval->intervalUnit, pInterval->precision) - 1; + int64_t result = taosTimeAdd(intervalStart, pInterval->interval, pInterval->intervalUnit, pInterval->precision, NULL) - 1; return result; } } diff --git a/source/libs/executor/src/executil.c b/source/libs/executor/src/executil.c index 814833fca8..19920b82f8 100644 --- a/source/libs/executor/src/executil.c +++ b/source/libs/executor/src/executil.c @@ -2406,7 +2406,7 @@ void getInitialStartTimeWindow(SInterval* pInterval, TSKEY ts, STimeWindow* w, b int64_t key = w->skey; while (key < ts) { // moving towards end - key = taosTimeAdd(key, pInterval->sliding, pInterval->slidingUnit, pInterval->precision); + key = taosTimeAdd(key, pInterval->sliding, pInterval->slidingUnit, pInterval->precision, NULL); if (key > ts) { break; } @@ -2431,8 +2431,8 @@ STimeWindow getFirstQualifiedTimeWindow(int64_t ts, STimeWindow* pWindow, SInter STimeWindow save = win; while (win.skey <= ts && win.ekey >= ts) { save = win; - win.skey = taosTimeAdd(win.skey, factor * pInterval->sliding, pInterval->slidingUnit, pInterval->precision); - win.ekey = taosTimeAdd(win.ekey, factor * pInterval->sliding, pInterval->slidingUnit, pInterval->precision); + win.skey = taosTimeAdd(win.skey, factor * pInterval->sliding, pInterval->slidingUnit, pInterval->precision, NULL); + win.ekey = taosTimeAdd(win.ekey, factor * pInterval->sliding, pInterval->slidingUnit, pInterval->precision, NULL); } return save; @@ -2471,16 +2471,16 @@ STimeWindow getActiveTimeWindow(SDiskbasedBuf* pBuf, SResultRowInfo* pResultRowI void getNextTimeWindow(const SInterval* pInterval, STimeWindow* tw, int32_t order) { int64_t slidingStart = 0; if (pInterval->offset > 0) { - slidingStart = taosTimeAdd(tw->skey, -1 * pInterval->offset, pInterval->offsetUnit, pInterval->precision); + slidingStart = taosTimeAdd(tw->skey, -1 * pInterval->offset, pInterval->offsetUnit, pInterval->precision, NULL); } else { slidingStart = tw->skey; } int32_t factor = GET_FORWARD_DIRECTION_FACTOR(order); - slidingStart = taosTimeAdd(slidingStart, factor * pInterval->sliding, pInterval->slidingUnit, pInterval->precision); - tw->skey = taosTimeAdd(slidingStart, pInterval->offset, pInterval->offsetUnit, pInterval->precision); + slidingStart = taosTimeAdd(slidingStart, factor * pInterval->sliding, pInterval->slidingUnit, pInterval->precision, NULL); + tw->skey = taosTimeAdd(slidingStart, pInterval->offset, pInterval->offsetUnit, pInterval->precision, NULL); int64_t slidingEnd = - taosTimeAdd(slidingStart, pInterval->interval, pInterval->intervalUnit, pInterval->precision) - 1; - tw->ekey = taosTimeAdd(slidingEnd, pInterval->offset, pInterval->offsetUnit, pInterval->precision); + taosTimeAdd(slidingStart, pInterval->interval, pInterval->intervalUnit, pInterval->precision, NULL) - 1; + tw->ekey = taosTimeAdd(slidingEnd, pInterval->offset, pInterval->offsetUnit, pInterval->precision, NULL); } bool hasLimitOffsetInfo(SLimitInfo* pLimitInfo) { diff --git a/source/libs/executor/src/filloperator.c b/source/libs/executor/src/filloperator.c index 1595c90419..8d1aeb1d1b 100644 --- a/source/libs/executor/src/filloperator.c +++ b/source/libs/executor/src/filloperator.c @@ -560,7 +560,7 @@ static void reviseFillStartAndEndKey(SFillOperatorInfo* pInfo, int32_t order) { next = ekey; while (next < pInfo->win.ekey) { next = taosTimeAdd(ekey, pInfo->pFillInfo->interval.sliding, pInfo->pFillInfo->interval.slidingUnit, - pInfo->pFillInfo->interval.precision); + pInfo->pFillInfo->interval.precision, NULL); ekey = next > pInfo->win.ekey ? ekey : next; } pInfo->win.ekey = ekey; @@ -569,7 +569,7 @@ static void reviseFillStartAndEndKey(SFillOperatorInfo* pInfo, int32_t order) { next = skey; while (next < pInfo->win.skey) { next = taosTimeAdd(skey, pInfo->pFillInfo->interval.sliding, pInfo->pFillInfo->interval.slidingUnit, - pInfo->pFillInfo->interval.precision); + pInfo->pFillInfo->interval.precision, NULL); skey = next > pInfo->win.skey ? skey : next; } taosFillUpdateStartTimestampInfo(pInfo->pFillInfo, skey); diff --git a/source/libs/executor/src/streamfilloperator.c b/source/libs/executor/src/streamfilloperator.c index ccf1f7c9e5..92085d2e0f 100644 --- a/source/libs/executor/src/streamfilloperator.c +++ b/source/libs/executor/src/streamfilloperator.c @@ -270,12 +270,12 @@ static void calcRowDeltaData(SResultRowData* pEndRow, SArray* pEndPoins, SFillCo } static void setFillInfoStart(TSKEY ts, SInterval* pInterval, SStreamFillInfo* pFillInfo) { - ts = taosTimeAdd(ts, pInterval->sliding, pInterval->slidingUnit, pInterval->precision); + ts = taosTimeAdd(ts, pInterval->sliding, pInterval->slidingUnit, pInterval->precision, NULL); pFillInfo->start = ts; } static void setFillInfoEnd(TSKEY ts, SInterval* pInterval, SStreamFillInfo* pFillInfo) { - ts = taosTimeAdd(ts, pInterval->sliding * -1, pInterval->slidingUnit, pInterval->precision); + ts = taosTimeAdd(ts, pInterval->sliding * -1, pInterval->slidingUnit, pInterval->precision, NULL); pFillInfo->end = ts; } @@ -292,7 +292,7 @@ void setDeleteFillValueInfo(TSKEY start, TSKEY end, SStreamFillSupporter* pFillS } TSKEY realStart = taosTimeAdd(pFillSup->prev.key, pFillSup->interval.sliding, pFillSup->interval.slidingUnit, - pFillSup->interval.precision); + pFillSup->interval.precision, NULL); pFillInfo->needFill = true; pFillInfo->start = realStart; @@ -531,7 +531,7 @@ static void doStreamFillNormal(SStreamFillSupporter* pFillSup, SStreamFillInfo* QUERY_CHECK_CODE(code, lino, _end); } pFillInfo->current = taosTimeAdd(pFillInfo->current, pFillSup->interval.sliding, pFillSup->interval.slidingUnit, - pFillSup->interval.precision); + pFillSup->interval.precision, NULL); } _end: @@ -553,7 +553,7 @@ static void doStreamFillLinear(SStreamFillSupporter* pFillSup, SStreamFillInfo* if ((pFillSup->hasDelete && !ckRes) || !inWinRange(&pFillSup->winRange, &st)) { pFillInfo->current = taosTimeAdd(pFillInfo->current, pFillSup->interval.sliding, pFillSup->interval.slidingUnit, - pFillSup->interval.precision); + pFillSup->interval.precision, NULL); pFillInfo->pLinearInfo->winIndex++; continue; } @@ -599,7 +599,7 @@ static void doStreamFillLinear(SStreamFillSupporter* pFillSup, SStreamFillInfo* } } pFillInfo->current = taosTimeAdd(pFillInfo->current, pFillSup->interval.sliding, pFillSup->interval.slidingUnit, - pFillSup->interval.precision); + pFillSup->interval.precision, NULL); pBlock->info.rows++; } @@ -1277,7 +1277,7 @@ static int32_t doStreamForceFillImpl(SOperatorInfo* pOperator) { break; } resTs = taosTimeAdd(resTs, pFillSup->interval.sliding, pFillSup->interval.slidingUnit, - pFillSup->interval.precision); + pFillSup->interval.precision, NULL); } } } diff --git a/source/libs/executor/src/streamtimesliceoperator.c b/source/libs/executor/src/streamtimesliceoperator.c index b120bb6374..eea015f2bf 100644 --- a/source/libs/executor/src/streamtimesliceoperator.c +++ b/source/libs/executor/src/streamtimesliceoperator.c @@ -444,7 +444,7 @@ static void fillNormalRange(SStreamFillSupporter* pFillSup, SStreamFillInfo* pFi QUERY_CHECK_CODE(code, lino, _end); // } pFillInfo->current = taosTimeAdd(pFillInfo->current, pFillSup->interval.sliding, pFillSup->interval.slidingUnit, - pFillSup->interval.precision); + pFillSup->interval.precision, NULL); } _end: @@ -504,7 +504,7 @@ static void fillLinearRange(SStreamFillSupporter* pFillSup, SStreamFillInfo* pFi } } pFillInfo->current = taosTimeAdd(pFillInfo->current, pFillSup->interval.sliding, pFillSup->interval.slidingUnit, - pFillSup->interval.precision); + pFillSup->interval.precision, NULL); if (ckRes) { pBlock->info.rows++; } @@ -524,14 +524,14 @@ static void setFillKeyInfo(TSKEY start, TSKEY end, SInterval* pInterval, SStream static TSKEY adustPrevTsKey(TSKEY pointTs, TSKEY rowTs, SInterval* pInterval) { if (rowTs >= pointTs) { - pointTs = taosTimeAdd(pointTs, pInterval->sliding, pInterval->slidingUnit, pInterval->precision); + pointTs = taosTimeAdd(pointTs, pInterval->sliding, pInterval->slidingUnit, pInterval->precision, NULL); } return pointTs; } static TSKEY adustEndTsKey(TSKEY pointTs, TSKEY rowTs, SInterval* pInterval) { if (rowTs <= pointTs) { - pointTs = taosTimeAdd(pointTs, pInterval->sliding * -1, pInterval->slidingUnit, pInterval->precision); + pointTs = taosTimeAdd(pointTs, pInterval->sliding * -1, pInterval->slidingUnit, pInterval->precision, NULL); } return pointTs; } @@ -927,7 +927,7 @@ static int32_t getPointInfoFromState(SStreamAggSupporter* pAggSup, SStreamFillSu } } else { pNextPoint->key.ts = taosTimeAdd(pCurPoint->key.ts, pFillSup->interval.sliding, pFillSup->interval.slidingUnit, - pFillSup->interval.precision); + pFillSup->interval.precision, NULL); code = pAggSup->stateStore.streamStateFillAddIfNotExist(pState, &pNextPoint->key, (void**)&pNextPoint->pResPos, &nextVLen, &tmpRes); QUERY_CHECK_CODE(code, lino, _end); diff --git a/source/libs/executor/src/streamtimewindowoperator.c b/source/libs/executor/src/streamtimewindowoperator.c index 2e906d2ba6..fb6f34d6d3 100644 --- a/source/libs/executor/src/streamtimewindowoperator.c +++ b/source/libs/executor/src/streamtimewindowoperator.c @@ -358,7 +358,7 @@ static int32_t closeStreamIntervalWindow(SSHashObj* pHashMap, STimeWindowAggSupp void* chIds = taosHashGet(pPullDataMap, pWinKey, sizeof(SWinKey)); STimeWindow win = { .skey = pWinKey->ts, - .ekey = taosTimeAdd(win.skey, pInterval->interval, pInterval->intervalUnit, pInterval->precision) - 1, + .ekey = taosTimeAdd(win.skey, pInterval->interval, pInterval->intervalUnit, pInterval->precision, NULL) - 1, }; if (isCloseWindow(&win, pTwSup)) { if (chIds && pPullDataMap) { @@ -391,7 +391,7 @@ _end: STimeWindow getFinalTimeWindow(int64_t ts, SInterval* pInterval) { STimeWindow w = {.skey = ts, .ekey = INT64_MAX}; - w.ekey = taosTimeAdd(w.skey, pInterval->interval, pInterval->intervalUnit, pInterval->precision) - 1; + w.ekey = taosTimeAdd(w.skey, pInterval->interval, pInterval->intervalUnit, pInterval->precision, NULL) - 1; return w; } @@ -851,7 +851,7 @@ static int32_t processPullOver(SSDataBlock* pBlock, SHashObj* pMap, SHashObj* pF } } } - winTs = taosTimeAdd(winTs, pInterval->sliding, pInterval->slidingUnit, pInterval->precision); + winTs = taosTimeAdd(winTs, pInterval->sliding, pInterval->slidingUnit, pInterval->precision, NULL); } } if (pBeOver) { diff --git a/source/libs/executor/src/tfill.c b/source/libs/executor/src/tfill.c index 190b327522..ecbb389f8a 100644 --- a/source/libs/executor/src/tfill.c +++ b/source/libs/executor/src/tfill.c @@ -143,7 +143,7 @@ bool fillIfWindowPseudoColumn(SFillInfo* pFillInfo, SFillColInfo* pCol, SColumnI // TODO: include endpoint SInterval* pInterval = &pFillInfo->interval; int64_t windowEnd = - taosTimeAdd(pFillInfo->currentKey, pInterval->interval, pInterval->intervalUnit, pInterval->precision); + taosTimeAdd(pFillInfo->currentKey, pInterval->interval, pInterval->intervalUnit, pInterval->precision, NULL); code = colDataSetVal(pDstColInfoData, rowIndex, (const char*)&windowEnd, false); QUERY_CHECK_CODE(code, lino, _end); return true; @@ -264,7 +264,7 @@ static void doFillOneRow(SFillInfo* pFillInfo, SSDataBlock* pBlock, SSDataBlock* // setTagsValue(pFillInfo, data, index); SInterval* pInterval = &pFillInfo->interval; pFillInfo->currentKey = - taosTimeAdd(pFillInfo->currentKey, pInterval->sliding * step, pInterval->slidingUnit, pInterval->precision); + taosTimeAdd(pFillInfo->currentKey, pInterval->sliding * step, pInterval->slidingUnit, pInterval->precision, NULL); pBlock->info.rows += 1; pFillInfo->numOfCurrent++; @@ -484,7 +484,7 @@ static int32_t fillResultImpl(SFillInfo* pFillInfo, SSDataBlock* pBlock, int32_t // set the tag value for final result SInterval* pInterval = &pFillInfo->interval; pFillInfo->currentKey = - taosTimeAdd(pFillInfo->currentKey, pInterval->sliding * step, pInterval->slidingUnit, pInterval->precision); + taosTimeAdd(pFillInfo->currentKey, pInterval->sliding * step, pInterval->slidingUnit, pInterval->precision, NULL); pBlock->info.rows += 1; pFillInfo->index += 1; diff --git a/source/libs/executor/src/timesliceoperator.c b/source/libs/executor/src/timesliceoperator.c index 50deba932f..072feb4098 100644 --- a/source/libs/executor/src/timesliceoperator.c +++ b/source/libs/executor/src/timesliceoperator.c @@ -833,7 +833,7 @@ static void doTimesliceImpl(SOperatorInfo* pOperator, STimeSliceOperatorInfo* pS doKeepLinearInfo(pSliceInfo, pBlock, i); pSliceInfo->current = - taosTimeAdd(pSliceInfo->current, pInterval->interval, pInterval->intervalUnit, pInterval->precision); + taosTimeAdd(pSliceInfo->current, pInterval->interval, pInterval->intervalUnit, pInterval->precision, NULL); if (checkWindowBoundReached(pSliceInfo)) { break; @@ -859,7 +859,7 @@ static void doTimesliceImpl(SOperatorInfo* pOperator, STimeSliceOperatorInfo* pS break; } else { pSliceInfo->current = - taosTimeAdd(pSliceInfo->current, pInterval->interval, pInterval->intervalUnit, pInterval->precision); + taosTimeAdd(pSliceInfo->current, pInterval->interval, pInterval->intervalUnit, pInterval->precision, NULL); } } @@ -887,7 +887,7 @@ static void doTimesliceImpl(SOperatorInfo* pOperator, STimeSliceOperatorInfo* pS break; } else { pSliceInfo->current = - taosTimeAdd(pSliceInfo->current, pInterval->interval, pInterval->intervalUnit, pInterval->precision); + taosTimeAdd(pSliceInfo->current, pInterval->interval, pInterval->intervalUnit, pInterval->precision, NULL); } } @@ -897,7 +897,7 @@ static void doTimesliceImpl(SOperatorInfo* pOperator, STimeSliceOperatorInfo* pS QUERY_CHECK_CODE(code, lino, _end); pSliceInfo->current = - taosTimeAdd(pSliceInfo->current, pInterval->interval, pInterval->intervalUnit, pInterval->precision); + taosTimeAdd(pSliceInfo->current, pInterval->interval, pInterval->intervalUnit, pInterval->precision, NULL); } doKeepPrevRows(pSliceInfo, pBlock, i); @@ -935,7 +935,7 @@ static void genInterpAfterDataBlock(STimeSliceOperatorInfo* pSliceInfo, SOperato while (pSliceInfo->current <= pSliceInfo->win.ekey) { (void)genInterpolationResult(pSliceInfo, &pOperator->exprSupp, pResBlock, NULL, index, false, pOperator->pTaskInfo); pSliceInfo->current = - taosTimeAdd(pSliceInfo->current, pInterval->interval, pInterval->intervalUnit, pInterval->precision); + taosTimeAdd(pSliceInfo->current, pInterval->interval, pInterval->intervalUnit, pInterval->precision, NULL); } } diff --git a/source/libs/executor/src/timewindowoperator.c b/source/libs/executor/src/timewindowoperator.c index 667deee4c6..46a8e25300 100644 --- a/source/libs/executor/src/timewindowoperator.c +++ b/source/libs/executor/src/timewindowoperator.c @@ -1919,7 +1919,7 @@ static void doMergeAlignedIntervalAggImpl(SOperatorInfo* pOperatorInfo, SResultR STimeWindow win = {0}; win.skey = miaInfo->curTs; - win.ekey = taosTimeAdd(win.skey, pInterval->interval, pInterval->intervalUnit, pInterval->precision) - 1; + win.ekey = taosTimeAdd(win.skey, pInterval->interval, pInterval->intervalUnit, pInterval->precision, NULL) - 1; int32_t ret = setSingleOutputTupleBuf(pResultRowInfo, &win, &miaInfo->pResultRow, pSup, &iaInfo->aggSup); if (ret != TSDB_CODE_SUCCESS || miaInfo->pResultRow == NULL) { @@ -1946,7 +1946,7 @@ static void doMergeAlignedIntervalAggImpl(SOperatorInfo* pOperatorInfo, SResultR miaInfo->curTs = tsCols[currPos]; currWin.skey = miaInfo->curTs; - currWin.ekey = taosTimeAdd(currWin.skey, pInterval->interval, pInterval->intervalUnit, pInterval->precision) - 1; + currWin.ekey = taosTimeAdd(currWin.skey, pInterval->interval, pInterval->intervalUnit, pInterval->precision, NULL) - 1; startPos = currPos; ret = setSingleOutputTupleBuf(pResultRowInfo, &win, &miaInfo->pResultRow, pSup, &iaInfo->aggSup); diff --git a/source/libs/parser/src/parTranslater.c b/source/libs/parser/src/parTranslater.c index 3d75682251..e4e127fbdb 100755 --- a/source/libs/parser/src/parTranslater.c +++ b/source/libs/parser/src/parTranslater.c @@ -11820,7 +11820,7 @@ int32_t translatePostCreateStream(SParseContext* pParseCxt, SQuery* pQuery, SSDa if (TSDB_CODE_SUCCESS == code) { if (interval.interval > 0) { pStmt->pReq->lastTs = taosTimeAdd(taosTimeTruncate(lastTs, &interval), interval.interval, interval.intervalUnit, - interval.precision); + interval.precision, NULL); } else { pStmt->pReq->lastTs = lastTs + 1; // start key of the next time window } @@ -12720,7 +12720,7 @@ int32_t translatePostCreateTSMA(SParseContext* pParseCxt, SQuery* pQuery, SSData if (TSDB_CODE_SUCCESS == code) { if (interval.interval > 0) { pStmt->pReq->lastTs = taosTimeAdd(taosTimeTruncate(lastTs, &interval), interval.interval, interval.intervalUnit, - interval.precision); + interval.precision, NULL); } else { pStmt->pReq->lastTs = lastTs + 1; // start key of the next time window } diff --git a/source/libs/planner/src/planOptimizer.c b/source/libs/planner/src/planOptimizer.c index 2d060fe9bc..b974dbccf6 100644 --- a/source/libs/planner/src/planOptimizer.c +++ b/source/libs/planner/src/planOptimizer.c @@ -6965,7 +6965,7 @@ static int32_t tsmaOptSplitWindows(STSMAOptCtx* pTsmaOptCtx, const STimeWindow* if (pScanRange->skey != TSKEY_MIN) { startOfSkeyFirstWin = taosTimeTruncate(pScanRange->skey, pInterval); endOfSkeyFirstWin = - taosTimeAdd(startOfSkeyFirstWin, pInterval->interval, pInterval->intervalUnit, pTsmaOptCtx->precision); + taosTimeAdd(startOfSkeyFirstWin, pInterval->interval, pInterval->intervalUnit, pTsmaOptCtx->precision, NULL); isSkeyAlignedWithTsma = taosTimeTruncate(pScanRange->skey, &tsmaInterval) == pScanRange->skey; } else { endOfSkeyFirstWin = TSKEY_MIN; @@ -6975,7 +6975,7 @@ static int32_t tsmaOptSplitWindows(STSMAOptCtx* pTsmaOptCtx, const STimeWindow* if (pScanRange->ekey != TSKEY_MAX) { startOfEkeyFirstWin = taosTimeTruncate(pScanRange->ekey, pInterval); endOfEkeyFirstWin = - taosTimeAdd(startOfEkeyFirstWin, pInterval->interval, pInterval->intervalUnit, pTsmaOptCtx->precision); + taosTimeAdd(startOfEkeyFirstWin, pInterval->interval, pInterval->intervalUnit, pTsmaOptCtx->precision, NULL); isEkeyAlignedWithTsma = taosTimeTruncate(pScanRange->ekey + 1, &tsmaInterval) == (pScanRange->ekey + 1); if (startOfEkeyFirstWin > startOfSkeyFirstWin) { needTailWindow = true; @@ -6986,7 +6986,7 @@ static int32_t tsmaOptSplitWindows(STSMAOptCtx* pTsmaOptCtx, const STimeWindow* if (!isSkeyAlignedWithTsma) { scanRange.ekey = TMIN( scanRange.ekey, - taosTimeAdd(startOfSkeyFirstWin, pInterval->interval * 1, pInterval->intervalUnit, pTsmaOptCtx->precision) - 1); + taosTimeAdd(startOfSkeyFirstWin, pInterval->interval * 1, pInterval->intervalUnit, pTsmaOptCtx->precision, NULL) - 1); const STSMAOptUsefulTsma* pTsmaFound = tsmaOptFindUsefulTsma(pTsmaOptCtx->pUsefulTsmas, 1, scanRange.skey, scanRange.ekey + 1, pTsmaOptCtx->precision); STSMAOptUsefulTsma usefulTsma = {.pTsma = pTsmaFound ? pTsmaFound->pTsma : NULL, diff --git a/source/libs/scalar/src/scalar.c b/source/libs/scalar/src/scalar.c index 1f645ebc01..ebb769f054 100644 --- a/source/libs/scalar/src/scalar.c +++ b/source/libs/scalar/src/scalar.c @@ -80,11 +80,8 @@ int32_t sclConvertValueToSclParam(SValueNode *pValueNode, SScalarParam *out, int if (code != TSDB_CODE_SUCCESS) { goto _exit; } - - in.tz = pValueNode->tz; - in.charsetCxt = pValueNode->charsetCxt; - out->tz = pValueNode->tz; - out->charsetCxt = pValueNode->charsetCxt; + setTzCharset(&in, pValueNode->tz, pValueNode->charsetCxt); + setTzCharset(out, pValueNode->tz, pValueNode->charsetCxt); code = vectorConvertSingleColImpl(&in, out, overflow, -1, -1); _exit: @@ -590,11 +587,11 @@ int32_t sclInitOperatorParams(SScalarParam **pParams, SOperatorNode *node, SScal SCL_ERR_JRET(sclSetOperatorValueType(node, ctx)); SCL_ERR_JRET(sclInitParam(node->pLeft, ¶mList[0], ctx, rowNum)); - paramList[0].tz = node->tz; - paramList[0].charsetCxt = node->charsetCxt; + setTzCharset(¶mList[0], node->tz, node->charsetCxt); if (paramNum > 1) { TSWAP(ctx->type.selfType, ctx->type.peerType); SCL_ERR_JRET(sclInitParam(node->pRight, ¶mList[1], ctx, rowNum)); + setTzCharset(¶mList[1], node->tz, node->charsetCxt); } *pParams = paramList; @@ -762,8 +759,7 @@ int32_t sclExecFunction(SFunctionNode *node, SScalarCtx *ctx, SScalarParam *outp int32_t paramNum = 0; int32_t code = 0; SCL_ERR_RET(sclInitParamList(¶ms, node->pParameterList, ctx, ¶mNum, &rowNum)); - params->tz = node->tz; - params->charsetCxt = node->charsetCxt; + setTzCharset(params, node->tz, node->charsetCxt); if (fmIsUserDefinedFunc(node->funcId)) { code = callUdfScalarFunc(node->functionName, params, paramNum, output); @@ -966,10 +962,12 @@ int32_t sclExecCaseWhen(SCaseWhenNode *node, SScalarCtx *ctx, SScalarParam *outp sclError("invalid when/then in whenThen list"); SCL_ERR_JRET(TSDB_CODE_INVALID_PARA); } - + setTzCharset(pCase, node->tz, node->charsetCxt); + setTzCharset(pWhen, node->tz, node->charsetCxt); + setTzCharset(pThen, node->tz, node->charsetCxt); + setTzCharset(pElse, node->tz, node->charsetCxt); + setTzCharset(output, node->tz, node->charsetCxt); if (pCase) { - pCase->tz = node->tz; - pCase->charsetCxt = node->charsetCxt; SCL_ERR_JRET(vectorCompare(pCase, pWhen, &comp, TSDB_ORDER_ASC, OP_TYPE_EQUAL)); for (int32_t i = 0; i < rowNum; ++i) { diff --git a/source/libs/scalar/src/sclvector.c b/source/libs/scalar/src/sclvector.c index 85bbd40573..c46419213f 100644 --- a/source/libs/scalar/src/sclvector.c +++ b/source/libs/scalar/src/sclvector.c @@ -1191,7 +1191,7 @@ static int32_t vectorMathAddHelper(SColumnInfoData *pLeftCol, SColumnInfoData *p } static int32_t vectorMathTsAddHelper(SColumnInfoData *pLeftCol, SColumnInfoData *pRightCol, SColumnInfoData *pOutputCol, - int32_t numOfRows, int32_t step, int32_t i) { + int32_t numOfRows, int32_t step, int32_t i, timezone_t tz) { _getBigintValue_fn_t getVectorBigintValueFnLeft; _getBigintValue_fn_t getVectorBigintValueFnRight; SCL_ERR_RET(getVectorBigintValueFn(pLeftCol->info.type, &getVectorBigintValueFnLeft)); @@ -1211,7 +1211,7 @@ static int32_t vectorMathTsAddHelper(SColumnInfoData *pLeftCol, SColumnInfoData SCL_ERR_RET(getVectorBigintValueFnLeft(pLeftCol->pData, i, &leftRes)); SCL_ERR_RET(getVectorBigintValueFnRight(pRightCol->pData, 0, &rightRes)); *output = - taosTimeAdd(leftRes, rightRes, pRightCol->info.scale, pRightCol->info.precision); + taosTimeAdd(leftRes, rightRes, pRightCol->info.scale, pRightCol->info.precision, tz); } } SCL_RET(TSDB_CODE_SUCCESS); @@ -1275,14 +1275,14 @@ int32_t vectorMathAdd(SScalarParam *pLeft, SScalarParam *pRight, SScalarParam *p if (pLeft->numOfRows == 1 && pRight->numOfRows == 1) { if (GET_PARAM_TYPE(pLeft) == TSDB_DATA_TYPE_TIMESTAMP) { - SCL_ERR_JRET(vectorMathTsAddHelper(pLeftCol, pRightCol, pOutputCol, pRight->numOfRows, step, i)); + SCL_ERR_JRET(vectorMathTsAddHelper(pLeftCol, pRightCol, pOutputCol, pRight->numOfRows, step, i, pLeft->tz)); } else { - SCL_ERR_JRET(vectorMathTsAddHelper(pRightCol, pLeftCol, pOutputCol, pRight->numOfRows, step, i)); + SCL_ERR_JRET(vectorMathTsAddHelper(pRightCol, pLeftCol, pOutputCol, pRight->numOfRows, step, i, pLeft->tz)); } } else if (pLeft->numOfRows == 1) { - SCL_ERR_JRET(vectorMathTsAddHelper(pRightCol, pLeftCol, pOutputCol, pRight->numOfRows, step, i)); + SCL_ERR_JRET(vectorMathTsAddHelper(pRightCol, pLeftCol, pOutputCol, pRight->numOfRows, step, i, pLeft->tz)); } else if (pRight->numOfRows == 1) { - SCL_ERR_JRET(vectorMathTsAddHelper(pLeftCol, pRightCol, pOutputCol, pLeft->numOfRows, step, i)); + SCL_ERR_JRET(vectorMathTsAddHelper(pLeftCol, pRightCol, pOutputCol, pLeft->numOfRows, step, i, pLeft->tz)); } else if (pLeft->numOfRows == pRight->numOfRows) { for (; i < pRight->numOfRows && i >= 0; i += step, output += 1) { if (IS_NULL) { @@ -1356,7 +1356,7 @@ static int32_t vectorMathSubHelper(SColumnInfoData *pLeftCol, SColumnInfoData *p } static int32_t vectorMathTsSubHelper(SColumnInfoData *pLeftCol, SColumnInfoData *pRightCol, SColumnInfoData *pOutputCol, - int32_t numOfRows, int32_t step, int32_t factor, int32_t i) { + int32_t numOfRows, int32_t step, int32_t factor, int32_t i, timezone_t tz) { _getBigintValue_fn_t getVectorBigintValueFnLeft; _getBigintValue_fn_t getVectorBigintValueFnRight; SCL_ERR_RET(getVectorBigintValueFn(pLeftCol->info.type, &getVectorBigintValueFnLeft)); @@ -1377,7 +1377,7 @@ static int32_t vectorMathTsSubHelper(SColumnInfoData *pLeftCol, SColumnInfoData SCL_ERR_RET(getVectorBigintValueFnLeft(pLeftCol->pData, i, &leftRes)); SCL_ERR_RET(getVectorBigintValueFnRight(pRightCol->pData, 0, &rightRes)); *output = - taosTimeAdd(leftRes, -rightRes, pRightCol->info.scale, pRightCol->info.precision); + taosTimeAdd(leftRes, -rightRes, pRightCol->info.scale, pRightCol->info.precision, tz); } } SCL_RET(TSDB_CODE_SUCCESS); @@ -1408,11 +1408,11 @@ int32_t vectorMathSub(SScalarParam *pLeft, SScalarParam *pRight, SScalarParam *p SCL_ERR_JRET(getVectorBigintValueFn(pRightCol->info.type, &getVectorBigintValueFnRight)); if (pLeft->numOfRows == 1 && pRight->numOfRows == 1) { - SCL_ERR_JRET(vectorMathTsSubHelper(pLeftCol, pRightCol, pOutputCol, pLeft->numOfRows, step, 1, i)); + SCL_ERR_JRET(vectorMathTsSubHelper(pLeftCol, pRightCol, pOutputCol, pLeft->numOfRows, step, 1, i, pLeft->tz)); } else if (pLeft->numOfRows == 1) { - SCL_ERR_JRET(vectorMathTsSubHelper(pRightCol, pLeftCol, pOutputCol, pRight->numOfRows, step, -1, i)); + SCL_ERR_JRET(vectorMathTsSubHelper(pRightCol, pLeftCol, pOutputCol, pRight->numOfRows, step, -1, i, pLeft->tz)); } else if (pRight->numOfRows == 1) { - SCL_ERR_JRET(vectorMathTsSubHelper(pLeftCol, pRightCol, pOutputCol, pLeft->numOfRows, step, 1, i)); + SCL_ERR_JRET(vectorMathTsSubHelper(pLeftCol, pRightCol, pOutputCol, pLeft->numOfRows, step, 1, i, pLeft->tz)); } else if (pLeft->numOfRows == pRight->numOfRows) { for (; i < pRight->numOfRows && i >= 0; i += step, output += 1) { if (IS_NULL) { @@ -2042,12 +2042,8 @@ int32_t vectorCompareImpl(SScalarParam *pLeft, SScalarParam *pRight, SScalarPara SScalarParam *param1 = NULL; SScalarParam *param2 = NULL; int32_t code = TSDB_CODE_SUCCESS; - pRight->tz = pLeft->tz; - pRight->charsetCxt = pLeft->charsetCxt; - pLeftOut.tz = pLeft->tz; - pLeftOut.charsetCxt = pLeft->charsetCxt; - pRightOut.tz = pRight->tz; - pRightOut.charsetCxt = pRight->charsetCxt; + setTzCharset(&pLeftOut, pLeft->tz, pLeft->charsetCxt); + setTzCharset(&pRightOut, pLeft->tz, pLeft->charsetCxt); if (noConvertBeforeCompare(GET_PARAM_TYPE(pLeft), GET_PARAM_TYPE(pRight), optr)) { param1 = pLeft; param2 = pRight; @@ -2131,10 +2127,6 @@ int32_t vectorIsNull(SScalarParam *pLeft, SScalarParam *pRight, SScalarParam *pO } int32_t vectorNotNull(SScalarParam *pLeft, SScalarParam *pRight, SScalarParam *pOut, int32_t _ord) { - if (pRight != NULL) { - pRight->tz = pLeft->tz; - pRight->charsetCxt = pLeft->charsetCxt; - } for (int32_t i = 0; i < pLeft->numOfRows; ++i) { int8_t v = IS_HELPER_NULL(pLeft->columnData, i) ? 0 : 1; if (v) { @@ -2148,11 +2140,6 @@ int32_t vectorNotNull(SScalarParam *pLeft, SScalarParam *pRight, SScalarParam *p } int32_t vectorIsTrue(SScalarParam *pLeft, SScalarParam *pRight, SScalarParam *pOut, int32_t _ord) { - if (pRight != NULL) { - pRight->tz = pLeft->tz; - pRight->charsetCxt = pLeft->charsetCxt; - - } SCL_ERR_RET(vectorConvertSingleColImpl(pLeft, pOut, NULL, -1, -1)); for (int32_t i = 0; i < pOut->numOfRows; ++i) { if (colDataIsNull_s(pOut->columnData, i)) { diff --git a/source/os/src/osTime.c b/source/os/src/osTime.c index be8067f208..f25631f932 100644 --- a/source/os/src/osTime.c +++ b/source/os/src/osTime.c @@ -610,7 +610,7 @@ struct tm *taosLocalTime(const time_t *timep, struct tm *result, char *buf, int3 (void)snprintf(buf, bufSize, "NaN"); } #endif - return result; + return res; } int32_t taosGetTimestampSec() { return (int32_t)time(NULL); } From 5ead3c68b13f40b39373a22d2e1b9083af8f4eff Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Fri, 29 Nov 2024 00:32:29 +0800 Subject: [PATCH 13/45] feat:[TD-32642] add timezone for connection support --- source/client/test/timezoneTest.cpp | 37 +++++++++++++++++++++++++++-- source/common/src/ttime.c | 33 ++++++++++++------------- source/os/src/osString.c | 6 ++--- 3 files changed, 55 insertions(+), 21 deletions(-) diff --git a/source/client/test/timezoneTest.cpp b/source/client/test/timezoneTest.cpp index b8f68f62dd..8b95dd148b 100644 --- a/source/client/test/timezoneTest.cpp +++ b/source/client/test/timezoneTest.cpp @@ -177,9 +177,41 @@ TEST(timezoneCase, alter_timezone_Test) { execQuery(pConn, "alter local 'timezone Asia/Kolkata'"); check_timezone(pConn, "show local variables", "Asia/Kolkata"); + execQuery(pConn, "alter local 'timezone Asia/Shanghai'"); + check_timezone(pConn, "show local variables", "Asia/Shanghai"); + execQueryFail(pConn, "alter dnode 1 'timezone Asia/Kolkata'"); } +char *tz_test[] = { + "2023-09-16 17:00:00+", + "2023-09-16 17:00:00+8:", + "2023-09-16 17:00:00+:30", + "2023-09-16 17:00:00+:-30", + "2023-09-16 17:00:00++:-30", + "2023-09-16 17:00:00+:", + "2023-09-16 17:00:00+8f:", + "2023-09-16 17:00:00+080:", + "2023-09-16 17:00:00+080", + "2023-09-16 17:00:00+a", + "2023-09-16 17:00:00+09:", + "2023-09-16 17:00:00+09:a", + "2023-09-16 17:00:00+09:abc", + "2023-09-16 17:00:00+09:001", +}; + +void do_insert_failed(){ + TAOS* pConn = getConnWithGlobalOption("UTC-8"); + + for (unsigned int i = 0; i < sizeof (tz_test) / sizeof (tz_test[0]); ++i){ + char sql[1024] = {0}; + (void)snprintf(sql, sizeof(sql), "insert into db1.ctb1 values('%s', '%s', 1)", tz_test[i], tz_test[i]); + + execQueryFail(pConn, sql); + } + taos_close(pConn); +} + struct insert_params { const char *tz; @@ -227,7 +259,7 @@ TEST(timezoneCase, insert_with_timezone_Test) { execQuery(pConn1, "create table db1.ctb1 using db1.stb tags(\"2023-09-16 17:00:00+05:00\", \"2023-09-16 17:00:00\", 1)"); execQuery(pConn1, "create table db1.ctb2 using db1.stb tags(1732178775000, 1732178775000, 1)"); execQuery(pConn1, "insert into db1.ntb values(1732178775133, 1732178775133, 1)"); - execQuery(pConn1, "insert into db1.ctb1 values(1732178775133, 1732178775133, 1)"); + execQuery(pConn1, "insert into db1.ctb1 values(1732178775133, 1732178775133, 1)"); //2024-11-21 10:46:15.133+02:00 execQuery(pConn1, "insert into db1.ctb2 values(1732178775133, 1732178775133, 1)"); /* @@ -251,6 +283,7 @@ TEST(timezoneCase, insert_with_timezone_Test) { do_select(params2[i]); } + do_insert_failed(); /* * 4. test NULL timezone, use default timezone UTC-8 */ @@ -284,7 +317,7 @@ TEST(timezoneCase, func_timezone_Test) { execQuery(pConn, "insert into db1.ntb values(1704142800000, '2024-01-01 23:00:00', 1)"); // 2024-01-01 23:00:00+0200 // test timezone - check_sql_result(pConn, "select timezone()", "UTC-test (UTC, +0200)"); + check_sql_result(pConn, "select timezone()", "UTC-2 (UTC, +0200)"); // test timetruncate check_sql_result(pConn, "select TO_ISO8601(TIMETRUNCATE('2024-01-01 23:00:00', 1d, 0))", "2024-01-01T02:00:00.000+0200"); diff --git a/source/common/src/ttime.c b/source/common/src/ttime.c index a5da856e4f..681660bfb7 100644 --- a/source/common/src/ttime.c +++ b/source/common/src/ttime.c @@ -130,8 +130,15 @@ int32_t parseFraction(char* str, char** end, int32_t timePrec, int64_t* pFractio TAOS_RETURN(TSDB_CODE_SUCCESS); } +#define PARSE(str,len,result) \ + if (len > 2 || len < 1) {\ + TAOS_RETURN(TSDB_CODE_INVALID_PARA);\ + }\ + result = strnatoi(str, len); + int32_t parseTimezone(char* str, int64_t* tzOffset) { int64_t hour = 0; + int64_t minute = 0; int32_t i = 0; if (str[i] != '+' && str[i] != '-') { @@ -152,30 +159,24 @@ int32_t parseTimezone(char* str, int64_t* tzOffset) { char* sep = strchr(&str[i], ':'); if (sep != NULL) { - int32_t len = (int32_t)(sep - &str[i]); + int32_t hourSize = (int32_t)(sep - &str[i]); + PARSE(&str[i], hourSize, hour); - hour = strnatoi(&str[i], len); - i += len + 1; + i += hourSize + 1; + size_t minSize = strlen(&str[i]); + PARSE(&str[i], minSize, minute); + if (minute > 59 || minute < 0) { + TAOS_RETURN(TSDB_CODE_INVALID_PARA); + } } else { - hour = strnatoi(&str[i], 2); - i += 2; + size_t hourSize = strlen(&str[i]); + PARSE(&str[i], hourSize, hour) } if (hour > 13 || hour < 0) { TAOS_RETURN(TSDB_CODE_INVALID_PARA); } - // return error if there're illegal charaters after min(2 Digits) - char* minStr = &str[i]; - if (minStr[1] != '\0' && minStr[2] != '\0') { - TAOS_RETURN(TSDB_CODE_INVALID_PARA); - } - - int64_t minute = strnatoi(&str[i], 2); - if (minute > 59 || minute < 0) { - TAOS_RETURN(TSDB_CODE_INVALID_PARA); - } - if (str[0] == '+') { *tzOffset = -(hour * 3600 + minute * 60); } else { diff --git a/source/os/src/osString.c b/source/os/src/osString.c index 4506248eec..d8109bab0c 100644 --- a/source/os/src/osString.c +++ b/source/os/src/osString.c @@ -238,16 +238,16 @@ iconv_t taosAcquireConv(int32_t *idx, ConvType type, void* charsetCxt) { charsetCxt = tsCharsetCxt; } SConvInfo *info = (SConvInfo *)charsetCxt; - if (info->gConvMaxNum[type] <= 0) { + if (info == NULL) { *idx = -1; if (type == M2C) { - iconv_t c = iconv_open(DEFAULT_UNICODE_ENCODEC, info->charset); + iconv_t c = iconv_open(DEFAULT_UNICODE_ENCODEC, "UTF-8"); if ((iconv_t)-1 == c) { terrno = TAOS_SYSTEM_ERROR(errno); } return c; } else { - iconv_t c = iconv_open(info->charset, DEFAULT_UNICODE_ENCODEC); + iconv_t c = iconv_open("UTF-8", DEFAULT_UNICODE_ENCODEC); if ((iconv_t)-1 == c) { terrno = TAOS_SYSTEM_ERROR(errno); } From 8c05378ca12f888e3631ef98c2eefbac536bf971 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Fri, 29 Nov 2024 01:13:52 +0800 Subject: [PATCH 14/45] feat:[TD-32642] add timezone for connection support --- source/common/src/ttime.c | 15 ++++++++++----- source/libs/scalar/src/sclfunc.c | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/source/common/src/ttime.c b/source/common/src/ttime.c index 681660bfb7..8ac50a2c17 100644 --- a/source/common/src/ttime.c +++ b/source/common/src/ttime.c @@ -163,20 +163,25 @@ int32_t parseTimezone(char* str, int64_t* tzOffset) { PARSE(&str[i], hourSize, hour); i += hourSize + 1; - size_t minSize = strlen(&str[i]); - PARSE(&str[i], minSize, minute); - if (minute > 59 || minute < 0) { - TAOS_RETURN(TSDB_CODE_INVALID_PARA); - } } else { size_t hourSize = strlen(&str[i]); + if (hourSize > 2){ + hourSize = 2; + } PARSE(&str[i], hourSize, hour) + i += hourSize; } if (hour > 13 || hour < 0) { TAOS_RETURN(TSDB_CODE_INVALID_PARA); } + size_t minSize = strlen(&str[i]); + PARSE(&str[i], minSize, minute); + if (minute > 59 || minute < 0) { + TAOS_RETURN(TSDB_CODE_INVALID_PARA); + } + if (str[0] == '+') { *tzOffset = -(hour * 3600 + minute * 60); } else { diff --git a/source/libs/scalar/src/sclfunc.c b/source/libs/scalar/src/sclfunc.c index 639676cdf1..efec336589 100644 --- a/source/libs/scalar/src/sclfunc.c +++ b/source/libs/scalar/src/sclfunc.c @@ -2259,10 +2259,10 @@ int32_t toISO8601Function(SScalarParam *pInput, int32_t inputNum, SScalarParam * len += tzLen; } -_end: memmove(buf + VARSTR_HEADER_SIZE, buf, len); varDataSetLen(buf, len); + _end: SCL_ERR_RET(colDataSetVal(pOutput->columnData, i, buf, false)); } From 8629a63c6b76122217ac9f22eb7ccbf9883c8a59 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Fri, 29 Nov 2024 01:34:20 +0800 Subject: [PATCH 15/45] feat:[TD-32642] add timezone for connection support --- source/client/test/timezoneTest.cpp | 1 - source/common/src/ttime.c | 9 ++++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/source/client/test/timezoneTest.cpp b/source/client/test/timezoneTest.cpp index 8b95dd148b..a9b31231bc 100644 --- a/source/client/test/timezoneTest.cpp +++ b/source/client/test/timezoneTest.cpp @@ -192,7 +192,6 @@ char *tz_test[] = { "2023-09-16 17:00:00+:", "2023-09-16 17:00:00+8f:", "2023-09-16 17:00:00+080:", - "2023-09-16 17:00:00+080", "2023-09-16 17:00:00+a", "2023-09-16 17:00:00+09:", "2023-09-16 17:00:00+09:a", diff --git a/source/common/src/ttime.c b/source/common/src/ttime.c index 8ac50a2c17..47747611d9 100644 --- a/source/common/src/ttime.c +++ b/source/common/src/ttime.c @@ -163,6 +163,8 @@ int32_t parseTimezone(char* str, int64_t* tzOffset) { PARSE(&str[i], hourSize, hour); i += hourSize + 1; + size_t minSize = strlen(&str[i]); + PARSE(&str[i], minSize, minute); } else { size_t hourSize = strlen(&str[i]); if (hourSize > 2){ @@ -170,14 +172,15 @@ int32_t parseTimezone(char* str, int64_t* tzOffset) { } PARSE(&str[i], hourSize, hour) i += hourSize; + size_t minSize = strlen(&str[i]); + if (minSize > 0){ + PARSE(&str[i], minSize, minute); + } } if (hour > 13 || hour < 0) { TAOS_RETURN(TSDB_CODE_INVALID_PARA); } - - size_t minSize = strlen(&str[i]); - PARSE(&str[i], minSize, minute); if (minute > 59 || minute < 0) { TAOS_RETURN(TSDB_CODE_INVALID_PARA); } From 5ee80ac3bbb2afbf94209f0e7216f14781c67145 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Fri, 29 Nov 2024 10:25:59 +0800 Subject: [PATCH 16/45] feat:[TD-32642] add timezone for connection support --- source/client/test/timezoneTest.cpp | 26 +++++++++++++++++++------- source/common/src/ttime.c | 2 +- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/source/client/test/timezoneTest.cpp b/source/client/test/timezoneTest.cpp index a9b31231bc..2ea2860d15 100644 --- a/source/client/test/timezoneTest.cpp +++ b/source/client/test/timezoneTest.cpp @@ -185,16 +185,28 @@ TEST(timezoneCase, alter_timezone_Test) { char *tz_test[] = { "2023-09-16 17:00:00+", - "2023-09-16 17:00:00+8:", - "2023-09-16 17:00:00+:30", - "2023-09-16 17:00:00+:-30", - "2023-09-16 17:00:00++:-30", + "2023-09-16 17:00:00+a", + "2023-09-16 17:00:00+8", + "2023-09-16 17:00:00+832", + "2023-09-16 17:00:00+8323", "2023-09-16 17:00:00+:", + "2023-09-16 17:00:00+8:", + "2023-09-16 17:00:00++:", + "2023-09-16 17:00:00+d:", + "2023-09-16 17:00:00+09:", "2023-09-16 17:00:00+8f:", "2023-09-16 17:00:00+080:", - "2023-09-16 17:00:00+a", - "2023-09-16 17:00:00+09:", - "2023-09-16 17:00:00+09:a", + "2023-09-16 17:00:00+:30", + "2023-09-16 17:00:00+:3", + "2023-09-16 17:00:00+:093", + "2023-09-16 17:00:00+:-30", + "2023-09-16 17:00:00++:-30", + "2023-09-16 17:00:00+8:8", + "2023-09-16 17:00:00+8:2a", + "2023-09-16 17:00:00+8:08", + "2023-09-16 17:00:00+8:038", + "2023-09-16 17:00:00+08:8", + "2023-09-16 17:00:00+09:3a", "2023-09-16 17:00:00+09:abc", "2023-09-16 17:00:00+09:001", }; diff --git a/source/common/src/ttime.c b/source/common/src/ttime.c index 47747611d9..45b5ab6564 100644 --- a/source/common/src/ttime.c +++ b/source/common/src/ttime.c @@ -131,7 +131,7 @@ int32_t parseFraction(char* str, char** end, int32_t timePrec, int64_t* pFractio } #define PARSE(str,len,result) \ - if (len > 2 || len < 1) {\ + if (len != 2) {\ TAOS_RETURN(TSDB_CODE_INVALID_PARA);\ }\ result = strnatoi(str, len); From d52e9a1c5c6c9a9c34bd8de478acde9340d2531b Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Fri, 29 Nov 2024 17:59:53 +0800 Subject: [PATCH 17/45] feat:[TD-32642] add userApp & userIp for connection support --- include/client/taos.h | 3 +- include/common/tmsg.h | 2 + source/client/inc/clientInt.h | 4 +- source/client/src/clientEnv.c | 4 +- source/client/src/clientHb.c | 3 + source/client/src/clientMain.c | 57 ++++++--- source/client/test/timezoneTest.cpp | 150 ++++++++++++++++++++++- source/common/src/msg/tmsg.c | 6 + source/common/src/systable.c | 4 + source/dnode/mnode/impl/src/mndProfile.c | 54 ++++++++ 10 files changed, 264 insertions(+), 23 deletions(-) diff --git a/include/client/taos.h b/include/client/taos.h index 7e300e898e..a529cf8d15 100644 --- a/include/client/taos.h +++ b/include/client/taos.h @@ -65,11 +65,12 @@ typedef enum { } TSDB_OPTION; typedef enum { + TSDB_OPTION_CONNECTION_CLEAR = -1, // clear all option in this connection TSDB_OPTION_CONNECTION_CHARSET, // charset, Same as the scope supported by the system TSDB_OPTION_CONNECTION_TIMEZONE, // timezone, Same as the scope supported by the system TSDB_OPTION_CONNECTION_USER_IP, // user ip TSDB_OPTION_CONNECTION_USER_APP, // user app - TSDB_MAX_CONNECTION_OPTIONS = 100 + TSDB_MAX_OPTIONS_CONNECTION } TSDB_OPTION_CONNECTION; typedef enum { diff --git a/include/common/tmsg.h b/include/common/tmsg.h index 42301773e9..9af98898a6 100644 --- a/include/common/tmsg.h +++ b/include/common/tmsg.h @@ -3421,6 +3421,8 @@ typedef struct { SQueryHbReqBasic* query; SHashObj* info; // hash char name[TSDB_APP_NAME_LEN]; + char userApp[TSDB_APP_NAME_LEN]; + uint32_t userIp; } SClientHbReq; typedef struct { diff --git a/source/client/inc/clientInt.h b/source/client/inc/clientInt.h index a6ab0f8489..300ea1e72b 100644 --- a/source/client/inc/clientInt.h +++ b/source/client/inc/clientInt.h @@ -156,8 +156,8 @@ typedef struct { typedef struct { timezone_t timezone; void *charsetCxt; - char app[TSDB_APP_NAME_LEN]; - uint32_t ip; + char userApp[TSDB_APP_NAME_LEN]; + uint32_t userIp; }SOptionInfo; typedef struct STscObj { diff --git a/source/client/src/clientEnv.c b/source/client/src/clientEnv.c index b67d0daf73..3b443c9eab 100644 --- a/source/client/src/clientEnv.c +++ b/source/client/src/clientEnv.c @@ -964,7 +964,7 @@ void taos_init_imp(void) { #endif if (taosCreateLog(logDirName, 10, configDir, NULL, NULL, NULL, NULL, 1) != 0) { (void)printf(" WARING: Create %s failed:%s. configDir=%s\n", logDirName, strerror(errno), configDir); - tscInitRes = -1; + tscInitRes = terrno; return; } @@ -981,7 +981,7 @@ void taos_init_imp(void) { ENV_ERR_RET(rpcInit(), "failed to init rpc"); if (InitRegexCache() != 0) { - tscInitRes = -1; + tscInitRes = terrno; (void)printf("failed to init regex cache\n"); return; } diff --git a/source/client/src/clientHb.c b/source/client/src/clientHb.c index 07be4bb596..23b3b0315f 100644 --- a/source/client/src/clientHb.c +++ b/source/client/src/clientHb.c @@ -1194,6 +1194,9 @@ int32_t hbGatherAllInfo(SAppHbMgr *pAppHbMgr, SClientHbBatchReq **pBatchReq) { continue; } + tstrncpy(pOneReq->userApp, pTscObj->optionInfo.userApp, sizeof(pOneReq->userApp)); + pOneReq->userIp = pTscObj->optionInfo.userIp; + pOneReq = taosArrayPush((*pBatchReq)->reqs, pOneReq); if (NULL == pOneReq) { releaseTscObj(connKey->tscRid); diff --git a/source/client/src/clientMain.c b/source/client/src/clientMain.c index 730916e31c..14d8394e25 100644 --- a/source/client/src/clientMain.c +++ b/source/client/src/clientMain.c @@ -39,6 +39,9 @@ static int32_t sentinel = TSC_VAR_NOT_RELEASE; static int32_t createParseContext(const SRequestObj *pRequest, SParseContext **pCxt, SSqlCallbackWrapper *pWrapper); int taos_options(TSDB_OPTION option, const void *arg, ...) { + if (arg == NULL) { + return TSDB_CODE_INVALID_PARA; + } static int32_t lock = 0; for (int i = 1; atomic_val_compare_exchange_32(&lock, 0, 1) != 0; ++i) { @@ -117,35 +120,47 @@ static int32_t setConnectionOption(TAOS *taos, TSDB_OPTION_CONNECTION option, co return TSDB_CODE_INVALID_PARA; } - if (option != TSDB_MAX_CONNECTION_OPTIONS && (option < TSDB_OPTION_CONNECTION_CHARSET && option > TSDB_OPTION_CONNECTION_USER_APP)){ + if (option < TSDB_OPTION_CONNECTION_CLEAR || option >= TSDB_MAX_OPTIONS_CONNECTION){ return TSDB_CODE_INVALID_PARA; } + int32_t code = taos_init(); + // initialize global config + if (code != 0) { + return code; + } + STscObj *pObj = acquireTscObj(*(int64_t *)taos); if (NULL == pObj) { tscError("invalid parameter for %s", __func__); return terrno; } - int32_t code = 0; - if (option == TSDB_OPTION_CONNECTION_CHARSET) { + if (option == TSDB_OPTION_CONNECTION_CLEAR){ + val = NULL; + } + + if (option == TSDB_OPTION_CONNECTION_CHARSET || option == TSDB_OPTION_CONNECTION_CLEAR) { if (val != NULL) { if (!taosValidateEncodec(val)) { code = terrno; goto END; } + void *tmp = taosConvInit(val); + if (tmp == NULL) { + code = terrno; + goto END; + } + pObj->optionInfo.charsetCxt = tmp; }else{ - val = tsCharset; + pObj->optionInfo.charsetCxt = NULL; } - void *tmp = taosConvInit(val); - if (tmp == NULL) { - code = terrno; - goto END; - } - pObj->optionInfo.charsetCxt = tmp; - } else if (option == TSDB_OPTION_CONNECTION_TIMEZONE) { + } + + if (option == TSDB_OPTION_CONNECTION_TIMEZONE || option == TSDB_OPTION_CONNECTION_CLEAR) { if (val != NULL){ if (strlen(val) == 0){ + tscError("%s empty timezone %s", __func__, val); code = TSDB_CODE_INVALID_PARA; goto END; } @@ -158,17 +173,25 @@ static int32_t setConnectionOption(TAOS *taos, TSDB_OPTION_CONNECTION option, co } else { pObj->optionInfo.timezone = NULL; } - } else if (option == TSDB_OPTION_CONNECTION_USER_APP) { + } + + if (option == TSDB_OPTION_CONNECTION_USER_APP || option == TSDB_OPTION_CONNECTION_CLEAR) { if (val != NULL) { - tstrncpy(pObj->optionInfo.app, val, TSDB_APP_NAME_LEN); + tstrncpy(pObj->optionInfo.userApp, val, sizeof(pObj->optionInfo.userApp)); } else { - pObj->optionInfo.app[0] = 0; + pObj->optionInfo.userApp[0] = 0; } - } else if (option == TSDB_OPTION_CONNECTION_USER_IP) { + } + + if (option == TSDB_OPTION_CONNECTION_USER_IP || option == TSDB_OPTION_CONNECTION_CLEAR) { if (val != NULL) { - pObj->optionInfo.ip = inet_addr(val); + pObj->optionInfo.userIp = inet_addr(val); + if (pObj->optionInfo.userIp == INADDR_NONE){ + code = TSDB_CODE_INVALID_PARA; + goto END; + } } else { - pObj->optionInfo.ip = 0; + pObj->optionInfo.userIp = INADDR_NONE; } } diff --git a/source/client/test/timezoneTest.cpp b/source/client/test/timezoneTest.cpp index 2ea2860d15..84ee53452a 100644 --- a/source/client/test/timezoneTest.cpp +++ b/source/client/test/timezoneTest.cpp @@ -27,7 +27,7 @@ #include "executor.h" #include "taos.h" - +#include "clientInt.h" int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); @@ -165,6 +165,154 @@ void check_set_timezone(TAOS* optionFunc(const char *tz)){ } } +#define CHECK_TAOS_OPTION_POINTER(taos, option, isnull) \ + { \ + STscObj* pObj = acquireTscObj(*(int64_t*)taos); \ + ASSERT(pObj != nullptr); \ + if (isnull) { \ + ASSERT(pObj->optionInfo.option == nullptr); \ + } else { \ + ASSERT(pObj->optionInfo.option != nullptr); \ + } \ + } + +#define CHECK_TAOS_OPTION_APP(taos, option, val) \ + { \ + STscObj* pObj = acquireTscObj(*(int64_t*)taos); \ + ASSERT(pObj != nullptr); \ + ASSERT(strcmp(pObj->optionInfo.option, val) == 0); \ + } + +#define CHECK_TAOS_OPTION_IP_ERROR(taos, option, val) \ + { \ + STscObj* pObj = acquireTscObj(*(int64_t*)taos); \ + ASSERT(pObj != nullptr); \ + ASSERT(pObj->optionInfo.option == val); \ + } + +#define CHECK_TAOS_OPTION_IP(taos, option, val) \ + { \ + STscObj* pObj = acquireTscObj(*(int64_t*)taos); \ + ASSERT(pObj != nullptr); \ + char ip[TD_IP_LEN] = {0}; \ + tinet_ntoa(ip, pObj->optionInfo.option); \ + ASSERT(strcmp(ip, val) == 0); \ + } + +TEST(timezoneCase, setConnectionOption_Test) { + int32_t code = taos_options_connection(NULL, TSDB_OPTION_CONNECTION_CHARSET, NULL); + ASSERT(code != 0); + TAOS* pConn = taos_connect("localhost", "root", "taosdata", NULL, 0); + ASSERT(pConn != nullptr); + + code = taos_options_connection(pConn, TSDB_MAX_OPTIONS_CONNECTION, NULL); + ASSERT(code != 0); + + // test charset + code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_CHARSET, ""); + ASSERT(code != 0); + + code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_CHARSET, NULL); + ASSERT(code == 0); + CHECK_TAOS_OPTION_POINTER(pConn, charsetCxt, true); + + code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_CHARSET, "Asia/Shanghai"); + ASSERT(code != 0); + + code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_CHARSET, "gbk"); + ASSERT(code == 0); + CHECK_TAOS_OPTION_POINTER(pConn, charsetCxt, false); + + // test timezone + code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_TIMEZONE, ""); + ASSERT(code != 0); + + code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_TIMEZONE, NULL); + ASSERT(code == 0); + CHECK_TAOS_OPTION_POINTER(pConn, timezone, true); + check_sql_result(pConn, "select timezone()", "Asia/Shanghai (CST, +0800)"); + + code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_TIMEZONE, "UTC"); + ASSERT(code == 0); + CHECK_TAOS_OPTION_POINTER(pConn, timezone, false); + check_sql_result(pConn, "select timezone()", "UTC (UTC, +0000)"); + + code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_TIMEZONE, "Asia/Kolkata"); + ASSERT(code == 0); + CHECK_TAOS_OPTION_POINTER(pConn, timezone, false); + check_sql_result(pConn, "select timezone()", "Asia/Kolkata (IST, +0530)"); + + code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_TIMEZONE, "adbc"); + ASSERT(code != 0); + + // test user APP + code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_USER_APP, ""); + ASSERT(code == 0); + CHECK_TAOS_OPTION_APP(pConn, userApp, ""); + + code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_USER_APP, NULL); + ASSERT(code == 0); + CHECK_TAOS_OPTION_APP(pConn, userApp, ""); + + code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_USER_APP, "aaaaaaaaaaaaaaaaaaaaaabbbbbbb"); + ASSERT(code == 0); + CHECK_TAOS_OPTION_APP(pConn, userApp, "aaaaaaaaaaaaaaaaaaaaaab"); + + + // test user IP + code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_USER_IP, ""); + ASSERT(code != 0); + CHECK_TAOS_OPTION_IP_ERROR(pConn, userIp, INADDR_NONE); + + code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_USER_IP, NULL); + ASSERT(code == 0); + CHECK_TAOS_OPTION_IP_ERROR(pConn, userIp, INADDR_NONE); + + code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_USER_IP, "aaaaaaaaaaaaaaaaaaaaaabbbbbbb"); + ASSERT(code != 0); + CHECK_TAOS_OPTION_IP_ERROR(pConn, userIp, INADDR_NONE); + + code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_USER_IP, "1292.168.0.2"); + ASSERT(code != 0); + CHECK_TAOS_OPTION_IP_ERROR(pConn, userIp, INADDR_NONE); + + code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_USER_IP, "192.168.0.2"); + ASSERT(code == 0); + CHECK_TAOS_OPTION_IP(pConn, userIp, "192.168.0.2"); + + taosMsleep(2 * HEARTBEAT_INTERVAL); + + //test user APP and user IP + check_sql_result(pConn, "select user_app from performance_schema.perf_connections", "aaaaaaaaaaaaaaaaaaaaaab"); + check_sql_result(pConn, "select user_ip from performance_schema.perf_connections", "192.168.0.2"); + + code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_USER_IP, "192.168.1.2"); + ASSERT(code == 0); + CHECK_TAOS_OPTION_IP(pConn, userIp, "192.168.1.2"); + + + code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_USER_APP, "user"); + ASSERT(code == 0); + CHECK_TAOS_OPTION_APP(pConn, userApp, "user"); + + taosMsleep(2 * HEARTBEAT_INTERVAL); + + check_sql_result(pConn, "select user_app from performance_schema.perf_connections", "user"); + check_sql_result(pConn, "select user_ip from performance_schema.perf_connections", "192.168.1.2"); + + + // test clear + code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_CLEAR, "192.168.0.2"); + ASSERT(code == 0); + CHECK_TAOS_OPTION_POINTER(pConn, charsetCxt, true); + CHECK_TAOS_OPTION_POINTER(pConn, timezone, true); + check_sql_result(pConn, "select timezone()", "Asia/Shanghai (CST, +0800)"); + CHECK_TAOS_OPTION_APP(pConn, userApp, ""); + CHECK_TAOS_OPTION_IP_ERROR(pConn, userIp, INADDR_NONE); + + taos_close(pConn); +} + TEST(timezoneCase, set_timezone_Test) { check_set_timezone(getConnWithGlobalOption); check_set_timezone(getConnWithOption); diff --git a/source/common/src/msg/tmsg.c b/source/common/src/msg/tmsg.c index 2e997218ac..540a1dfcc8 100644 --- a/source/common/src/msg/tmsg.c +++ b/source/common/src/msg/tmsg.c @@ -302,6 +302,8 @@ static int32_t tSerializeSClientHbReq(SEncoder *pEncoder, const SClientHbReq *pR TAOS_CHECK_RETURN(tEncodeSKv(pEncoder, kv)); pIter = taosHashIterate(pReq->info, pIter); } + TAOS_CHECK_RETURN(tEncodeU32(pEncoder, pReq->userIp)); + TAOS_CHECK_RETURN(tEncodeCStr(pEncoder, pReq->userApp)); return 0; } @@ -399,6 +401,10 @@ static int32_t tDeserializeSClientHbReq(SDecoder *pDecoder, SClientHbReq *pReq) return terrno = code; } } + if (!tDecodeIsEnd(pDecoder)) { + TAOS_CHECK_RETURN(tDecodeU32(pDecoder, &pReq->userIp)); + TAOS_CHECK_RETURN(tDecodeCStrTo(pDecoder, pReq->userApp)); + } return 0; } diff --git a/source/common/src/systable.c b/source/common/src/systable.c index bfe82aa7ae..27d0c91b31 100644 --- a/source/common/src/systable.c +++ b/source/common/src/systable.c @@ -501,6 +501,8 @@ static const SSysDbTableSchema connectionsSchema[] = { {.name = "end_point", .bytes = TSDB_EP_LEN + VARSTR_HEADER_SIZE, .type = TSDB_DATA_TYPE_BINARY, .sysInfo = false}, {.name = "login_time", .bytes = 8, .type = TSDB_DATA_TYPE_TIMESTAMP, .sysInfo = false}, {.name = "last_access", .bytes = 8, .type = TSDB_DATA_TYPE_TIMESTAMP, .sysInfo = false}, + {.name = "user_app", .bytes = TSDB_APP_NAME_LEN + VARSTR_HEADER_SIZE, .type = TSDB_DATA_TYPE_BINARY, .sysInfo = false}, + {.name = "user_ip", .bytes = TSDB_IPv4ADDR_LEN + 6 + VARSTR_HEADER_SIZE, .type = TSDB_DATA_TYPE_VARCHAR, .sysInfo = false}, }; static const SSysDbTableSchema consumerSchema[] = { @@ -542,6 +544,8 @@ static const SSysDbTableSchema querySchema[] = { {.name = "sub_num", .bytes = 4, .type = TSDB_DATA_TYPE_INT, .sysInfo = false}, {.name = "sub_status", .bytes = TSDB_SHOW_SUBQUERY_LEN + VARSTR_HEADER_SIZE, .type = TSDB_DATA_TYPE_VARCHAR, .sysInfo = false}, {.name = "sql", .bytes = TSDB_SHOW_SQL_LEN + VARSTR_HEADER_SIZE, .type = TSDB_DATA_TYPE_VARCHAR, .sysInfo = false}, + {.name = "user_app", .bytes = TSDB_APP_NAME_LEN + VARSTR_HEADER_SIZE, .type = TSDB_DATA_TYPE_VARCHAR, .sysInfo = false}, + {.name = "user_ip", .bytes = TSDB_IPv4ADDR_LEN + 6 + VARSTR_HEADER_SIZE, .type = TSDB_DATA_TYPE_VARCHAR, .sysInfo = false}, }; static const SSysDbTableSchema appSchema[] = { diff --git a/source/dnode/mnode/impl/src/mndProfile.c b/source/dnode/mnode/impl/src/mndProfile.c index 91ded71aa8..338f71856a 100644 --- a/source/dnode/mnode/impl/src/mndProfile.c +++ b/source/dnode/mnode/impl/src/mndProfile.c @@ -45,6 +45,8 @@ typedef struct { int32_t numOfQueries; SRWLatch queryLock; SArray *pQueries; // SArray + char userApp[TSDB_APP_NAME_LEN]; + uint32_t userIp; } SConnObj; typedef struct { @@ -135,6 +137,13 @@ void mndCleanupProfile(SMnode *pMnode) { } } +static void setUserInfo2Conn(SConnObj* connObj, char* userApp, uint32_t userIp){ + if (connObj == NULL){ + return; + } + tstrncpy(connObj->userApp, userApp, sizeof(connObj->userApp)); + connObj->userIp = userIp; +} static SConnObj *mndCreateConn(SMnode *pMnode, const char *user, int8_t connType, uint32_t ip, uint16_t port, int32_t pid, const char *app, int64_t startTime) { SProfileMgmt *pMgmt = &pMnode->profileMgmt; @@ -513,6 +522,7 @@ static int32_t mndProcessQueryHeartBeat(SMnode *pMnode, SRpcMsg *pMsg, SClientHb } } + setUserInfo2Conn(pConn, pHbReq->userApp, pHbReq->userIp); SQueryHbRspBasic *rspBasic = taosMemoryCalloc(1, sizeof(SQueryHbRspBasic)); if (rspBasic == NULL) { mndReleaseConn(pMnode, pConn, true); @@ -921,6 +931,27 @@ static int32_t mndRetrieveConns(SRpcMsg *pReq, SShowObj *pShow, SSDataBlock *pBl return code; } + char userApp[TSDB_APP_NAME_LEN + VARSTR_HEADER_SIZE]; + STR_TO_VARSTR(userApp, pConn->userApp); + pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); + code = colDataSetVal(pColInfo, numOfRows, (const char *)userApp, false); + if (code != 0) { + mError("failed to set user app since %s", tstrerror(code)); + return code; + } + + char userIp[TD_IP_LEN + 6 + VARSTR_HEADER_SIZE] = {0}; + if (pConn->userIp != 0 && pConn->userIp != INADDR_NONE){ + tinet_ntoa(varDataVal(userIp), pConn->userIp); + varDataLen(userIp) = strlen(varDataVal(userIp)); + } + pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); + code = colDataSetVal(pColInfo, numOfRows, (const char *)userIp, false); + if (code != 0) { + mError("failed to set user ip since %s", tstrerror(code)); + return code; + } + numOfRows++; } @@ -1093,6 +1124,29 @@ static int32_t packQueriesIntoBlock(SShowObj *pShow, SConnObj *pConn, SSDataBloc return code; } + char userApp[TSDB_APP_NAME_LEN + VARSTR_HEADER_SIZE]; + STR_TO_VARSTR(userApp, pConn->userApp); + pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); + code = colDataSetVal(pColInfo, curRowIndex, (const char *)userApp, false); + if (code != 0) { + mError("failed to set user app since %s", tstrerror(code)); + taosRUnLockLatch(&pConn->queryLock); + return code; + } + + char userIp[TD_IP_LEN + 6 + VARSTR_HEADER_SIZE] = {0}; + if (pConn->userIp != 0 && pConn->userIp != INADDR_NONE){ + tinet_ntoa(varDataVal(userIp), pConn->userIp); + varDataLen(userIp) = strlen(varDataVal(userIp)); + } + pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); + code = colDataSetVal(pColInfo, curRowIndex, (const char *)userIp, false); + if (code != 0) { + mError("failed to set user ip since %s", tstrerror(code)); + taosRUnLockLatch(&pConn->queryLock); + return code; + } + pBlock->info.rows++; } From 9cfd293793416bab3175776cebb529e8fb1f9185 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Mon, 2 Dec 2024 10:22:48 +0800 Subject: [PATCH 18/45] feat:[TD-32642] add timezone support in windows --- contrib/CMakeLists.txt | 24 +++++---- include/os/osTimezone.h | 9 +++- source/client/src/clientMain.c | 16 +++--- source/os/CMakeLists.txt | 6 ++- source/os/src/osTimezone.c | 87 +++++++----------------------- source/os/src/timezone/localtime.c | 34 ++++++++---- source/os/src/timezone/private.h | 34 +++++++----- source/os/src/timezone/strftime.c | 6 +-- source/util/src/tconfig.c | 10 ++-- 9 files changed, 105 insertions(+), 121 deletions(-) diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index 6253d258a8..345f23943d 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -106,7 +106,9 @@ cat("${TD_SUPPORT_DIR}/zlib_CMakeLists.txt.in" ${CONTRIB_TMP_FILE}) # cJson cat("${TD_SUPPORT_DIR}/cjson_CMakeLists.txt.in" ${CONTRIB_TMP_FILE}) -cat("${TD_SUPPORT_DIR}/tz_CMakeLists.txt.in" ${CONTRIB_TMP_FILE}) +if(NOT ${TD_WINDOWS}) + cat("${TD_SUPPORT_DIR}/tz_CMakeLists.txt.in" ${CONTRIB_TMP_FILE}) +endif(NOT ${TD_WINDOWS}) # xz # cat("${TD_SUPPORT_DIR}/xz_CMakeLists.txt.in" ${CONTRIB_TMP_FILE}) @@ -653,16 +655,18 @@ if(${TD_LINUX} AND ${BUILD_WITH_S3}) add_subdirectory(azure-cmake EXCLUDE_FROM_ALL) endif() -execute_process( - COMMAND make TZDIR=${TZ_OUTPUT_PATH}/ clean zic posix_only - WORKING_DIRECTORY "${TD_CONTRIB_DIR}/tz" -) +if(NOT ${TD_WINDOWS}) + execute_process( + COMMAND make TZDIR=${TZ_OUTPUT_PATH}/ clean zic posix_only + WORKING_DIRECTORY "${TD_CONTRIB_DIR}/tz" + ) -set(TZ_SRC_DIR "${TD_SOURCE_DIR}/source/os/src/timezone") -file(MAKE_DIRECTORY ${TZ_SRC_DIR}) -file(COPY ${TD_CONTRIB_DIR}/tz/private.h ${TD_CONTRIB_DIR}/tz/tzdir.h ${TD_CONTRIB_DIR}/tz/tzfile.h - ${TD_CONTRIB_DIR}/tz/localtime.c ${TD_CONTRIB_DIR}/tz/strftime.c - DESTINATION ${TZ_SRC_DIR}) + set(TZ_SRC_DIR "${TD_SOURCE_DIR}/source/os/src/timezone") + file(MAKE_DIRECTORY ${TZ_SRC_DIR}) + file(COPY ${TD_CONTRIB_DIR}/tz/private.h ${TD_CONTRIB_DIR}/tz/tzdir.h ${TD_CONTRIB_DIR}/tz/tzfile.h + ${TD_CONTRIB_DIR}/tz/localtime.c ${TD_CONTRIB_DIR}/tz/strftime.c + DESTINATION ${TZ_SRC_DIR}) +endif(NOT ${TD_WINDOWS}) # ================================================================================================ # Build test # ================================================================================================ diff --git a/include/os/osTimezone.h b/include/os/osTimezone.h index 79dc854fa4..c9cb55207c 100644 --- a/include/os/osTimezone.h +++ b/include/os/osTimezone.h @@ -25,18 +25,23 @@ extern "C" { extern void* pTimezoneNameMap; +#ifdef WINDOWS +typedef struct void *timezone_t; +#else typedef struct state *timezone_t; struct tm *localtime_rz(timezone_t , time_t const *, struct tm *); time_t mktime_z(timezone_t, struct tm *); timezone_t tzalloc(char const *); void tzfree(timezone_t); - void getTimezoneStr(char *tz); + +#endif + + int32_t taosGetLocalTimezoneOffset(); int32_t taosGetSystemTimezone(char *outTimezone); int32_t taosSetGlobalTimezone(const char *tz); int32_t taosFormatTimezoneStr(time_t t, const char* tzStr, timezone_t sp, char *outTimezoneStr); -int32_t taosIsValidateTimezone(const char *tz); #ifdef __cplusplus } #endif diff --git a/source/client/src/clientMain.c b/source/client/src/clientMain.c index 14d8394e25..d87f43dd2b 100644 --- a/source/client/src/clientMain.c +++ b/source/client/src/clientMain.c @@ -93,9 +93,14 @@ static timezone_t setConnnectionTz(const char* val){ tscDebug("set timezone to %s", val); tz = tzalloc(val); if (tz == NULL) { - tscError("%s unknown timezone %s", __func__, val); - terrno = TAOS_SYSTEM_ERROR(errno); - goto END; + tscWarn("%s unknown timezone %s change to UTC", __func__, val); + val = "UTC"; + tz = tzalloc(val); + if (tz == NULL) { + tscError("%s set timezone %s error", __func__, val); + terrno = TAOS_SYSTEM_ERROR(errno); + goto END; + } } int32_t code = taosHashPut(pTimezoneMap, val, strlen(val), &tz, sizeof(timezone_t)); if (code != 0){ @@ -159,11 +164,6 @@ static int32_t setConnectionOption(TAOS *taos, TSDB_OPTION_CONNECTION option, co if (option == TSDB_OPTION_CONNECTION_TIMEZONE || option == TSDB_OPTION_CONNECTION_CLEAR) { if (val != NULL){ - if (strlen(val) == 0){ - tscError("%s empty timezone %s", __func__, val); - code = TSDB_CODE_INVALID_PARA; - goto END; - } timezone_t tz = setConnnectionTz(val); if (tz == NULL){ code = terrno; diff --git a/source/os/CMakeLists.txt b/source/os/CMakeLists.txt index dbcf0f9080..9dbf940c31 100644 --- a/source/os/CMakeLists.txt +++ b/source/os/CMakeLists.txt @@ -1,6 +1,8 @@ aux_source_directory(src OS_SRC) -aux_source_directory(src/timezone OS_TZ) -add_library(os STATIC ${OS_SRC} ${OS_TZ}) +if(NOT ${TD_WINDOWS}) + aux_source_directory(src/timezone OS_TZ) + add_library(os STATIC ${OS_SRC} ${OS_TZ}) +endif(NOT ${TD_WINDOWS}) target_include_directories( os PUBLIC "${TD_SOURCE_DIR}/include/os" diff --git a/source/os/src/osTimezone.c b/source/os/src/osTimezone.c index 762391c17a..a5a839ea35 100644 --- a/source/os/src/osTimezone.c +++ b/source/os/src/osTimezone.c @@ -33,7 +33,8 @@ #include #pragma warning(pop) -char *win_tz[139][2] = {{"China Standard Time", "Asia/Shanghai"}, +#define W_TZ_NUM 139 +char *win_tz[W_TZ_NUM][2] = {{"China Standard Time", "Asia/Shanghai"}, {"AUS Central Standard Time", "Australia/Darwin"}, {"AUS Eastern Standard Time", "Australia/Sydney"}, {"Afghanistan Standard Time", "Asia/Kabul"}, @@ -172,7 +173,8 @@ char *win_tz[139][2] = {{"China Standard Time", "Asia/Shanghai"}, {"West Pacific Standard Time", "Pacific/Port_Moresby"}, {"Yakutsk Standard Time", "Asia/Yakutsk"}, {"Yukon Standard Time", "America/Whitehorse"}}; -char *tz_win[554][2] = {{"Asia/Shanghai", "China Standard Time"}, +#define W_TZ_CITY_NUM 554 +char *tz_win[W_TZ_CITY_NUM][2] = {{"Asia/Shanghai", "China Standard Time"}, {"Africa/Abidjan", "Greenwich Standard Time"}, {"Africa/Accra", "Greenwich Standard Time"}, {"Africa/Addis_Ababa", "E. Africa Standard Time"}, @@ -740,38 +742,19 @@ char *tz_win[554][2] = {{"Asia/Shanghai", "China Standard Time"}, #include #endif -void parseTimeStr(char *p, char to[5]) { - for (int i = 0; i < 5; ++i) { - if (strlen(p) > i) { - to[i] = p[i]; - } else { - to[i] = '0'; - } - } - if (strlen(p) == 2) { - to[1] = '0'; - to[2] = p[1]; - } -} - -int32_t taosIsValidateTimezone(const char *tz) { - return true; -} - int32_t taosSetGlobalTimezone(const char *tz) { - if (tz == NULL || tz[0] == 0) { + if (tz == NULL) { terrno = TSDB_CODE_INVALID_PARA; return terrno; } int32_t code = TSDB_CODE_SUCCESS; uDebug("[tz]set timezone to %s", tz) #ifdef WINDOWS - char winStr[TD_LOCALE_LEN * 2]; - memset(winStr, 0, sizeof(winStr)); - for (size_t i = 0; i < 554; i++) { - if (strcmp(tz_win[i][0], buf) == 0) { - char keyPath[256]; - char keyValue[100]; + char winStr[TD_TIMEZONE_LEN * 2] = {0}; + for (size_t i = 0; i < W_TZ_CITY_NUM; i++) { + if (strcmp(tz_win[i][0], tz) == 0) { + char keyPath[256] = {0}; + char keyValue[100] = {0}; DWORD keyValueSize = sizeof(keyValue); snprintf(keyPath, sizeof(keyPath), "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\%s", tz_win[i][1]); RegGetValue(HKEY_LOCAL_MACHINE, keyPath, "Display", RRF_RT_ANY, NULL, (PVOID)&keyValue, &keyValueSize); @@ -779,40 +762,13 @@ int32_t taosSetGlobalTimezone(const char *tz) { keyValue[4] = (keyValue[4] == '+' ? '-' : '+'); keyValue[10] = 0; snprintf(winStr, sizeof(winStr), "TZ=%s:00", &(keyValue[1])); - *tsTimezone = -taosStr2Int32(&keyValue[4], NULL, 10); } break; } } - if (winStr[0] == 0) { - char *p = strchr(inTimezoneStr, '+'); - if (p == NULL) p = strchr(inTimezoneStr, '-'); - if (p != NULL) { - char *pp = strchr(inTimezoneStr, '('); - char *ppp = strchr(inTimezoneStr, ','); - int indexStr; - if (pp == NULL || ppp == NULL) { - indexStr = tsnprintf(winStr, sizeof(winStr), "TZ=UTC"); - } else { - memcpy(winStr, "TZ=", 3); - pp++; - memcpy(&winStr[3], pp, ppp - pp); - indexStr = ppp - pp + 3; - } - char to[5]; - parseTimeStr(p, to); - snprintf(&winStr[indexStr], sizeof(winStr) - indexStr, "%c%c%c:%c%c:00", (to[0] == '+' ? '+' : '-'), to[1], to[2], to[3], to[4]); - *tsTimezone = -taosStr2Int32(p, NULL, 10); - } else { - *tsTimezone = 0; - } - } + _putenv(winStr); _tzset(); - if (outTimezoneStr != inTimezoneStr) { - tstrncpy(outTimezoneStr, inTimezoneStr, TD_TIMEZONE_LEN); - } -// *outDaylight = 0; #else code = setenv("TZ", tz, 1); if (-1 == code) { @@ -821,11 +777,10 @@ int32_t taosSetGlobalTimezone(const char *tz) { } tzset(); +#endif time_t tx1 = taosGetTimestampSec(); return taosFormatTimezoneStr(tx1, tz, NULL, tsTimezoneStr); -#endif - } int32_t taosGetLocalTimezoneOffset() { @@ -869,6 +824,7 @@ int32_t taosFormatTimezoneStr(time_t t, const char* tz, timezone_t sp, char *out return 0; } +#ifndef WINDOWS void getTimezoneStr(char *tz) { do { int n = readlink("/etc/localtime", tz, TD_TIMEZONE_LEN - 1); @@ -909,21 +865,20 @@ END: } uDebug("[tz] system timezone:%s", tz); } +#endif int32_t taosGetSystemTimezone(char *outTimezoneStr) { #ifdef WINDOWS - char value[100]; - char keyPath[100]; + char value[100] = {0}; + char keyPath[100] = {0}; DWORD bufferSize = sizeof(value); LONG result = RegGetValue(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", "TimeZoneKeyName", RRF_RT_ANY, NULL, (PVOID)&value, &bufferSize); if (result != ERROR_SUCCESS) { return TAOS_SYSTEM_WINAPI_ERROR(result); } - tstrncpy(outTimezoneStr, "not configured", TD_TIMEZONE_LEN); - *tsTimezone = 0; if (bufferSize > 0) { - for (size_t i = 0; i < 139; i++) { + for (size_t i = 0; i < W_TZ_NUM; i++) { if (strcmp(win_tz[i][0], value) == 0) { tstrncpy(outTimezoneStr, win_tz[i][1], TD_TIMEZONE_LEN); bufferSize = sizeof(value); @@ -932,11 +887,9 @@ int32_t taosGetSystemTimezone(char *outTimezoneStr) { if (result != ERROR_SUCCESS) { return TAOS_SYSTEM_WINAPI_ERROR(result); } - if (bufferSize > 0) { - // value[4] = (value[4] == '+' ? '-' : '+'); - snprintf(outTimezoneStr, TD_TIMEZONE_LEN, "%s (UTC, %c%c%c%c%c)", outTimezoneStr, value[4], value[5], value[6], value[8], - value[9]); - *tsTimezone = taosStr2Int32(&value[4], NULL, 10); + if (bufferSize > 0) { // value like (UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi + snprintf(outTimezoneStr, TD_TIMEZONE_LEN, "%s (UTC, %c%c%c%c%c)", outTimezoneStr, + value[4], value[5], value[6], value[8], value[9]); } break; } diff --git a/source/os/src/timezone/localtime.c b/source/os/src/timezone/localtime.c index e6d79d05d9..492965d9e4 100644 --- a/source/os/src/timezone/localtime.c +++ b/source/os/src/timezone/localtime.c @@ -145,10 +145,31 @@ static char const *utc = etc_utc + sizeof "Etc/" - 1; # define TZDEFRULESTRING ",M3.2.0,M11.1.0" #endif +/* Limit to time zone abbreviation length in proleptic TZ strings. + This is distinct from TZ_MAX_CHARS, which limits TZif file contents. + It defaults to 254, not 255, so that desigidx_type can be an unsigned char. + unsigned char suffices for TZif files, so the only reason to increase + TZNAME_MAXIMUM is to support TZ strings specifying abbreviations + longer than 254 bytes. There is little reason to do that, though, + as strings that long are hardly "abbreviations". */ +#ifndef TZNAME_MAXIMUM +# define TZNAME_MAXIMUM 254 +#endif + +#if TZNAME_MAXIMUM < UCHAR_MAX +typedef unsigned char desigidx_type; +#elif TZNAME_MAXIMUM < INT_MAX +typedef int desigidx_type; +#elif TZNAME_MAXIMUM < PTRDIFF_MAX +typedef ptrdiff_t desigidx_type; +#else +# error "TZNAME_MAXIMUM too large" +#endif + struct ttinfo { /* time type information */ - int_fast32_t tt_utoff; /* UT offset in seconds */ + int_least32_t tt_utoff; /* UT offset in seconds */ + desigidx_type tt_desigidx; /* abbreviation list index */ bool tt_isdst; /* used to set tm_isdst */ - int tt_desigidx; /* abbreviation list index */ bool tt_ttisstd; /* transition is std time */ bool tt_ttisut; /* transition is UT */ }; @@ -167,12 +188,6 @@ static char const UNSPEC[] = "-00"; for ttunspecified to work without crashing. */ enum { CHARS_EXTRA = max(sizeof UNSPEC, 2) - 1 }; -/* Limit to time zone abbreviation length in proleptic TZ strings. - This is distinct from TZ_MAX_CHARS, which limits TZif file contents. */ -#ifndef TZNAME_MAXIMUM -# define TZNAME_MAXIMUM 255 -#endif - /* A representation of the contents of a TZif file. Ideally this would have no size limits; the following sizes should suffice for practical use. This struct should not be too large, as instances @@ -273,7 +288,8 @@ long altzone; /* Initialize *S to a value based on UTOFF, ISDST, and DESIGIDX. */ static void -init_ttinfo(struct ttinfo *s, int_fast32_t utoff, bool isdst, int desigidx) +init_ttinfo(struct ttinfo *s, int_fast32_t utoff, bool isdst, + desigidx_type desigidx) { s->tt_utoff = utoff; s->tt_isdst = isdst; diff --git a/source/os/src/timezone/private.h b/source/os/src/timezone/private.h index da6dc79010..edc188a264 100644 --- a/source/os/src/timezone/private.h +++ b/source/os/src/timezone/private.h @@ -88,11 +88,11 @@ #if !defined HAVE_GETTEXT && defined __has_include # if __has_include() -# define HAVE_GETTEXT true +# define HAVE_GETTEXT 1 # endif #endif #ifndef HAVE_GETTEXT -# define HAVE_GETTEXT false +# define HAVE_GETTEXT 0 #endif #ifndef HAVE_INCOMPATIBLE_CTIME_R @@ -125,20 +125,20 @@ #if !defined HAVE_SYS_STAT_H && defined __has_include # if !__has_include() -# define HAVE_SYS_STAT_H false +# define HAVE_SYS_STAT_H 0 # endif #endif #ifndef HAVE_SYS_STAT_H -# define HAVE_SYS_STAT_H true +# define HAVE_SYS_STAT_H 1 #endif #if !defined HAVE_UNISTD_H && defined __has_include # if !__has_include() -# define HAVE_UNISTD_H false +# define HAVE_UNISTD_H 0 # endif #endif #ifndef HAVE_UNISTD_H -# define HAVE_UNISTD_H true +# define HAVE_UNISTD_H 1 #endif #ifndef NETBSD_INSPIRED @@ -263,6 +263,10 @@ # endif #endif +#ifndef HAVE_SNPRINTF +# define HAVE_SNPRINTF (!PORT_TO_C89 || 199901 <= __STDC_VERSION__) +#endif + #ifndef HAVE_STRFTIME_L # if _POSIX_VERSION < 200809 # define HAVE_STRFTIME_L 0 @@ -308,7 +312,7 @@ ** stdint.h, even with pre-C99 compilers. */ #if !defined HAVE_STDINT_H && defined __has_include -# define HAVE_STDINT_H true /* C23 __has_include implies C99 stdint.h. */ +# define HAVE_STDINT_H 1 /* C23 __has_include implies C99 stdint.h. */ #endif #ifndef HAVE_STDINT_H # define HAVE_STDINT_H \ @@ -378,11 +382,15 @@ typedef int int_fast32_t; # endif #endif +#ifndef INT_LEAST32_MAX +typedef int_fast32_t int_least32_t; +#endif + #ifndef INTMAX_MAX # ifdef LLONG_MAX typedef long long intmax_t; # ifndef HAVE_STRTOLL -# define HAVE_STRTOLL true +# define HAVE_STRTOLL 1 # endif # if HAVE_STRTOLL # define strtoimax strtoll @@ -462,7 +470,7 @@ typedef unsigned long uintmax_t; hosts, unless compiled with -DHAVE_STDCKDINT_H=0 or with pre-C23 EDG. */ #if !defined HAVE_STDCKDINT_H && defined __has_include # if __has_include() -# define HAVE_STDCKDINT_H true +# define HAVE_STDCKDINT_H 1 # endif #endif #ifdef HAVE_STDCKDINT_H @@ -630,9 +638,9 @@ typedef unsigned long uintmax_t; ** typical platforms. */ #if defined time_tz || EPOCH_LOCAL || EPOCH_OFFSET != 0 -# define TZ_TIME_T 1 +# define TZ_TIME_T true #else -# define TZ_TIME_T 0 +# define TZ_TIME_T false #endif #if defined LOCALTIME_IMPLEMENTATION && TZ_TIME_T @@ -749,9 +757,9 @@ void tzset(void); || defined __GLIBC__ || defined __tm_zone /* musl */ \ || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \ || (defined __APPLE__ && defined __MACH__)) -# define HAVE_DECL_TIMEGM true +# define HAVE_DECL_TIMEGM 1 # else -# define HAVE_DECL_TIMEGM false +# define HAVE_DECL_TIMEGM 0 # endif #endif #if !HAVE_DECL_TIMEGM && !defined timegm diff --git a/source/os/src/timezone/strftime.c b/source/os/src/timezone/strftime.c index fc0c87d20a..487a5234cb 100644 --- a/source/os/src/timezone/strftime.c +++ b/source/os/src/timezone/strftime.c @@ -79,9 +79,9 @@ !defined __NetBSD_Version__ || __NetBSD_Version__ < 600000000) \ && (/* OpenBSD 5.5 (2014-05-01) and later has 64-bit time_t. */ \ !defined OpenBSD || OpenBSD < 201405))) -# define MKTIME_MIGHT_OVERFLOW true +# define MKTIME_MIGHT_OVERFLOW 1 # else -# define MKTIME_MIGHT_OVERFLOW false +# define MKTIME_MIGHT_OVERFLOW 0 # endif #endif /* Check that MKTIME_MIGHT_OVERFLOW is consistent with time_t's range. */ @@ -95,7 +95,7 @@ static_assert(MKTIME_MIGHT_OVERFLOW #endif #ifndef DEPRECATE_TWO_DIGIT_YEARS -# define DEPRECATE_TWO_DIGIT_YEARS false +# define DEPRECATE_TWO_DIGIT_YEARS 0 #endif struct lc_time_T { diff --git a/source/util/src/tconfig.c b/source/util/src/tconfig.c index 57895d0cca..f12f17853a 100644 --- a/source/util/src/tconfig.c +++ b/source/util/src/tconfig.c @@ -251,13 +251,9 @@ static int32_t cfgSetTimezone(SConfigItem *pItem, const char *value, ECfgSrcType TAOS_RETURN(TSDB_CODE_INVALID_CFG); } - if(!taosIsValidateTimezone(value)){ - uError("invalid timezone:%s", value); - TAOS_RETURN(TSDB_CODE_INVALID_TIMEZONE); - } - if (value == NULL || strlen(value) == 0) { - uError("cfg:%s, type:%s src:%s, value:%s, skip to set timezone", pItem->name, cfgDtypeStr(pItem->dtype), - cfgStypeStr(stype), value); + if (value == NULL) { + uError("cfg:%s, type:%s src:%s, value is null, skip to set timezone", pItem->name, cfgDtypeStr(pItem->dtype), + cfgStypeStr(stype)); TAOS_RETURN(TSDB_CODE_INVALID_CFG); } TAOS_CHECK_RETURN(osSetTimezone(value)); From 61f11ddb4e28c421a27974d6f94e32bec84b5e2f Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Mon, 2 Dec 2024 15:33:03 +0800 Subject: [PATCH 19/45] feat:[TD-32642] add timezone support in windows --- source/os/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/os/CMakeLists.txt b/source/os/CMakeLists.txt index 9dbf940c31..0374df3487 100644 --- a/source/os/CMakeLists.txt +++ b/source/os/CMakeLists.txt @@ -2,6 +2,8 @@ aux_source_directory(src OS_SRC) if(NOT ${TD_WINDOWS}) aux_source_directory(src/timezone OS_TZ) add_library(os STATIC ${OS_SRC} ${OS_TZ}) +else() + add_library(os STATIC ${OS_SRC}) endif(NOT ${TD_WINDOWS}) target_include_directories( os From adff8a60d1648e10124319a43c415bf318e82977 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Mon, 2 Dec 2024 18:49:06 +0800 Subject: [PATCH 20/45] feat:[TD-32642] add timezone support in windows --- include/os/osTimezone.h | 2 +- include/util/taoserror.h | 2 +- source/client/src/clientMain.c | 26 ++++- source/client/test/timezoneTest.cpp | 21 +++- source/common/src/ttime.c | 19 +++- source/os/src/osTime.c | 107 ++---------------- source/os/src/osTimezone.c | 4 + source/util/src/terror.c | 2 +- .../0-others/information_schema.py | 2 +- tests/system-test/2-query/timezone.py | 4 +- 10 files changed, 77 insertions(+), 112 deletions(-) diff --git a/include/os/osTimezone.h b/include/os/osTimezone.h index c9cb55207c..82f4b7b30b 100644 --- a/include/os/osTimezone.h +++ b/include/os/osTimezone.h @@ -26,7 +26,7 @@ extern "C" { extern void* pTimezoneNameMap; #ifdef WINDOWS -typedef struct void *timezone_t; +typedef void *timezone_t; #else typedef struct state *timezone_t; struct tm *localtime_rz(timezone_t , time_t const *, struct tm *); diff --git a/include/util/taoserror.h b/include/util/taoserror.h index 73a38ac38f..9b1e0d7320 100644 --- a/include/util/taoserror.h +++ b/include/util/taoserror.h @@ -1027,7 +1027,7 @@ int32_t taosGetErrSize(); #define TSDB_CODE_AUDIT_FAIL_GENERATE_JSON TAOS_DEF_ERROR_CODE(0, 0x6102) //TIMEZONE -#define TSDB_CODE_INVALID_TIMEZONE TAOS_DEF_ERROR_CODE(0, 0x6200) +#define TSDB_CODE_NOT_SUPPORTTED_IN_WINDOWS TAOS_DEF_ERROR_CODE(0, 0x6200) #ifdef __cplusplus } diff --git a/source/client/src/clientMain.c b/source/client/src/clientMain.c index d87f43dd2b..13cf39f924 100644 --- a/source/client/src/clientMain.c +++ b/source/client/src/clientMain.c @@ -56,6 +56,12 @@ int taos_options(TSDB_OPTION option, const void *arg, ...) { return ret; } +#ifndef WINDOWS +static void freeTz(void *p){ + timezone_t tz = *(timezone_t *)p; + tzfree(tz); +} + static timezone_t setConnnectionTz(const char* val){ timezone_t tz = NULL; static int32_t lock_c = 0; @@ -73,7 +79,7 @@ static timezone_t setConnnectionTz(const char* val){ atomic_store_32(&lock_c, 0); goto END; } - taosHashSetFreeFp(pTimezoneMap, (_hash_free_fn_t)tzfree); + taosHashSetFreeFp(pTimezoneMap, freeTz); } if (pTimezoneNameMap == NULL){ @@ -94,10 +100,9 @@ static timezone_t setConnnectionTz(const char* val){ tz = tzalloc(val); if (tz == NULL) { tscWarn("%s unknown timezone %s change to UTC", __func__, val); - val = "UTC"; - tz = tzalloc(val); + tz = tzalloc("UTC"); if (tz == NULL) { - tscError("%s set timezone %s error", __func__, val); + tscError("%s set timezone UTC error", __func__); terrno = TAOS_SYSTEM_ERROR(errno); goto END; } @@ -120,11 +125,19 @@ END: atomic_store_32(&lock_c, 0); return tz; } +#endif + static int32_t setConnectionOption(TAOS *taos, TSDB_OPTION_CONNECTION option, const char* val){ if (taos == NULL) { return TSDB_CODE_INVALID_PARA; } +#ifdef WINDOWS + if (option == TSDB_OPTION_CONNECTION_TIMEZONE){ + return TSDB_CODE_NOT_SUPPORTTED_IN_WINDOWS; + } +#endif + if (option < TSDB_OPTION_CONNECTION_CLEAR || option >= TSDB_MAX_OPTIONS_CONNECTION){ return TSDB_CODE_INVALID_PARA; } @@ -163,7 +176,11 @@ static int32_t setConnectionOption(TAOS *taos, TSDB_OPTION_CONNECTION option, co } if (option == TSDB_OPTION_CONNECTION_TIMEZONE || option == TSDB_OPTION_CONNECTION_CLEAR) { +#ifndef WINDOWS if (val != NULL){ + if (val[0] == 0){ + val = "UTC"; + } timezone_t tz = setConnnectionTz(val); if (tz == NULL){ code = terrno; @@ -173,6 +190,7 @@ static int32_t setConnectionOption(TAOS *taos, TSDB_OPTION_CONNECTION option, co } else { pObj->optionInfo.timezone = NULL; } +#endif } if (option == TSDB_OPTION_CONNECTION_USER_APP || option == TSDB_OPTION_CONNECTION_CLEAR) { diff --git a/source/client/test/timezoneTest.cpp b/source/client/test/timezoneTest.cpp index 84ee53452a..382b28e275 100644 --- a/source/client/test/timezoneTest.cpp +++ b/source/client/test/timezoneTest.cpp @@ -225,7 +225,9 @@ TEST(timezoneCase, setConnectionOption_Test) { // test timezone code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_TIMEZONE, ""); - ASSERT(code != 0); + ASSERT(code == 0); + CHECK_TAOS_OPTION_POINTER(pConn, timezone, false); + check_sql_result(pConn, "select timezone()", "UTC (UTC, +0000)"); code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_TIMEZONE, NULL); ASSERT(code == 0); @@ -243,7 +245,9 @@ TEST(timezoneCase, setConnectionOption_Test) { check_sql_result(pConn, "select timezone()", "Asia/Kolkata (IST, +0530)"); code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_TIMEZONE, "adbc"); - ASSERT(code != 0); + ASSERT(code == 0); + CHECK_TAOS_OPTION_POINTER(pConn, timezone, false); + check_sql_result(pConn, "select timezone()", "adbc (UTC, +0000)"); // test user APP code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_USER_APP, ""); @@ -593,6 +597,19 @@ TEST(timezoneCase, func_timezone_Test) { execQuery(pConn, "insert into db1.ntb1 values(1704070200000, '2023-12-31 23:50:00', 11)"); // 2023-12-31 23:50:00-0100 checkRows(pConn, "select a.ts,b.ts from db1.ntb a join db1.ntb1 b on timetruncate(a.ts, 1d) = timetruncate(b.ts, 1d)", 1); + // operator +1n +1y + check_sql_result(pConn, "select TO_ISO8601(CAST('2023-01-31T00:00:00.000-01' as timestamp) + 1n)", "2023-02-28T00:00:00.000-0100"); + check_sql_result(pConn, "select TO_ISO8601(CAST('2024-01-31T00:00:00.000-01' as timestamp) + 1n)", "2024-02-29T00:00:00.000-0100"); + check_sql_result(pConn, "select TO_ISO8601(CAST('2024-02-29T00:00:00.000-01' as timestamp) + 1y)", "2025-02-28T00:00:00.000-0100"); + check_sql_result(pConn, "select TO_ISO8601(CAST('2024-01-31T00:00:00.000-01' as timestamp) + 1y)", "2025-01-31T00:00:00.000-0100"); + + check_sql_result(pConn, "select TO_ISO8601(CAST('2024-01-01T00:00:00.000+01' as timestamp) + 1n)", "2024-01-31T22:00:00.000-0100"); + check_sql_result(pConn, "select TO_ISO8601(CAST('2024-01-01T00:00:00.000+01' as timestamp) + 1y)", "2024-12-31T22:00:00.000-0100"); + + // case when + check_sql_result_integer(pConn, "select case CAST('2024-01-01T00:00:00.000+01' as timestamp) when 1704063600000 then 1 end", 1); + check_sql_result_integer(pConn, "select case CAST('2024-01-01T00:00:00.000' as timestamp) when 1704070800000 then 1 end", 1); + taos_close(pConn); } diff --git a/source/common/src/ttime.c b/source/common/src/ttime.c index 45b5ab6564..db3f7fec32 100644 --- a/source/common/src/ttime.c +++ b/source/common/src/ttime.c @@ -1408,11 +1408,17 @@ static int32_t tm2char(const SArray* formats, const struct STm* tm, char* s, int (void)sprintf(s, "%09" PRId64, tm->fsec); s += 9; break; - case TSFKW_TZH: - (void)sprintf(s, "%c%02d", (tm->tm.tm_gmtoff >= 0) ? '+' : '-', - abs((int) tm->tm.tm_gmtoff) / 3600); + case TSFKW_TZH:{ +#ifdef WINDOWS + int32_t gmtoff = -_timezone; +#else + int32_t gmtoff = tm->tm.tm_gmtoff; +#endif + (void)sprintf(s, "%c%02d", (gmtoff >= 0) ? '+' : '-', + abs(gmtoff) / 3600); s += strlen(s); break; + } case TSFKW_YYYY: (void)sprintf(s, "%04d", tm->tm.tm_year + 1900); s += strlen(s); @@ -1832,7 +1838,12 @@ static int32_t char2ts(const char* s, SArray* formats, int64_t* ts, int32_t prec tm.fsec = ms * 1000000 + us * 1000 + ns; int32_t ret = taosTm2Ts(&tm, ts, precision, tz); if (tzHour != 0) { - *ts += (tm.tm.tm_gmtoff - tzHour * 3600) * TICK_PER_SECOND[precision]; +#ifdef WINDOWS + int32_t gmtoff = -_timezone; +#else + int32_t gmtoff = tm.tm.tm_gmtoff; +#endif + *ts += (gmtoff - tzHour * 3600) * TICK_PER_SECOND[precision]; } return ret; } diff --git a/source/os/src/osTime.c b/source/os/src/osTime.c index f25631f932..2720eab56e 100644 --- a/source/os/src/osTime.c +++ b/source/os/src/osTime.c @@ -422,56 +422,18 @@ int64_t user_mktime64(const uint32_t year, const uint32_t mon, const uint32_t da time_t taosMktime(struct tm *timep, timezone_t tz) { #ifdef WINDOWS -#if 0 - struct tm tm1 = {0}; - LARGE_INTEGER t; - FILETIME f; - SYSTEMTIME s; - FILETIME ff; - SYSTEMTIME ss; - LARGE_INTEGER offset; - - time_t tt = 0; - localtime_s(&tm1, &tt); - ss.wYear = tm1.tm_year + 1900; - ss.wMonth = tm1.tm_mon + 1; - ss.wDay = tm1.tm_mday; - ss.wHour = tm1.tm_hour; - ss.wMinute = tm1.tm_min; - ss.wSecond = tm1.tm_sec; - ss.wMilliseconds = 0; - SystemTimeToFileTime(&ss, &ff); - offset.QuadPart = ff.dwHighDateTime; - offset.QuadPart <<= 32; - offset.QuadPart |= ff.dwLowDateTime; - - s.wYear = timep->tm_year + 1900; - s.wMonth = timep->tm_mon + 1; - s.wDay = timep->tm_mday; - s.wHour = timep->tm_hour; - s.wMinute = timep->tm_min; - s.wSecond = timep->tm_sec; - s.wMilliseconds = 0; - SystemTimeToFileTime(&s, &f); - t.QuadPart = f.dwHighDateTime; - t.QuadPart <<= 32; - t.QuadPart |= f.dwLowDateTime; - - t.QuadPart -= offset.QuadPart; - return (time_t)(t.QuadPart / 10000000); -#else time_t result = mktime(timep); if (result != -1) { return result; } -#ifdef _MSC_VER -#if _MSC_VER >= 1900 - int64_t tz = _timezone; -#endif -#endif + int64_t tzw = 0; + #ifdef _MSC_VER + #if _MSC_VER >= 1900 + tzw = _timezone; + #endif + #endif return user_mktime64(timep->tm_year + 1900, timep->tm_mon + 1, timep->tm_mday, timep->tm_hour, timep->tm_min, - timep->tm_sec, tz); -#endif + timep->tm_sec, tzw); #else time_t r = tz != NULL ? mktime_z(tz, timep) : mktime(timep); if (r == (time_t)-1) { @@ -486,56 +448,7 @@ struct tm *taosGmTimeR(const time_t *timep, struct tm *result){ return NULL; } #ifdef WINDOWS - if (*timep < -2208988800LL) { - if (buf != NULL) { - snprintf(buf, bufSize, "NaN"); - } - return NULL; - } else if (*timep < 0) { - SYSTEMTIME ss, s; - FILETIME ff, f; - - LARGE_INTEGER offset; - struct tm tm1; - time_t tt = 0; - if (localtime_s(&tm1, &tt) != 0) { - if (buf != NULL) { - snprintf(buf, bufSize, "NaN"); - } - return NULL; - } - ss.wYear = tm1.tm_year + 1900; - ss.wMonth = tm1.tm_mon + 1; - ss.wDay = tm1.tm_mday; - ss.wHour = tm1.tm_hour; - ss.wMinute = tm1.tm_min; - ss.wSecond = tm1.tm_sec; - ss.wMilliseconds = 0; - SystemTimeToFileTime(&ss, &ff); - offset.QuadPart = ff.dwHighDateTime; - offset.QuadPart <<= 32; - offset.QuadPart |= ff.dwLowDateTime; - offset.QuadPart += *timep * 10000000; - f.dwLowDateTime = offset.QuadPart & 0xffffffff; - f.dwHighDateTime = (offset.QuadPart >> 32) & 0xffffffff; - FileTimeToSystemTime(&f, &s); - result->tm_sec = s.wSecond; - result->tm_min = s.wMinute; - result->tm_hour = s.wHour; - result->tm_mday = s.wDay; - result->tm_mon = s.wMonth - 1; - result->tm_year = s.wYear - 1900; - result->tm_wday = s.wDayOfWeek; - result->tm_yday = 0; - result->tm_isdst = 0; - } else { - if (localtime_s(result, timep) != 0) { - if (buf != NULL) { - snprintf(buf, bufSize, "NaN"); - } - return NULL; - } - } + return gmtime_s(result, timep); #else return gmtime_r(timep, result); #endif @@ -545,7 +458,11 @@ time_t taosTimeGm(struct tm *tmp){ if (tmp == NULL) { return -1; } +#ifdef WINDOWS + return _mkgmtime(tmp); +#else return timegm(tmp); +#endif } struct tm *taosLocalTime(const time_t *timep, struct tm *result, char *buf, int32_t bufSize, timezone_t tz) { diff --git a/source/os/src/osTimezone.c b/source/os/src/osTimezone.c index a5a839ea35..d47c0ac663 100644 --- a/source/os/src/osTimezone.c +++ b/source/os/src/osTimezone.c @@ -790,7 +790,11 @@ int32_t taosGetLocalTimezoneOffset() { uError("%s failed to get local time: code:%d", __FUNCTION__, errno); return TSDB_CODE_TIME_ERROR; } +#ifdef WINDOWS + return -_timezone; +#else return (int32_t)(tm1.tm_gmtoff); +#endif } int32_t taosFormatTimezoneStr(time_t t, const char* tz, timezone_t sp, char *outTimezoneStr){ diff --git a/source/util/src/terror.c b/source/util/src/terror.c index a3cb3e80bf..ca498a9c75 100644 --- a/source/util/src/terror.c +++ b/source/util/src/terror.c @@ -871,7 +871,7 @@ TAOS_DEFINE_ERROR(TSDB_CODE_AUDIT_FAIL_SEND_AUDIT_RECORD, "Failed to send out TAOS_DEFINE_ERROR(TSDB_CODE_AUDIT_FAIL_GENERATE_JSON, "Failed to generate json") //TIMEZONE -TAOS_DEFINE_ERROR(TSDB_CODE_INVALID_TIMEZONE, "Invalid timezone") +TAOS_DEFINE_ERROR(TSDB_CODE_NOT_SUPPORTTED_IN_WINDOWS,"Operation not supported in windows") #ifdef TAOS_ERROR_C }; #endif diff --git a/tests/system-test/0-others/information_schema.py b/tests/system-test/0-others/information_schema.py index 538aa1ad63..f5378b189e 100644 --- a/tests/system-test/0-others/information_schema.py +++ b/tests/system-test/0-others/information_schema.py @@ -225,7 +225,7 @@ class TDTestCase: tdSql.checkEqual(True, len(tdSql.queryResult) in range(282, 283)) tdSql.query("select * from information_schema.ins_columns where db_name ='performance_schema'") - tdSql.checkEqual(56, len(tdSql.queryResult)) + tdSql.checkEqual(60, len(tdSql.queryResult)) def ins_dnodes_check(self): tdSql.execute('drop database if exists db2') diff --git a/tests/system-test/2-query/timezone.py b/tests/system-test/2-query/timezone.py index 88c16fb163..e08e85dcf5 100644 --- a/tests/system-test/2-query/timezone.py +++ b/tests/system-test/2-query/timezone.py @@ -142,9 +142,7 @@ class TDTestCase: tdSql.query(f"select ts from {self.dbname}.d5") tdSql.checkData(0, 0, "2021-07-01 20:00:00.000") - tdSql.execute(f"insert into {self.dbname}.d6 using {self.dbname}.stb tags (1) values ('2021-07-01T00:00:00.000-115',1)") - tdSql.query(f"select ts from {self.dbname}.d6") - tdSql.checkData(0, 0, "2021-07-01 19:05:00.000") + tdSql.error(f"insert into {self.dbname}.d6 using {self.dbname}.stb tags (1) values ('2021-07-01T00:00:00.000-115',1)") tdSql.execute(f"insert into {self.dbname}.d7 using {self.dbname}.stb tags (1) values ('2021-07-01T00:00:00.000-1105',1)") tdSql.query(f"select ts from {self.dbname}.d7") From 074f39f4f6443498105a29ab7355f6e0be566fad Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Tue, 3 Dec 2024 18:28:36 +0800 Subject: [PATCH 21/45] feat:[TD-32642] add timezone support in windows --- cmake/cmake.define | 6 - contrib/CMakeLists.txt | 20 +- include/os/osString.h | 2 +- include/os/osTimezone.h | 9 +- packaging/tools/make_install.bat | 4 - packaging/tools/make_install.sh | 8 - source/os/CMakeLists.txt | 12 +- source/os/src/timezone/localtime.c | 2514 ---------------------------- source/os/src/timezone/private.h | 1088 ------------ source/os/src/timezone/strftime.c | 711 -------- source/os/src/timezone/tzdir.h | 6 - source/os/src/timezone/tzfile.h | 121 -- 12 files changed, 26 insertions(+), 4475 deletions(-) delete mode 100644 source/os/src/timezone/localtime.c delete mode 100644 source/os/src/timezone/private.h delete mode 100644 source/os/src/timezone/strftime.c delete mode 100644 source/os/src/timezone/tzdir.h delete mode 100644 source/os/src/timezone/tzfile.h diff --git a/cmake/cmake.define b/cmake/cmake.define index 1eb537a7e0..eb95feaf82 100644 --- a/cmake/cmake.define +++ b/cmake/cmake.define @@ -106,12 +106,6 @@ ELSE() SET(TAOS_LIB_PLATFORM_SPEC taos) ENDIF() -IF(NOT DEFINED TZ_OUTPUT_PATH) - SET(TZ_OUTPUT_PATH ${PROJECT_BINARY_DIR}/build/share/timezone) -ENDIF() -MESSAGE(STATUS "timezone output path: " ${TZ_OUTPUT_PATH}) - - # build TSZ by default IF("${TSZ_ENABLED}" MATCHES "false") set(VAR_TSZ "" CACHE INTERNAL "global variant empty") diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index 345f23943d..c5ed4cf52d 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -655,17 +655,25 @@ if(${TD_LINUX} AND ${BUILD_WITH_S3}) add_subdirectory(azure-cmake EXCLUDE_FROM_ALL) endif() +IF(TD_LINUX) + SET(TZ_OUTPUT_PATH /usr/share/zoneinfo) +ELSEIF(TD_DARWIN) + SET(TZ_OUTPUT_PATH /var/db/timezone/zoneinfo) +ENDIF() + +MESSAGE(STATUS "timezone file path: " ${TZ_OUTPUT_PATH}) + if(NOT ${TD_WINDOWS}) execute_process( - COMMAND make TZDIR=${TZ_OUTPUT_PATH}/ clean zic posix_only + COMMAND make TZDIR=${TZ_OUTPUT_PATH}/ clean libtz.a WORKING_DIRECTORY "${TD_CONTRIB_DIR}/tz" ) - set(TZ_SRC_DIR "${TD_SOURCE_DIR}/source/os/src/timezone") - file(MAKE_DIRECTORY ${TZ_SRC_DIR}) - file(COPY ${TD_CONTRIB_DIR}/tz/private.h ${TD_CONTRIB_DIR}/tz/tzdir.h ${TD_CONTRIB_DIR}/tz/tzfile.h - ${TD_CONTRIB_DIR}/tz/localtime.c ${TD_CONTRIB_DIR}/tz/strftime.c - DESTINATION ${TZ_SRC_DIR}) +# set(TZ_SRC_DIR "${TD_SOURCE_DIR}/source/os/src/timezone") +# file(MAKE_DIRECTORY ${TZ_SRC_DIR}) +# file(COPY ${TD_CONTRIB_DIR}/tz/private.h ${TD_CONTRIB_DIR}/tz/tzdir.h ${TD_CONTRIB_DIR}/tz/tzfile.h +# ${TD_CONTRIB_DIR}/tz/localtime.c ${TD_CONTRIB_DIR}/tz/strftime.c +# DESTINATION ${TZ_SRC_DIR}) endif(NOT ${TD_WINDOWS}) # ================================================================================================ # Build test diff --git a/include/os/osString.h b/include/os/osString.h index 1fe83b7e3b..c30437ee2c 100644 --- a/include/os/osString.h +++ b/include/os/osString.h @@ -22,7 +22,7 @@ extern "C" { typedef wchar_t TdWchar; typedef int32_t TdUcs4; -#if !defined(DISALLOW_NCHAR_WITHOUT_ICONV) +#if !defined(DISALLOW_NCHAR_WITHOUT_ICONV)// && defined(DARWIN) #include "iconv.h" #else typedef void *iconv_t; diff --git a/include/os/osTimezone.h b/include/os/osTimezone.h index 82f4b7b30b..f770a9dba0 100644 --- a/include/os/osTimezone.h +++ b/include/os/osTimezone.h @@ -29,11 +29,12 @@ extern void* pTimezoneNameMap; typedef void *timezone_t; #else typedef struct state *timezone_t; -struct tm *localtime_rz(timezone_t , time_t const *, struct tm *); -time_t mktime_z(timezone_t, struct tm *); + +struct tm* localtime_rz(timezone_t , time_t const *, struct tm *); +time_t mktime_z(timezone_t, struct tm *); timezone_t tzalloc(char const *); -void tzfree(timezone_t); -void getTimezoneStr(char *tz); +void tzfree(timezone_t); +void getTimezoneStr(char *tz); #endif diff --git a/packaging/tools/make_install.bat b/packaging/tools/make_install.bat index ed4fc982b4..04d342ea06 100644 --- a/packaging/tools/make_install.bat +++ b/packaging/tools/make_install.bat @@ -43,9 +43,6 @@ if not exist %target_dir%\\cfg ( if not exist %target_dir%\\include ( mkdir %target_dir%\\include ) -if not exist %target_dir%\\share ( - mkdir %target_dir%\\share -) if not exist %target_dir%\\driver ( mkdir %target_dir%\\driver ) @@ -70,7 +67,6 @@ copy %binary_dir%\\build\\lib\\taos.lib %target_dir%\\driver > nul copy %binary_dir%\\build\\lib\\taos_static.lib %target_dir%\\driver > nul copy %binary_dir%\\build\\lib\\taos.dll %target_dir%\\driver > nul copy %binary_dir%\\build\\bin\\taos.exe %target_dir% > nul -xcopy %binary_dir%\\build\\share\\* %target_dir%\\share > nul if exist %binary_dir%\\build\\bin\\taosBenchmark.exe ( copy %binary_dir%\\build\\bin\\taosBenchmark.exe %target_dir% > nul ) diff --git a/packaging/tools/make_install.sh b/packaging/tools/make_install.sh index 14ced5e676..0bc82cdbd1 100755 --- a/packaging/tools/make_install.sh +++ b/packaging/tools/make_install.sh @@ -53,7 +53,6 @@ else fi fi -timezone_dir="/usr/local/share/timezone" install_main_dir=${installDir} bin_dir="${installDir}/bin" cfg_dir="${installDir}/cfg" @@ -387,11 +386,6 @@ function install_header() { ${csudo}chmod 644 ${install_main_dir}/include/* } -function install_timezone(){ - ${csudo}mkdir -p ${timezone_dir} - ${csudo}cp -rf ${binary_dir}/build/share/timezone/* ${timezone_dir} && ${csudo}chmod 644 ${timezone_dir}/* -} - function install_config() { if [ ! -f ${cfg_install_dir}/${configFile} ]; then ${csudo}mkdir -p ${cfg_install_dir} @@ -648,7 +642,6 @@ function update_TDengine() { install_log install_header install_lib - install_timezone # install_connector install_examples install_bin @@ -714,7 +707,6 @@ function install_TDengine() { install_log install_header install_lib - install_timezone # install_connector install_examples install_bin diff --git a/source/os/CMakeLists.txt b/source/os/CMakeLists.txt index 0374df3487..220fef072b 100644 --- a/source/os/CMakeLists.txt +++ b/source/os/CMakeLists.txt @@ -1,10 +1,5 @@ aux_source_directory(src OS_SRC) -if(NOT ${TD_WINDOWS}) - aux_source_directory(src/timezone OS_TZ) - add_library(os STATIC ${OS_SRC} ${OS_TZ}) -else() - add_library(os STATIC ${OS_SRC}) -endif(NOT ${TD_WINDOWS}) +add_library(os STATIC ${OS_SRC}) target_include_directories( os PUBLIC "${TD_SOURCE_DIR}/include/os" @@ -75,6 +70,11 @@ IF (JEMALLOC_ENABLED) target_link_libraries(os PUBLIC -L${CMAKE_BINARY_DIR}/build/lib -ljemalloc) ENDIF () +if(NOT ${TD_WINDOWS}) + find_library(tz libtz.a "${TD_SOURCE_DIR}/contrib/tz") + target_link_libraries(os PUBLIC ${tz}) +endif(NOT ${TD_WINDOWS}) + if(${BUILD_TEST}) add_subdirectory(test) endif(${BUILD_TEST}) diff --git a/source/os/src/timezone/localtime.c b/source/os/src/timezone/localtime.c deleted file mode 100644 index 492965d9e4..0000000000 --- a/source/os/src/timezone/localtime.c +++ /dev/null @@ -1,2514 +0,0 @@ -/* Convert timestamp from time_t to struct tm. */ - -/* -** This file is in the public domain, so clarified as of -** 1996-06-05 by Arthur David Olson. -*/ - -/* -** Leap second handling from Bradley White. -** POSIX.1-1988 style TZ environment variable handling from Guy Harris. -*/ - -/*LINTLIBRARY*/ - -#define LOCALTIME_IMPLEMENTATION -#include "private.h" - -#include "tzdir.h" -#include "tzfile.h" -#include - -#if defined THREAD_SAFE && THREAD_SAFE -# include -static pthread_mutex_t locallock = PTHREAD_MUTEX_INITIALIZER; -static int lock(void) { return pthread_mutex_lock(&locallock); } -static void unlock(void) { pthread_mutex_unlock(&locallock); } -#else -static int lock(void) { return 0; } -static void unlock(void) { } -#endif - -/* A signed type wider than int, so that we can add 1900 + tm_mon/12 to tm_year - without overflow. The static_assert checks that it is indeed wider - than int; if this fails on your platform please let us know. */ -#if INT_MAX < LONG_MAX -typedef long iinntt; -# define IINNTT_MIN LONG_MIN -# define IINNTT_MAX LONG_MAX -#elif INT_MAX < LLONG_MAX -typedef long long iinntt; -# define IINNTT_MIN LLONG_MIN -# define IINNTT_MAX LLONG_MAX -#else -typedef intmax_t iinntt; -# define IINNTT_MIN INTMAX_MIN -# define IINNTT_MAX INTMAX_MAX -#endif -static_assert(IINNTT_MIN < INT_MIN && INT_MAX < IINNTT_MAX); - -/* On platforms where offtime or mktime might overflow, - strftime.c defines USE_TIMEX_T to be true and includes us. - This tells us to #define time_t to an internal type timex_t that is - wide enough so that strftime %s never suffers from integer overflow, - and to #define offtime (if TM_GMTOFF is defined) or mktime (otherwise) - to a static function that returns the redefined time_t. - It also tells us to define only data and code needed - to support the offtime or mktime variant. */ -#ifndef USE_TIMEX_T -# define USE_TIMEX_T false -#endif -#if USE_TIMEX_T -# undef TIME_T_MIN -# undef TIME_T_MAX -# undef time_t -# define time_t timex_t -# if MKTIME_FITS_IN(LONG_MIN, LONG_MAX) -typedef long timex_t; -# define TIME_T_MIN LONG_MIN -# define TIME_T_MAX LONG_MAX -# elif MKTIME_FITS_IN(LLONG_MIN, LLONG_MAX) -typedef long long timex_t; -# define TIME_T_MIN LLONG_MIN -# define TIME_T_MAX LLONG_MAX -# else -typedef intmax_t timex_t; -# define TIME_T_MIN INTMAX_MIN -# define TIME_T_MAX INTMAX_MAX -# endif - -# ifdef TM_GMTOFF -# undef timeoff -# define timeoff timex_timeoff -# undef EXTERN_TIMEOFF -# else -# undef mktime -# define mktime timex_mktime -# endif -#endif - -#ifndef TZ_ABBR_CHAR_SET -# define TZ_ABBR_CHAR_SET \ - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 :+-._" -#endif /* !defined TZ_ABBR_CHAR_SET */ - -#ifndef TZ_ABBR_ERR_CHAR -# define TZ_ABBR_ERR_CHAR '_' -#endif /* !defined TZ_ABBR_ERR_CHAR */ - -/* -** Support non-POSIX platforms that distinguish between text and binary files. -*/ - -#ifndef O_BINARY -# define O_BINARY 0 -#endif - -#ifndef WILDABBR -/* -** Someone might make incorrect use of a time zone abbreviation: -** 1. They might reference tzname[0] before calling tzset (explicitly -** or implicitly). -** 2. They might reference tzname[1] before calling tzset (explicitly -** or implicitly). -** 3. They might reference tzname[1] after setting to a time zone -** in which Daylight Saving Time is never observed. -** 4. They might reference tzname[0] after setting to a time zone -** in which Standard Time is never observed. -** 5. They might reference tm.TM_ZONE after calling offtime. -** What's best to do in the above cases is open to debate; -** for now, we just set things up so that in any of the five cases -** WILDABBR is used. Another possibility: initialize tzname[0] to the -** string "tzname[0] used before set", and similarly for the other cases. -** And another: initialize tzname[0] to "ERA", with an explanation in the -** manual page of what this "time zone abbreviation" means (doing this so -** that tzname[0] has the "normal" length of three characters). -*/ -# define WILDABBR " " -#endif /* !defined WILDABBR */ - -static const char wildabbr[] = WILDABBR; - -static char const etc_utc[] = "Etc/UTC"; - -#if defined TM_ZONE || ((!USE_TIMEX_T || !defined TM_GMTOFF) && defined TZ_NAME) -static char const *utc = etc_utc + sizeof "Etc/" - 1; -#endif - -/* -** The DST rules to use if TZ has no rules and we can't load TZDEFRULES. -** Default to US rules as of 2017-05-07. -** POSIX does not specify the default DST rules; -** for historical reasons, US rules are a common default. -*/ -#ifndef TZDEFRULESTRING -# define TZDEFRULESTRING ",M3.2.0,M11.1.0" -#endif - -/* Limit to time zone abbreviation length in proleptic TZ strings. - This is distinct from TZ_MAX_CHARS, which limits TZif file contents. - It defaults to 254, not 255, so that desigidx_type can be an unsigned char. - unsigned char suffices for TZif files, so the only reason to increase - TZNAME_MAXIMUM is to support TZ strings specifying abbreviations - longer than 254 bytes. There is little reason to do that, though, - as strings that long are hardly "abbreviations". */ -#ifndef TZNAME_MAXIMUM -# define TZNAME_MAXIMUM 254 -#endif - -#if TZNAME_MAXIMUM < UCHAR_MAX -typedef unsigned char desigidx_type; -#elif TZNAME_MAXIMUM < INT_MAX -typedef int desigidx_type; -#elif TZNAME_MAXIMUM < PTRDIFF_MAX -typedef ptrdiff_t desigidx_type; -#else -# error "TZNAME_MAXIMUM too large" -#endif - -struct ttinfo { /* time type information */ - int_least32_t tt_utoff; /* UT offset in seconds */ - desigidx_type tt_desigidx; /* abbreviation list index */ - bool tt_isdst; /* used to set tm_isdst */ - bool tt_ttisstd; /* transition is std time */ - bool tt_ttisut; /* transition is UT */ -}; - -struct lsinfo { /* leap second information */ - time_t ls_trans; /* transition time */ - int_fast32_t ls_corr; /* correction to apply */ -}; - -/* This abbreviation means local time is unspecified. */ -static char const UNSPEC[] = "-00"; - -/* How many extra bytes are needed at the end of struct state's chars array. - This needs to be at least 1 for null termination in case the input - data isn't properly terminated, and it also needs to be big enough - for ttunspecified to work without crashing. */ -enum { CHARS_EXTRA = max(sizeof UNSPEC, 2) - 1 }; - -/* A representation of the contents of a TZif file. Ideally this - would have no size limits; the following sizes should suffice for - practical use. This struct should not be too large, as instances - are put on the stack and stacks are relatively small on some platforms. - See tzfile.h for more about the sizes. */ -struct state { - int leapcnt; - int timecnt; - int typecnt; - int charcnt; - bool goback; - bool goahead; - time_t ats[TZ_MAX_TIMES]; - unsigned char types[TZ_MAX_TIMES]; - struct ttinfo ttis[TZ_MAX_TYPES]; - char chars[max(max(TZ_MAX_CHARS + CHARS_EXTRA, sizeof "UTC"), - 2 * (TZNAME_MAXIMUM + 1))]; - struct lsinfo lsis[TZ_MAX_LEAPS]; -}; - -enum r_type { - JULIAN_DAY, /* Jn = Julian day */ - DAY_OF_YEAR, /* n = day of year */ - MONTH_NTH_DAY_OF_WEEK /* Mm.n.d = month, week, day of week */ -}; - -struct rule { - enum r_type r_type; /* type of rule */ - int r_day; /* day number of rule */ - int r_week; /* week number of rule */ - int r_mon; /* month number of rule */ - int_fast32_t r_time; /* transition time of rule */ -}; - -static struct tm *gmtsub(struct state const *, time_t const *, int_fast32_t, - struct tm *); -static bool increment_overflow(int *, int); -static bool increment_overflow_time(time_t *, int_fast32_t); -static int_fast32_t leapcorr(struct state const *, time_t); -static struct tm *timesub(time_t const *, int_fast32_t, struct state const *, - struct tm *); -static bool tzparse(char const *, struct state *, struct state const *); - -#ifdef ALL_STATE -static struct state * lclptr; -static struct state * gmtptr; -#endif /* defined ALL_STATE */ - -#ifndef ALL_STATE -static struct state lclmem; -static struct state gmtmem; -static struct state *const lclptr = &lclmem; -static struct state *const gmtptr = &gmtmem; -#endif /* State Farm */ - -#ifndef TZ_STRLEN_MAX -# define TZ_STRLEN_MAX 255 -#endif /* !defined TZ_STRLEN_MAX */ - -#if !USE_TIMEX_T || !defined TM_GMTOFF -static char lcl_TZname[TZ_STRLEN_MAX + 1]; -static int lcl_is_set; -#endif - -/* -** Section 4.12.3 of X3.159-1989 requires that -** Except for the strftime function, these functions [asctime, -** ctime, gmtime, localtime] return values in one of two static -** objects: a broken-down time structure and an array of char. -** Thanks to Paul Eggert for noting this. -** -** Although this requirement was removed in C99 it is still present in POSIX. -** Follow the requirement if SUPPORT_C89, even though this is more likely to -** trigger latent bugs in programs. -*/ - -#if !USE_TIMEX_T - -# if SUPPORT_C89 -static struct tm tm; -#endif - -# if 2 <= HAVE_TZNAME + TZ_TIME_T -char * tzname[2] = { - (char *) wildabbr, - (char *) wildabbr -}; -# endif -# if 2 <= USG_COMPAT + TZ_TIME_T -long timezone; -int daylight; -# endif -# if 2 <= ALTZONE + TZ_TIME_T -long altzone; -# endif - -#endif - -/* Initialize *S to a value based on UTOFF, ISDST, and DESIGIDX. */ -static void -init_ttinfo(struct ttinfo *s, int_fast32_t utoff, bool isdst, - desigidx_type desigidx) -{ - s->tt_utoff = utoff; - s->tt_isdst = isdst; - s->tt_desigidx = desigidx; - s->tt_ttisstd = false; - s->tt_ttisut = false; -} - -/* Return true if SP's time type I does not specify local time. */ -static bool -ttunspecified(struct state const *sp, int i) -{ - char const *abbr = &sp->chars[sp->ttis[i].tt_desigidx]; - /* memcmp is likely faster than strcmp, and is safe due to CHARS_EXTRA. */ - return memcmp(abbr, UNSPEC, sizeof UNSPEC) == 0; -} - -static int_fast32_t -detzcode(const char *const codep) -{ - register int_fast32_t result; - register int i; - int_fast32_t one = 1; - int_fast32_t halfmaxval = one << (32 - 2); - int_fast32_t maxval = halfmaxval - 1 + halfmaxval; - int_fast32_t minval = -1 - maxval; - - result = codep[0] & 0x7f; - for (i = 1; i < 4; ++i) - result = (result << 8) | (codep[i] & 0xff); - - if (codep[0] & 0x80) { - /* Do two's-complement negation even on non-two's-complement machines. - If the result would be minval - 1, return minval. */ - result -= !TWOS_COMPLEMENT(int_fast32_t) && result != 0; - result += minval; - } - return result; -} - -static int_fast64_t -detzcode64(const char *const codep) -{ - register int_fast64_t result; - register int i; - int_fast64_t one = 1; - int_fast64_t halfmaxval = one << (64 - 2); - int_fast64_t maxval = halfmaxval - 1 + halfmaxval; - int_fast64_t minval = -TWOS_COMPLEMENT(int_fast64_t) - maxval; - - result = codep[0] & 0x7f; - for (i = 1; i < 8; ++i) - result = (result << 8) | (codep[i] & 0xff); - - if (codep[0] & 0x80) { - /* Do two's-complement negation even on non-two's-complement machines. - If the result would be minval - 1, return minval. */ - result -= !TWOS_COMPLEMENT(int_fast64_t) && result != 0; - result += minval; - } - return result; -} - -#if !USE_TIMEX_T || !defined TM_GMTOFF - -static void -update_tzname_etc(struct state const *sp, struct ttinfo const *ttisp) -{ -# if HAVE_TZNAME - tzname[ttisp->tt_isdst] = (char *) &sp->chars[ttisp->tt_desigidx]; -# endif -# if USG_COMPAT - if (!ttisp->tt_isdst) - timezone = - ttisp->tt_utoff; -# endif -# if ALTZONE - if (ttisp->tt_isdst) - altzone = - ttisp->tt_utoff; -# endif -} - -/* If STDDST_MASK indicates that SP's TYPE provides useful info, - update tzname, timezone, and/or altzone and return STDDST_MASK, - diminished by the provided info if it is a specified local time. - Otherwise, return STDDST_MASK. See settzname for STDDST_MASK. */ -static int -may_update_tzname_etc(int stddst_mask, struct state *sp, int type) -{ - struct ttinfo *ttisp = &sp->ttis[type]; - int this_bit = 1 << ttisp->tt_isdst; - if (stddst_mask & this_bit) { - update_tzname_etc(sp, ttisp); - if (!ttunspecified(sp, type)) - return stddst_mask & ~this_bit; - } - return stddst_mask; -} - -static void -settzname(void) -{ - register struct state * const sp = lclptr; - register int i; - - /* If STDDST_MASK & 1 we need info about a standard time. - If STDDST_MASK & 2 we need info about a daylight saving time. - When STDDST_MASK becomes zero we can stop looking. */ - int stddst_mask = 0; - -# if HAVE_TZNAME - tzname[0] = tzname[1] = (char *) (sp ? wildabbr : utc); - stddst_mask = 3; -# endif -# if USG_COMPAT - timezone = 0; - stddst_mask = 3; -# endif -# if ALTZONE - altzone = 0; - stddst_mask |= 2; -# endif - /* - ** And to get the latest time zone abbreviations into tzname. . . - */ - if (sp) { - for (i = sp->timecnt - 1; stddst_mask && 0 <= i; i--) - stddst_mask = may_update_tzname_etc(stddst_mask, sp, sp->types[i]); - for (i = sp->typecnt - 1; stddst_mask && 0 <= i; i--) - stddst_mask = may_update_tzname_etc(stddst_mask, sp, i); - } -# if USG_COMPAT - daylight = stddst_mask >> 1 ^ 1; -# endif -} - -/* Replace bogus characters in time zone abbreviations. - Return 0 on success, an errno value if a time zone abbreviation is - too long. */ -static int -scrub_abbrs(struct state *sp) -{ - int i; - - /* Reject overlong abbreviations. */ - for (i = 0; i < sp->charcnt - (TZNAME_MAXIMUM + 1); ) { - int len = strlen(&sp->chars[i]); - if (TZNAME_MAXIMUM < len) - return EOVERFLOW; - i += len + 1; - } - - /* Replace bogus characters. */ - for (i = 0; i < sp->charcnt; ++i) - if (strchr(TZ_ABBR_CHAR_SET, sp->chars[i]) == NULL) - sp->chars[i] = TZ_ABBR_ERR_CHAR; - - return 0; -} - -#endif - -/* Input buffer for data read from a compiled tz file. */ -union input_buffer { - /* The first part of the buffer, interpreted as a header. */ - struct tzhead tzhead; - - /* The entire buffer. Ideally this would have no size limits; - the following should suffice for practical use. */ - char buf[2 * sizeof(struct tzhead) + 2 * sizeof(struct state) - + 4 * TZ_MAX_TIMES]; -}; - -/* TZDIR with a trailing '/' rather than a trailing '\0'. */ -static char const tzdirslash[sizeof TZDIR] = TZDIR "/"; - -/* Local storage needed for 'tzloadbody'. */ -union local_storage { - /* The results of analyzing the file's contents after it is opened. */ - struct file_analysis { - /* The input buffer. */ - union input_buffer u; - - /* A temporary state used for parsing a TZ string in the file. */ - struct state st; - } u; - - /* The name of the file to be opened. Ideally this would have no - size limits, to support arbitrarily long Zone names. - Limiting Zone names to 1024 bytes should suffice for practical use. - However, there is no need for this to be smaller than struct - file_analysis as that struct is allocated anyway, as the other - union member. */ - char fullname[max(sizeof(struct file_analysis), sizeof tzdirslash + 1024)]; -}; - -/* Load tz data from the file named NAME into *SP. Read extended - format if DOEXTEND. Use *LSP for temporary storage. Return 0 on - success, an errno value on failure. */ -static int -tzloadbody(char const *name, struct state *sp, bool doextend, - union local_storage *lsp) -{ - register int i; - register int fid; - register int stored; - register ssize_t nread; - register bool doaccess; - register union input_buffer *up = &lsp->u.u; - register int tzheadsize = sizeof(struct tzhead); - - sp->goback = sp->goahead = false; - - if (! name) { - name = TZDEFAULT; - if (! name) - return EINVAL; - } - - if (name[0] == ':') - ++name; -#ifdef SUPPRESS_TZDIR - /* Do not prepend TZDIR. This is intended for specialized - applications only, due to its security implications. */ - doaccess = true; -#else - doaccess = name[0] == '/'; -#endif - if (!doaccess) { - char const *dot; - if (sizeof lsp->fullname - sizeof tzdirslash <= strlen(name)) - return ENAMETOOLONG; - - /* Create a string "TZDIR/NAME". Using sprintf here - would pull in stdio (and would fail if the - resulting string length exceeded INT_MAX!). */ - memcpy(lsp->fullname, tzdirslash, sizeof tzdirslash); - strcpy(lsp->fullname + sizeof tzdirslash, name); - - /* Set doaccess if NAME contains a ".." file name - component, as such a name could read a file outside - the TZDIR virtual subtree. */ - for (dot = name; (dot = strchr(dot, '.')); dot++) - if ((dot == name || dot[-1] == '/') && dot[1] == '.' - && (dot[2] == '/' || !dot[2])) { - doaccess = true; - break; - } - - name = lsp->fullname; - } - if (doaccess && access(name, R_OK) != 0) - return errno; - fid = open(name, O_RDONLY | O_BINARY); - if (fid < 0) - return errno; - - nread = read(fid, up->buf, sizeof up->buf); - if (nread < tzheadsize) { - int err = nread < 0 ? errno : EINVAL; - close(fid); - return err; - } - if (close(fid) < 0) - return errno; - for (stored = 4; stored <= 8; stored *= 2) { - char version = up->tzhead.tzh_version[0]; - bool skip_datablock = stored == 4 && version; - int_fast32_t datablock_size; - int_fast32_t ttisstdcnt = detzcode(up->tzhead.tzh_ttisstdcnt); - int_fast32_t ttisutcnt = detzcode(up->tzhead.tzh_ttisutcnt); - int_fast64_t prevtr = -1; - int_fast32_t prevcorr; - int_fast32_t leapcnt = detzcode(up->tzhead.tzh_leapcnt); - int_fast32_t timecnt = detzcode(up->tzhead.tzh_timecnt); - int_fast32_t typecnt = detzcode(up->tzhead.tzh_typecnt); - int_fast32_t charcnt = detzcode(up->tzhead.tzh_charcnt); - char const *p = up->buf + tzheadsize; - /* Although tzfile(5) currently requires typecnt to be nonzero, - support future formats that may allow zero typecnt - in files that have a TZ string and no transitions. */ - if (! (0 <= leapcnt && leapcnt < TZ_MAX_LEAPS - && 0 <= typecnt && typecnt < TZ_MAX_TYPES - && 0 <= timecnt && timecnt < TZ_MAX_TIMES - && 0 <= charcnt && charcnt < TZ_MAX_CHARS - && 0 <= ttisstdcnt && ttisstdcnt < TZ_MAX_TYPES - && 0 <= ttisutcnt && ttisutcnt < TZ_MAX_TYPES)) - return EINVAL; - datablock_size - = (timecnt * stored /* ats */ - + timecnt /* types */ - + typecnt * 6 /* ttinfos */ - + charcnt /* chars */ - + leapcnt * (stored + 4) /* lsinfos */ - + ttisstdcnt /* ttisstds */ - + ttisutcnt); /* ttisuts */ - if (nread < tzheadsize + datablock_size) - return EINVAL; - if (skip_datablock) - p += datablock_size; - else { - if (! ((ttisstdcnt == typecnt || ttisstdcnt == 0) - && (ttisutcnt == typecnt || ttisutcnt == 0))) - return EINVAL; - - sp->leapcnt = leapcnt; - sp->timecnt = timecnt; - sp->typecnt = typecnt; - sp->charcnt = charcnt; - - /* Read transitions, discarding those out of time_t range. - But pretend the last transition before TIME_T_MIN - occurred at TIME_T_MIN. */ - timecnt = 0; - for (i = 0; i < sp->timecnt; ++i) { - int_fast64_t at - = stored == 4 ? detzcode(p) : detzcode64(p); - sp->types[i] = at <= TIME_T_MAX; - if (sp->types[i]) { - time_t attime - = ((TYPE_SIGNED(time_t) ? at < TIME_T_MIN : at < 0) - ? TIME_T_MIN : at); - if (timecnt && attime <= sp->ats[timecnt - 1]) { - if (attime < sp->ats[timecnt - 1]) - return EINVAL; - sp->types[i - 1] = 0; - timecnt--; - } - sp->ats[timecnt++] = attime; - } - p += stored; - } - - timecnt = 0; - for (i = 0; i < sp->timecnt; ++i) { - unsigned char typ = *p++; - if (sp->typecnt <= typ) - return EINVAL; - if (sp->types[i]) - sp->types[timecnt++] = typ; - } - sp->timecnt = timecnt; - for (i = 0; i < sp->typecnt; ++i) { - register struct ttinfo * ttisp; - unsigned char isdst, desigidx; - - ttisp = &sp->ttis[i]; - ttisp->tt_utoff = detzcode(p); - p += 4; - isdst = *p++; - if (! (isdst < 2)) - return EINVAL; - ttisp->tt_isdst = isdst; - desigidx = *p++; - if (! (desigidx < sp->charcnt)) - return EINVAL; - ttisp->tt_desigidx = desigidx; - } - for (i = 0; i < sp->charcnt; ++i) - sp->chars[i] = *p++; - /* Ensure '\0'-terminated, and make it safe to call - ttunspecified later. */ - memset(&sp->chars[i], 0, CHARS_EXTRA); - - /* Read leap seconds, discarding those out of time_t range. */ - leapcnt = 0; - for (i = 0; i < sp->leapcnt; ++i) { - int_fast64_t tr = stored == 4 ? detzcode(p) : detzcode64(p); - int_fast32_t corr = detzcode(p + stored); - p += stored + 4; - - /* Leap seconds cannot occur before the Epoch, - or out of order. */ - if (tr <= prevtr) - return EINVAL; - - /* To avoid other botches in this code, each leap second's - correction must differ from the previous one's by 1 - second or less, except that the first correction can be - any value; these requirements are more generous than - RFC 9636, to allow future RFC extensions. */ - if (! (i == 0 - || (prevcorr < corr - ? corr == prevcorr + 1 - : (corr == prevcorr - || corr == prevcorr - 1)))) - return EINVAL; - prevtr = tr; - prevcorr = corr; - - if (tr <= TIME_T_MAX) { - sp->lsis[leapcnt].ls_trans = tr; - sp->lsis[leapcnt].ls_corr = corr; - leapcnt++; - } - } - sp->leapcnt = leapcnt; - - for (i = 0; i < sp->typecnt; ++i) { - register struct ttinfo * ttisp; - - ttisp = &sp->ttis[i]; - if (ttisstdcnt == 0) - ttisp->tt_ttisstd = false; - else { - if (*p != true && *p != false) - return EINVAL; - ttisp->tt_ttisstd = *p++; - } - } - for (i = 0; i < sp->typecnt; ++i) { - register struct ttinfo * ttisp; - - ttisp = &sp->ttis[i]; - if (ttisutcnt == 0) - ttisp->tt_ttisut = false; - else { - if (*p != true && *p != false) - return EINVAL; - ttisp->tt_ttisut = *p++; - } - } - } - - nread -= p - up->buf; - memmove(up->buf, p, nread); - - /* If this is an old file, we're done. */ - if (!version) - break; - } - if (doextend && nread > 2 && - up->buf[0] == '\n' && up->buf[nread - 1] == '\n' && - sp->typecnt + 2 <= TZ_MAX_TYPES) { - struct state *ts = &lsp->u.st; - - up->buf[nread - 1] = '\0'; - if (tzparse(&up->buf[1], ts, sp)) { - - /* Attempt to reuse existing abbreviations. - Without this, America/Anchorage would be right on - the edge after 2037 when TZ_MAX_CHARS is 50, as - sp->charcnt equals 40 (for LMT AST AWT APT AHST - AHDT YST AKDT AKST) and ts->charcnt equals 10 - (for AKST AKDT). Reusing means sp->charcnt can - stay 40 in this example. */ - int gotabbr = 0; - int charcnt = sp->charcnt; - for (i = 0; i < ts->typecnt; i++) { - char *tsabbr = ts->chars + ts->ttis[i].tt_desigidx; - int j; - for (j = 0; j < charcnt; j++) - if (strcmp(sp->chars + j, tsabbr) == 0) { - ts->ttis[i].tt_desigidx = j; - gotabbr++; - break; - } - if (! (j < charcnt)) { - int tsabbrlen = strlen(tsabbr); - if (j + tsabbrlen < TZ_MAX_CHARS) { - strcpy(sp->chars + j, tsabbr); - charcnt = j + tsabbrlen + 1; - ts->ttis[i].tt_desigidx = j; - gotabbr++; - } - } - } - if (gotabbr == ts->typecnt) { - sp->charcnt = charcnt; - - /* Ignore any trailing, no-op transitions generated - by zic as they don't help here and can run afoul - of bugs in zic 2016j or earlier. */ - while (1 < sp->timecnt - && (sp->types[sp->timecnt - 1] - == sp->types[sp->timecnt - 2])) - sp->timecnt--; - - sp->goahead = ts->goahead; - - for (i = 0; i < ts->timecnt; i++) { - time_t t = ts->ats[i]; - if (increment_overflow_time(&t, leapcorr(sp, t)) - || (0 < sp->timecnt - && t <= sp->ats[sp->timecnt - 1])) - continue; - if (TZ_MAX_TIMES <= sp->timecnt) { - sp->goahead = false; - break; - } - sp->ats[sp->timecnt] = t; - sp->types[sp->timecnt] = (sp->typecnt - + ts->types[i]); - sp->timecnt++; - } - for (i = 0; i < ts->typecnt; i++) - sp->ttis[sp->typecnt++] = ts->ttis[i]; - } - } - } - if (sp->typecnt == 0) - return EINVAL; - - return 0; -} - -/* Load tz data from the file named NAME into *SP. Read extended - format if DOEXTEND. Return 0 on success, an errno value on failure. */ -static int -tzload(char const *name, struct state *sp, bool doextend) -{ -#ifdef ALL_STATE - union local_storage *lsp = malloc(sizeof *lsp); - if (!lsp) { - return HAVE_MALLOC_ERRNO ? errno : ENOMEM; - } else { - int err = tzloadbody(name, sp, doextend, lsp); - free(lsp); - return err; - } -#else - union local_storage ls; - return tzloadbody(name, sp, doextend, &ls); -#endif -} - -static const int mon_lengths[2][MONSPERYEAR] = { - { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, - { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } -}; - -static const int year_lengths[2] = { - DAYSPERNYEAR, DAYSPERLYEAR -}; - -/* Is C an ASCII digit? */ -static bool -is_digit(char c) -{ - return '0' <= c && c <= '9'; -} - -/* -** Given a pointer into a timezone string, scan until a character that is not -** a valid character in a time zone abbreviation is found. -** Return a pointer to that character. -*/ - -ATTRIBUTE_PURE_114833 static const char * -getzname(register const char *strp) -{ - register char c; - - while ((c = *strp) != '\0' && !is_digit(c) && c != ',' && c != '-' && - c != '+') - ++strp; - return strp; -} - -/* -** Given a pointer into an extended timezone string, scan until the ending -** delimiter of the time zone abbreviation is located. -** Return a pointer to the delimiter. -** -** As with getzname above, the legal character set is actually quite -** restricted, with other characters producing undefined results. -** We don't do any checking here; checking is done later in common-case code. -*/ - -ATTRIBUTE_PURE_114833 static const char * -getqzname(register const char *strp, const int delim) -{ - register int c; - - while ((c = *strp) != '\0' && c != delim) - ++strp; - return strp; -} - -/* -** Given a pointer into a timezone string, extract a number from that string. -** Check that the number is within a specified range; if it is not, return -** NULL. -** Otherwise, return a pointer to the first character not part of the number. -*/ - -static const char * -getnum(register const char *strp, int *const nump, const int min, const int max) -{ - register char c; - register int num; - - if (strp == NULL || !is_digit(c = *strp)) - return NULL; - num = 0; - do { - num = num * 10 + (c - '0'); - if (num > max) - return NULL; /* illegal value */ - c = *++strp; - } while (is_digit(c)); - if (num < min) - return NULL; /* illegal value */ - *nump = num; - return strp; -} - -/* -** Given a pointer into a timezone string, extract a number of seconds, -** in hh[:mm[:ss]] form, from the string. -** If any error occurs, return NULL. -** Otherwise, return a pointer to the first character not part of the number -** of seconds. -*/ - -static const char * -getsecs(register const char *strp, int_fast32_t *const secsp) -{ - int num; - int_fast32_t secsperhour = SECSPERHOUR; - - /* - ** 'HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-POSIX rules like - ** "M10.4.6/26", which does not conform to POSIX, - ** but which specifies the equivalent of - ** "02:00 on the first Sunday on or after 23 Oct". - */ - strp = getnum(strp, &num, 0, HOURSPERDAY * DAYSPERWEEK - 1); - if (strp == NULL) - return NULL; - *secsp = num * secsperhour; - if (*strp == ':') { - ++strp; - strp = getnum(strp, &num, 0, MINSPERHOUR - 1); - if (strp == NULL) - return NULL; - *secsp += num * SECSPERMIN; - if (*strp == ':') { - ++strp; - /* 'SECSPERMIN' allows for leap seconds. */ - strp = getnum(strp, &num, 0, SECSPERMIN); - if (strp == NULL) - return NULL; - *secsp += num; - } - } - return strp; -} - -/* -** Given a pointer into a timezone string, extract an offset, in -** [+-]hh[:mm[:ss]] form, from the string. -** If any error occurs, return NULL. -** Otherwise, return a pointer to the first character not part of the time. -*/ - -static const char * -getoffset(register const char *strp, int_fast32_t *const offsetp) -{ - register bool neg = false; - - if (*strp == '-') { - neg = true; - ++strp; - } else if (*strp == '+') - ++strp; - strp = getsecs(strp, offsetp); - if (strp == NULL) - return NULL; /* illegal time */ - if (neg) - *offsetp = -*offsetp; - return strp; -} - -/* -** Given a pointer into a timezone string, extract a rule in the form -** date[/time]. See POSIX Base Definitions section 8.3 variable TZ -** for the format of "date" and "time". -** If a valid rule is not found, return NULL. -** Otherwise, return a pointer to the first character not part of the rule. -*/ - -static const char * -getrule(const char *strp, register struct rule *const rulep) -{ - if (*strp == 'J') { - /* - ** Julian day. - */ - rulep->r_type = JULIAN_DAY; - ++strp; - strp = getnum(strp, &rulep->r_day, 1, DAYSPERNYEAR); - } else if (*strp == 'M') { - /* - ** Month, week, day. - */ - rulep->r_type = MONTH_NTH_DAY_OF_WEEK; - ++strp; - strp = getnum(strp, &rulep->r_mon, 1, MONSPERYEAR); - if (strp == NULL) - return NULL; - if (*strp++ != '.') - return NULL; - strp = getnum(strp, &rulep->r_week, 1, 5); - if (strp == NULL) - return NULL; - if (*strp++ != '.') - return NULL; - strp = getnum(strp, &rulep->r_day, 0, DAYSPERWEEK - 1); - } else if (is_digit(*strp)) { - /* - ** Day of year. - */ - rulep->r_type = DAY_OF_YEAR; - strp = getnum(strp, &rulep->r_day, 0, DAYSPERLYEAR - 1); - } else return NULL; /* invalid format */ - if (strp == NULL) - return NULL; - if (*strp == '/') { - /* - ** Time specified. - */ - ++strp; - strp = getoffset(strp, &rulep->r_time); - } else rulep->r_time = 2 * SECSPERHOUR; /* default = 2:00:00 */ - return strp; -} - -/* -** Given a year, a rule, and the offset from UT at the time that rule takes -** effect, calculate the year-relative time that rule takes effect. -*/ - -static int_fast32_t -transtime(const int year, register const struct rule *const rulep, - const int_fast32_t offset) -{ - register bool leapyear; - register int_fast32_t value; - register int i; - int d, m1, yy0, yy1, yy2, dow; - - leapyear = isleap(year); - switch (rulep->r_type) { - - case JULIAN_DAY: - /* - ** Jn - Julian day, 1 == January 1, 60 == March 1 even in leap - ** years. - ** In non-leap years, or if the day number is 59 or less, just - ** add SECSPERDAY times the day number-1 to the time of - ** January 1, midnight, to get the day. - */ - value = (rulep->r_day - 1) * SECSPERDAY; - if (leapyear && rulep->r_day >= 60) - value += SECSPERDAY; - break; - - case DAY_OF_YEAR: - /* - ** n - day of year. - ** Just add SECSPERDAY times the day number to the time of - ** January 1, midnight, to get the day. - */ - value = rulep->r_day * SECSPERDAY; - break; - - case MONTH_NTH_DAY_OF_WEEK: - /* - ** Mm.n.d - nth "dth day" of month m. - */ - - /* - ** Use Zeller's Congruence to get day-of-week of first day of - ** month. - */ - m1 = (rulep->r_mon + 9) % 12 + 1; - yy0 = (rulep->r_mon <= 2) ? (year - 1) : year; - yy1 = yy0 / 100; - yy2 = yy0 % 100; - dow = ((26 * m1 - 2) / 10 + - 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7; - if (dow < 0) - dow += DAYSPERWEEK; - - /* - ** "dow" is the day-of-week of the first day of the month. Get - ** the day-of-month (zero-origin) of the first "dow" day of the - ** month. - */ - d = rulep->r_day - dow; - if (d < 0) - d += DAYSPERWEEK; - for (i = 1; i < rulep->r_week; ++i) { - if (d + DAYSPERWEEK >= - mon_lengths[leapyear][rulep->r_mon - 1]) - break; - d += DAYSPERWEEK; - } - - /* - ** "d" is the day-of-month (zero-origin) of the day we want. - */ - value = d * SECSPERDAY; - for (i = 0; i < rulep->r_mon - 1; ++i) - value += mon_lengths[leapyear][i] * SECSPERDAY; - break; - - default: unreachable(); - } - - /* - ** "value" is the year-relative time of 00:00:00 UT on the day in - ** question. To get the year-relative time of the specified local - ** time on that day, add the transition time and the current offset - ** from UT. - */ - return value + rulep->r_time + offset; -} - -/* -** Given a POSIX.1 proleptic TZ string, fill in the rule tables as -** appropriate. -*/ - -static bool -tzparse(const char *name, struct state *sp, struct state const *basep) -{ - const char * stdname; - const char * dstname; - int_fast32_t stdoffset; - int_fast32_t dstoffset; - register char * cp; - register bool load_ok; - ptrdiff_t stdlen, dstlen, charcnt; - time_t atlo = TIME_T_MIN, leaplo = TIME_T_MIN; - - stdname = name; - if (*name == '<') { - name++; - stdname = name; - name = getqzname(name, '>'); - if (*name != '>') - return false; - stdlen = name - stdname; - name++; - } else { - name = getzname(name); - stdlen = name - stdname; - } - if (! (0 < stdlen && stdlen <= TZNAME_MAXIMUM)) - return false; - name = getoffset(name, &stdoffset); - if (name == NULL) - return false; - charcnt = stdlen + 1; - if (basep) { - if (0 < basep->timecnt) - atlo = basep->ats[basep->timecnt - 1]; - load_ok = false; - sp->leapcnt = basep->leapcnt; - memcpy(sp->lsis, basep->lsis, sp->leapcnt * sizeof *sp->lsis); - } else { - load_ok = tzload(TZDEFRULES, sp, false) == 0; - if (!load_ok) - sp->leapcnt = 0; /* So, we're off a little. */ - } - if (0 < sp->leapcnt) - leaplo = sp->lsis[sp->leapcnt - 1].ls_trans; - sp->goback = sp->goahead = false; - if (*name != '\0') { - if (*name == '<') { - dstname = ++name; - name = getqzname(name, '>'); - if (*name != '>') - return false; - dstlen = name - dstname; - name++; - } else { - dstname = name; - name = getzname(name); - dstlen = name - dstname; /* length of DST abbr. */ - } - if (! (0 < dstlen && dstlen <= TZNAME_MAXIMUM)) - return false; - charcnt += dstlen + 1; - if (*name != '\0' && *name != ',' && *name != ';') { - name = getoffset(name, &dstoffset); - if (name == NULL) - return false; - } else dstoffset = stdoffset - SECSPERHOUR; - if (*name == '\0' && !load_ok) - name = TZDEFRULESTRING; - if (*name == ',' || *name == ';') { - struct rule start; - struct rule end; - register int year; - register int timecnt; - time_t janfirst; - int_fast32_t janoffset = 0; - int yearbeg, yearlim; - - ++name; - if ((name = getrule(name, &start)) == NULL) - return false; - if (*name++ != ',') - return false; - if ((name = getrule(name, &end)) == NULL) - return false; - if (*name != '\0') - return false; - sp->typecnt = 2; /* standard time and DST */ - /* - ** Two transitions per year, from EPOCH_YEAR forward. - */ - init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); - init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1); - timecnt = 0; - janfirst = 0; - yearbeg = EPOCH_YEAR; - - do { - int_fast32_t yearsecs - = year_lengths[isleap(yearbeg - 1)] * SECSPERDAY; - time_t janfirst1 = janfirst; - yearbeg--; - if (increment_overflow_time(&janfirst1, -yearsecs)) { - janoffset = -yearsecs; - break; - } - janfirst = janfirst1; - } while (atlo < janfirst - && EPOCH_YEAR - YEARSPERREPEAT / 2 < yearbeg); - - while (true) { - int_fast32_t yearsecs - = year_lengths[isleap(yearbeg)] * SECSPERDAY; - int yearbeg1 = yearbeg; - time_t janfirst1 = janfirst; - if (increment_overflow_time(&janfirst1, yearsecs) - || increment_overflow(&yearbeg1, 1) - || atlo <= janfirst1) - break; - yearbeg = yearbeg1; - janfirst = janfirst1; - } - - yearlim = yearbeg; - if (increment_overflow(&yearlim, years_of_observations)) - yearlim = INT_MAX; - for (year = yearbeg; year < yearlim; year++) { - int_fast32_t - starttime = transtime(year, &start, stdoffset), - endtime = transtime(year, &end, dstoffset); - int_fast32_t - yearsecs = (year_lengths[isleap(year)] - * SECSPERDAY); - bool reversed = endtime < starttime; - if (reversed) { - int_fast32_t swap = starttime; - starttime = endtime; - endtime = swap; - } - if (reversed - || (starttime < endtime - && endtime - starttime < yearsecs)) { - if (TZ_MAX_TIMES - 2 < timecnt) - break; - sp->ats[timecnt] = janfirst; - if (! increment_overflow_time - (&sp->ats[timecnt], - janoffset + starttime) - && atlo <= sp->ats[timecnt]) - sp->types[timecnt++] = !reversed; - sp->ats[timecnt] = janfirst; - if (! increment_overflow_time - (&sp->ats[timecnt], - janoffset + endtime) - && atlo <= sp->ats[timecnt]) { - sp->types[timecnt++] = reversed; - } - } - if (endtime < leaplo) { - yearlim = year; - if (increment_overflow(&yearlim, - years_of_observations)) - yearlim = INT_MAX; - } - if (increment_overflow_time - (&janfirst, janoffset + yearsecs)) - break; - janoffset = 0; - } - sp->timecnt = timecnt; - if (! timecnt) { - sp->ttis[0] = sp->ttis[1]; - sp->typecnt = 1; /* Perpetual DST. */ - } else if (years_of_observations <= year - yearbeg) - sp->goback = sp->goahead = true; - } else { - register int_fast32_t theirstdoffset; - register int_fast32_t theirdstoffset; - register int_fast32_t theiroffset; - register bool isdst; - register int i; - register int j; - - if (*name != '\0') - return false; - /* - ** Initial values of theirstdoffset and theirdstoffset. - */ - theirstdoffset = 0; - for (i = 0; i < sp->timecnt; ++i) { - j = sp->types[i]; - if (!sp->ttis[j].tt_isdst) { - theirstdoffset = - - sp->ttis[j].tt_utoff; - break; - } - } - theirdstoffset = 0; - for (i = 0; i < sp->timecnt; ++i) { - j = sp->types[i]; - if (sp->ttis[j].tt_isdst) { - theirdstoffset = - - sp->ttis[j].tt_utoff; - break; - } - } - /* - ** Initially we're assumed to be in standard time. - */ - isdst = false; - /* - ** Now juggle transition times and types - ** tracking offsets as you do. - */ - for (i = 0; i < sp->timecnt; ++i) { - j = sp->types[i]; - sp->types[i] = sp->ttis[j].tt_isdst; - if (sp->ttis[j].tt_ttisut) { - /* No adjustment to transition time */ - } else { - /* - ** If daylight saving time is in - ** effect, and the transition time was - ** not specified as standard time, add - ** the daylight saving time offset to - ** the transition time; otherwise, add - ** the standard time offset to the - ** transition time. - */ - /* - ** Transitions from DST to DDST - ** will effectively disappear since - ** proleptic TZ strings have only one - ** DST offset. - */ - if (isdst && !sp->ttis[j].tt_ttisstd) { - sp->ats[i] += dstoffset - - theirdstoffset; - } else { - sp->ats[i] += stdoffset - - theirstdoffset; - } - } - theiroffset = -sp->ttis[j].tt_utoff; - if (sp->ttis[j].tt_isdst) - theirdstoffset = theiroffset; - else theirstdoffset = theiroffset; - } - /* - ** Finally, fill in ttis. - */ - init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); - init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1); - sp->typecnt = 2; - } - } else { - dstlen = 0; - sp->typecnt = 1; /* only standard time */ - sp->timecnt = 0; - init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); - } - sp->charcnt = charcnt; - cp = sp->chars; - memcpy(cp, stdname, stdlen); - cp += stdlen; - *cp++ = '\0'; - if (dstlen != 0) { - memcpy(cp, dstname, dstlen); - *(cp + dstlen) = '\0'; - } - return true; -} - -static void -gmtload(struct state *const sp) -{ - if (tzload(etc_utc, sp, true) != 0) - tzparse("UTC0", sp, NULL); -} - -#if !USE_TIMEX_T || !defined TM_GMTOFF - -/* Initialize *SP to a value appropriate for the TZ setting NAME. - Return 0 on success, an errno value on failure. */ -static int -zoneinit(struct state *sp, char const *name) -{ - if (name && ! name[0]) { - /* - ** User wants it fast rather than right. - */ - sp->leapcnt = 0; /* so, we're off a little */ - sp->timecnt = 0; - sp->typecnt = 0; - sp->charcnt = 0; - sp->goback = sp->goahead = false; - init_ttinfo(&sp->ttis[0], 0, false, 0); - strcpy(sp->chars, utc); - return 0; - } else { - int err = tzload(name, sp, true); - if (err != 0 && name && name[0] != ':' && tzparse(name, sp, NULL)) - err = 0; - if (err == 0) - err = scrub_abbrs(sp); - return err; - } -} - -static void -tzset_unlocked(void) -{ - char const *name = getenv("TZ"); - struct state *sp = lclptr; - int lcl = name ? strlen(name) < sizeof lcl_TZname : -1; - if (lcl < 0 - ? lcl_is_set < 0 - : 0 < lcl_is_set && strcmp(lcl_TZname, name) == 0) - return; -# ifdef ALL_STATE - if (! sp) - lclptr = sp = malloc(sizeof *lclptr); -# endif - if (sp) { - if (zoneinit(sp, name) != 0) - zoneinit(sp, ""); - if (0 < lcl) - strcpy(lcl_TZname, name); - } - settzname(); - lcl_is_set = lcl; -} - -#endif - -#if !USE_TIMEX_T -void -tzset(void) -{ - if (lock() != 0) - return; - tzset_unlocked(); - unlock(); -} -#endif - -static void -gmtcheck(void) -{ - static bool gmt_is_set; - if (lock() != 0) - return; - if (! gmt_is_set) { -#ifdef ALL_STATE - gmtptr = malloc(sizeof *gmtptr); -#endif - if (gmtptr) - gmtload(gmtptr); - gmt_is_set = true; - } - unlock(); -} - -#if NETBSD_INSPIRED && !USE_TIMEX_T - -timezone_t -tzalloc(char const *name) -{ - timezone_t sp = malloc(sizeof *sp); - if (sp) { - int err = zoneinit(sp, name); - if (err != 0) { - free(sp); - errno = err; - return NULL; - } - } else if (!HAVE_MALLOC_ERRNO) - errno = ENOMEM; - return sp; -} - -void -tzfree(timezone_t sp) -{ - free(sp); -} - -/* -** NetBSD 6.1.4 has ctime_rz, but omit it because C23 deprecates ctime and -** POSIX.1-2024 removes ctime_r. Both have potential security problems that -** ctime_rz would share. Callers can instead use localtime_rz + strftime. -** -** NetBSD 6.1.4 has tzgetname, but omit it because it doesn't work -** in zones with three or more time zone abbreviations. -** Callers can instead use localtime_rz + strftime. -*/ - -#endif - -#if !USE_TIMEX_T || !defined TM_GMTOFF - -/* -** The easy way to behave "as if no library function calls" localtime -** is to not call it, so we drop its guts into "localsub", which can be -** freely called. (And no, the PANS doesn't require the above behavior, -** but it *is* desirable.) -** -** If successful and SETNAME is nonzero, -** set the applicable parts of tzname, timezone and altzone; -** however, it's OK to omit this step for proleptic TZ strings -** since in that case tzset should have already done this step correctly. -** SETNAME's type is int_fast32_t for compatibility with gmtsub, -** but it is actually a boolean and its value should be 0 or 1. -*/ - -/*ARGSUSED*/ -static struct tm * -localsub(struct state const *sp, time_t const *timep, int_fast32_t setname, - struct tm *const tmp) -{ - register const struct ttinfo * ttisp; - register int i; - register struct tm * result; - const time_t t = *timep; - - if (sp == NULL) { - /* Don't bother to set tzname etc.; tzset has already done it. */ - return gmtsub(gmtptr, timep, 0, tmp); - } - if ((sp->goback && t < sp->ats[0]) || - (sp->goahead && t > sp->ats[sp->timecnt - 1])) { - time_t newt; - register time_t seconds; - register time_t years; - - if (t < sp->ats[0]) - seconds = sp->ats[0] - t; - else seconds = t - sp->ats[sp->timecnt - 1]; - --seconds; - - /* Beware integer overflow, as SECONDS might - be close to the maximum time_t. */ - years = seconds / SECSPERREPEAT * YEARSPERREPEAT; - seconds = years * AVGSECSPERYEAR; - years += YEARSPERREPEAT; - if (t < sp->ats[0]) - newt = t + seconds + SECSPERREPEAT; - else - newt = t - seconds - SECSPERREPEAT; - - if (newt < sp->ats[0] || - newt > sp->ats[sp->timecnt - 1]) - return NULL; /* "cannot happen" */ - result = localsub(sp, &newt, setname, tmp); - if (result) { -# if defined ckd_add && defined ckd_sub - if (t < sp->ats[0] - ? ckd_sub(&result->tm_year, - result->tm_year, years) - : ckd_add(&result->tm_year, - result->tm_year, years)) - return NULL; -# else - register int_fast64_t newy; - - newy = result->tm_year; - if (t < sp->ats[0]) - newy -= years; - else newy += years; - if (! (INT_MIN <= newy && newy <= INT_MAX)) - return NULL; - result->tm_year = newy; -# endif - } - return result; - } - if (sp->timecnt == 0 || t < sp->ats[0]) { - i = 0; - } else { - register int lo = 1; - register int hi = sp->timecnt; - - while (lo < hi) { - register int mid = (lo + hi) >> 1; - - if (t < sp->ats[mid]) - hi = mid; - else lo = mid + 1; - } - i = sp->types[lo - 1]; - } - ttisp = &sp->ttis[i]; - /* - ** To get (wrong) behavior that's compatible with System V Release 2.0 - ** you'd replace the statement below with - ** t += ttisp->tt_utoff; - ** timesub(&t, 0L, sp, tmp); - */ - result = timesub(&t, ttisp->tt_utoff, sp, tmp); - if (result) { - result->tm_isdst = ttisp->tt_isdst; -# ifdef TM_ZONE - result->TM_ZONE = (char *) &sp->chars[ttisp->tt_desigidx]; -# endif - if (setname) - update_tzname_etc(sp, ttisp); - } - return result; -} -#endif - -#if !USE_TIMEX_T - -# if NETBSD_INSPIRED -struct tm * -localtime_rz(struct state *restrict sp, time_t const *restrict timep, - struct tm *restrict tmp) -{ - return localsub(sp, timep, 0, tmp); -} -# endif - -static struct tm * -localtime_tzset(time_t const *timep, struct tm *tmp, bool setname) -{ - int err = lock(); - if (err) { - errno = err; - return NULL; - } - if (setname || !lcl_is_set) - tzset_unlocked(); - tmp = localsub(lclptr, timep, setname, tmp); - unlock(); - return tmp; -} - -struct tm * -localtime(const time_t *timep) -{ -# if !SUPPORT_C89 - static struct tm tm; -# endif - return localtime_tzset(timep, &tm, true); -} - -struct tm * -localtime_r(const time_t *restrict timep, struct tm *restrict tmp) -{ - return localtime_tzset(timep, tmp, false); -} -#endif - -/* -** gmtsub is to gmtime as localsub is to localtime. -*/ - -static struct tm * -gmtsub(ATTRIBUTE_MAYBE_UNUSED struct state const *sp, time_t const *timep, - int_fast32_t offset, struct tm *tmp) -{ - register struct tm * result; - - result = timesub(timep, offset, gmtptr, tmp); -#ifdef TM_ZONE - /* - ** Could get fancy here and deliver something such as - ** "+xx" or "-xx" if offset is non-zero, - ** but this is no time for a treasure hunt. - */ - tmp->TM_ZONE = ((char *) - (offset ? wildabbr : gmtptr ? gmtptr->chars : utc)); -#endif /* defined TM_ZONE */ - return result; -} - -#if !USE_TIMEX_T - -/* -* Re-entrant version of gmtime. -*/ - -struct tm * -gmtime_r(time_t const *restrict timep, struct tm *restrict tmp) -{ - gmtcheck(); - return gmtsub(gmtptr, timep, 0, tmp); -} - -struct tm * -gmtime(const time_t *timep) -{ -# if !SUPPORT_C89 - static struct tm tm; -# endif - return gmtime_r(timep, &tm); -} - -# if STD_INSPIRED - -/* This function is obsolescent and may disappear in future releases. - Callers can instead use localtime_rz with a fixed-offset zone. */ - -struct tm * -offtime(const time_t *timep, long offset) -{ - gmtcheck(); - -# if !SUPPORT_C89 - static struct tm tm; -# endif - return gmtsub(gmtptr, timep, offset, &tm); -} - -# endif -#endif - -/* -** Return the number of leap years through the end of the given year -** where, to make the math easy, the answer for year zero is defined as zero. -*/ - -static time_t -leaps_thru_end_of_nonneg(time_t y) -{ - return y / 4 - y / 100 + y / 400; -} - -static time_t -leaps_thru_end_of(time_t y) -{ - return (y < 0 - ? -1 - leaps_thru_end_of_nonneg(-1 - y) - : leaps_thru_end_of_nonneg(y)); -} - -static struct tm * -timesub(const time_t *timep, int_fast32_t offset, - const struct state *sp, struct tm *tmp) -{ - register const struct lsinfo * lp; - register time_t tdays; - register const int * ip; - register int_fast32_t corr; - register int i; - int_fast32_t idays, rem, dayoff, dayrem; - time_t y; - - /* If less than SECSPERMIN, the number of seconds since the - most recent positive leap second; otherwise, do not add 1 - to localtime tm_sec because of leap seconds. */ - time_t secs_since_posleap = SECSPERMIN; - - corr = 0; - i = (sp == NULL) ? 0 : sp->leapcnt; - while (--i >= 0) { - lp = &sp->lsis[i]; - if (*timep >= lp->ls_trans) { - corr = lp->ls_corr; - if ((i == 0 ? 0 : lp[-1].ls_corr) < corr) - secs_since_posleap = *timep - lp->ls_trans; - break; - } - } - - /* Calculate the year, avoiding integer overflow even if - time_t is unsigned. */ - tdays = *timep / SECSPERDAY; - rem = *timep % SECSPERDAY; - rem += offset % SECSPERDAY - corr % SECSPERDAY + 3 * SECSPERDAY; - dayoff = offset / SECSPERDAY - corr / SECSPERDAY + rem / SECSPERDAY - 3; - rem %= SECSPERDAY; - /* y = (EPOCH_YEAR - + floor((tdays + dayoff) / DAYSPERREPEAT) * YEARSPERREPEAT), - sans overflow. But calculate against 1570 (EPOCH_YEAR - - YEARSPERREPEAT) instead of against 1970 so that things work - for localtime values before 1970 when time_t is unsigned. */ - dayrem = tdays % DAYSPERREPEAT; - dayrem += dayoff % DAYSPERREPEAT; - y = (EPOCH_YEAR - YEARSPERREPEAT - + ((1 + dayoff / DAYSPERREPEAT + dayrem / DAYSPERREPEAT - - ((dayrem % DAYSPERREPEAT) < 0) - + tdays / DAYSPERREPEAT) - * YEARSPERREPEAT)); - /* idays = (tdays + dayoff) mod DAYSPERREPEAT, sans overflow. */ - idays = tdays % DAYSPERREPEAT; - idays += dayoff % DAYSPERREPEAT + 2 * DAYSPERREPEAT; - idays %= DAYSPERREPEAT; - /* Increase Y and decrease IDAYS until IDAYS is in range for Y. */ - while (year_lengths[isleap(y)] <= idays) { - int tdelta = idays / DAYSPERLYEAR; - int_fast32_t ydelta = tdelta + !tdelta; - time_t newy = y + ydelta; - register int leapdays; - leapdays = leaps_thru_end_of(newy - 1) - - leaps_thru_end_of(y - 1); - idays -= ydelta * DAYSPERNYEAR; - idays -= leapdays; - y = newy; - } - -#ifdef ckd_add - if (ckd_add(&tmp->tm_year, y, -TM_YEAR_BASE)) { - errno = EOVERFLOW; - return NULL; - } -#else - if (!TYPE_SIGNED(time_t) && y < TM_YEAR_BASE) { - int signed_y = y; - tmp->tm_year = signed_y - TM_YEAR_BASE; - } else if ((!TYPE_SIGNED(time_t) || INT_MIN + TM_YEAR_BASE <= y) - && y - TM_YEAR_BASE <= INT_MAX) - tmp->tm_year = y - TM_YEAR_BASE; - else { - errno = EOVERFLOW; - return NULL; - } -#endif - tmp->tm_yday = idays; - /* - ** The "extra" mods below avoid overflow problems. - */ - tmp->tm_wday = (TM_WDAY_BASE - + ((tmp->tm_year % DAYSPERWEEK) - * (DAYSPERNYEAR % DAYSPERWEEK)) - + leaps_thru_end_of(y - 1) - - leaps_thru_end_of(TM_YEAR_BASE - 1) - + idays); - tmp->tm_wday %= DAYSPERWEEK; - if (tmp->tm_wday < 0) - tmp->tm_wday += DAYSPERWEEK; - tmp->tm_hour = rem / SECSPERHOUR; - rem %= SECSPERHOUR; - tmp->tm_min = rem / SECSPERMIN; - tmp->tm_sec = rem % SECSPERMIN; - - /* Use "... ??:??:60" at the end of the localtime minute containing - the second just before the positive leap second. */ - tmp->tm_sec += secs_since_posleap <= tmp->tm_sec; - - ip = mon_lengths[isleap(y)]; - for (tmp->tm_mon = 0; idays >= ip[tmp->tm_mon]; ++(tmp->tm_mon)) - idays -= ip[tmp->tm_mon]; - tmp->tm_mday = idays + 1; - tmp->tm_isdst = 0; -#ifdef TM_GMTOFF - tmp->TM_GMTOFF = offset; -#endif /* defined TM_GMTOFF */ - return tmp; -} - -/* -** Adapted from code provided by Robert Elz, who writes: -** The "best" way to do mktime I think is based on an idea of Bob -** Kridle's (so its said...) from a long time ago. -** It does a binary search of the time_t space. Since time_t's are -** just 32 bits, its a max of 32 iterations (even at 64 bits it -** would still be very reasonable). -*/ - -#ifndef WRONG -# define WRONG (-1) -#endif /* !defined WRONG */ - -/* -** Normalize logic courtesy Paul Eggert. -*/ - -static bool -increment_overflow(int *ip, int j) -{ -#ifdef ckd_add - return ckd_add(ip, *ip, j); -#else - register int const i = *ip; - - /* - ** If i >= 0 there can only be overflow if i + j > INT_MAX - ** or if j > INT_MAX - i; given i >= 0, INT_MAX - i cannot overflow. - ** If i < 0 there can only be overflow if i + j < INT_MIN - ** or if j < INT_MIN - i; given i < 0, INT_MIN - i cannot overflow. - */ - if ((i >= 0) ? (j > INT_MAX - i) : (j < INT_MIN - i)) - return true; - *ip += j; - return false; -#endif -} - -static bool -increment_overflow_time_iinntt(time_t *tp, iinntt j) -{ -#ifdef ckd_add - return ckd_add(tp, *tp, j); -#else - if (j < 0 - ? (TYPE_SIGNED(time_t) ? *tp < TIME_T_MIN - j : *tp <= -1 - j) - : TIME_T_MAX - j < *tp) - return true; - *tp += j; - return false; -#endif -} - -static bool -increment_overflow_time(time_t *tp, int_fast32_t j) -{ -#ifdef ckd_add - return ckd_add(tp, *tp, j); -#else - /* - ** This is like - ** 'if (! (TIME_T_MIN <= *tp + j && *tp + j <= TIME_T_MAX)) ...', - ** except that it does the right thing even if *tp + j would overflow. - */ - if (! (j < 0 - ? (TYPE_SIGNED(time_t) ? TIME_T_MIN - j <= *tp : -1 - j < *tp) - : *tp <= TIME_T_MAX - j)) - return true; - *tp += j; - return false; -#endif -} - -static int -tmcomp(register const struct tm *const atmp, - register const struct tm *const btmp) -{ - register int result; - - if (atmp->tm_year != btmp->tm_year) - return atmp->tm_year < btmp->tm_year ? -1 : 1; - if ((result = (atmp->tm_mon - btmp->tm_mon)) == 0 && - (result = (atmp->tm_mday - btmp->tm_mday)) == 0 && - (result = (atmp->tm_hour - btmp->tm_hour)) == 0 && - (result = (atmp->tm_min - btmp->tm_min)) == 0) - result = atmp->tm_sec - btmp->tm_sec; - return result; -} - -/* Copy to *DEST from *SRC. Copy only the members needed for mktime, - as other members might not be initialized. */ -static void -mktmcpy(struct tm *dest, struct tm const *src) -{ - dest->tm_sec = src->tm_sec; - dest->tm_min = src->tm_min; - dest->tm_hour = src->tm_hour; - dest->tm_mday = src->tm_mday; - dest->tm_mon = src->tm_mon; - dest->tm_year = src->tm_year; - dest->tm_isdst = src->tm_isdst; -#if defined TM_GMTOFF && ! UNINIT_TRAP - dest->TM_GMTOFF = src->TM_GMTOFF; -#endif -} - -static time_t -time2sub(struct tm *const tmp, - struct tm *(*funcp)(struct state const *, time_t const *, - int_fast32_t, struct tm *), - struct state const *sp, - const int_fast32_t offset, - bool *okayp, - bool do_norm_secs) -{ - register int dir; - register int i, j; - register time_t lo; - register time_t hi; - iinntt y, mday, hour, min, saved_seconds; - time_t newt; - time_t t; - struct tm yourtm, mytm; - - *okayp = false; - mktmcpy(&yourtm, tmp); - - min = yourtm.tm_min; - if (do_norm_secs) { - min += yourtm.tm_sec / SECSPERMIN; - yourtm.tm_sec %= SECSPERMIN; - if (yourtm.tm_sec < 0) { - yourtm.tm_sec += SECSPERMIN; - min--; - } - } - - hour = yourtm.tm_hour; - hour += min / MINSPERHOUR; - yourtm.tm_min = min % MINSPERHOUR; - if (yourtm.tm_min < 0) { - yourtm.tm_min += MINSPERHOUR; - hour--; - } - - mday = yourtm.tm_mday; - mday += hour / HOURSPERDAY; - yourtm.tm_hour = hour % HOURSPERDAY; - if (yourtm.tm_hour < 0) { - yourtm.tm_hour += HOURSPERDAY; - mday--; - } - - y = yourtm.tm_year; - y += yourtm.tm_mon / MONSPERYEAR; - yourtm.tm_mon %= MONSPERYEAR; - if (yourtm.tm_mon < 0) { - yourtm.tm_mon += MONSPERYEAR; - y--; - } - - /* - ** Turn y into an actual year number for now. - ** It is converted back to an offset from TM_YEAR_BASE later. - */ - y += TM_YEAR_BASE; - - while (mday <= 0) { - iinntt li = y - (yourtm.tm_mon <= 1); - mday += year_lengths[isleap(li)]; - y--; - } - while (DAYSPERLYEAR < mday) { - iinntt li = y + (1 < yourtm.tm_mon); - mday -= year_lengths[isleap(li)]; - y++; - } - yourtm.tm_mday = mday; - for ( ; ; ) { - i = mon_lengths[isleap(y)][yourtm.tm_mon]; - if (yourtm.tm_mday <= i) - break; - yourtm.tm_mday -= i; - if (++yourtm.tm_mon >= MONSPERYEAR) { - yourtm.tm_mon = 0; - y++; - } - } -#ifdef ckd_add - if (ckd_add(&yourtm.tm_year, y, -TM_YEAR_BASE)) - return WRONG; -#else - y -= TM_YEAR_BASE; - if (! (INT_MIN <= y && y <= INT_MAX)) - return WRONG; - yourtm.tm_year = y; -#endif - if (yourtm.tm_sec >= 0 && yourtm.tm_sec < SECSPERMIN) - saved_seconds = 0; - else if (yourtm.tm_year < EPOCH_YEAR - TM_YEAR_BASE) { - /* - ** We can't set tm_sec to 0, because that might push the - ** time below the minimum representable time. - ** Set tm_sec to 59 instead. - ** This assumes that the minimum representable time is - ** not in the same minute that a leap second was deleted from, - ** which is a safer assumption than using 58 would be. - */ - saved_seconds = yourtm.tm_sec; - saved_seconds -= SECSPERMIN - 1; - yourtm.tm_sec = SECSPERMIN - 1; - } else { - saved_seconds = yourtm.tm_sec; - yourtm.tm_sec = 0; - } - /* - ** Do a binary search (this works whatever time_t's type is). - */ - lo = TIME_T_MIN; - hi = TIME_T_MAX; - for ( ; ; ) { - t = lo / 2 + hi / 2; - if (t < lo) - t = lo; - else if (t > hi) - t = hi; - if (! funcp(sp, &t, offset, &mytm)) { - /* - ** Assume that t is too extreme to be represented in - ** a struct tm; arrange things so that it is less - ** extreme on the next pass. - */ - dir = (t > 0) ? 1 : -1; - } else dir = tmcomp(&mytm, &yourtm); - if (dir != 0) { - if (t == lo) { - if (t == TIME_T_MAX) - return WRONG; - ++t; - ++lo; - } else if (t == hi) { - if (t == TIME_T_MIN) - return WRONG; - --t; - --hi; - } - if (lo > hi) - return WRONG; - if (dir > 0) - hi = t; - else lo = t; - continue; - } -#if defined TM_GMTOFF && ! UNINIT_TRAP - if (mytm.TM_GMTOFF != yourtm.TM_GMTOFF - && (yourtm.TM_GMTOFF < 0 - ? (-SECSPERDAY <= yourtm.TM_GMTOFF - && (mytm.TM_GMTOFF <= - (min(INT_FAST32_MAX, LONG_MAX) - + yourtm.TM_GMTOFF))) - : (yourtm.TM_GMTOFF <= SECSPERDAY - && ((max(INT_FAST32_MIN, LONG_MIN) - + yourtm.TM_GMTOFF) - <= mytm.TM_GMTOFF)))) { - /* MYTM matches YOURTM except with the wrong UT offset. - YOURTM.TM_GMTOFF is plausible, so try it instead. - It's OK if YOURTM.TM_GMTOFF contains uninitialized data, - since the guess gets checked. */ - time_t altt = t; - int_fast32_t diff = mytm.TM_GMTOFF - yourtm.TM_GMTOFF; - if (!increment_overflow_time(&altt, diff)) { - struct tm alttm; - if (funcp(sp, &altt, offset, &alttm) - && alttm.tm_isdst == mytm.tm_isdst - && alttm.TM_GMTOFF == yourtm.TM_GMTOFF - && tmcomp(&alttm, &yourtm) == 0) { - t = altt; - mytm = alttm; - } - } - } -#endif - if (yourtm.tm_isdst < 0 || mytm.tm_isdst == yourtm.tm_isdst) - break; - /* - ** Right time, wrong type. - ** Hunt for right time, right type. - ** It's okay to guess wrong since the guess - ** gets checked. - */ - if (sp == NULL) - return WRONG; - for (i = sp->typecnt - 1; i >= 0; --i) { - if (sp->ttis[i].tt_isdst != yourtm.tm_isdst) - continue; - for (j = sp->typecnt - 1; j >= 0; --j) { - if (sp->ttis[j].tt_isdst == yourtm.tm_isdst) - continue; - if (ttunspecified(sp, j)) - continue; - newt = (t + sp->ttis[j].tt_utoff - - sp->ttis[i].tt_utoff); - if (! funcp(sp, &newt, offset, &mytm)) - continue; - if (tmcomp(&mytm, &yourtm) != 0) - continue; - if (mytm.tm_isdst != yourtm.tm_isdst) - continue; - /* - ** We have a match. - */ - t = newt; - goto label; - } - } - return WRONG; - } -label: - if (increment_overflow_time_iinntt(&t, saved_seconds)) - return WRONG; - if (funcp(sp, &t, offset, tmp)) - *okayp = true; - return t; -} - -static time_t -time2(struct tm * const tmp, - struct tm *(*funcp)(struct state const *, time_t const *, - int_fast32_t, struct tm *), - struct state const *sp, - const int_fast32_t offset, - bool *okayp) -{ - time_t t; - - /* - ** First try without normalization of seconds - ** (in case tm_sec contains a value associated with a leap second). - ** If that fails, try with normalization of seconds. - */ - t = time2sub(tmp, funcp, sp, offset, okayp, false); - return *okayp ? t : time2sub(tmp, funcp, sp, offset, okayp, true); -} - -static time_t -time1(struct tm *const tmp, - struct tm *(*funcp)(struct state const *, time_t const *, - int_fast32_t, struct tm *), - struct state const *sp, - const int_fast32_t offset) -{ - register time_t t; - register int samei, otheri; - register int sameind, otherind; - register int i; - register int nseen; - char seen[TZ_MAX_TYPES]; - unsigned char types[TZ_MAX_TYPES]; - bool okay; - - if (tmp == NULL) { - errno = EINVAL; - return WRONG; - } - if (tmp->tm_isdst > 1) - tmp->tm_isdst = 1; - t = time2(tmp, funcp, sp, offset, &okay); - if (okay) - return t; - if (tmp->tm_isdst < 0) -#ifdef PCTS - /* - ** POSIX Conformance Test Suite code courtesy Grant Sullivan. - */ - tmp->tm_isdst = 0; /* reset to std and try again */ -#else - return t; -#endif /* !defined PCTS */ - /* - ** We're supposed to assume that somebody took a time of one type - ** and did some math on it that yielded a "struct tm" that's bad. - ** We try to divine the type they started from and adjust to the - ** type they need. - */ - if (sp == NULL) - return WRONG; - for (i = 0; i < sp->typecnt; ++i) - seen[i] = false; - nseen = 0; - for (i = sp->timecnt - 1; i >= 0; --i) - if (!seen[sp->types[i]] && !ttunspecified(sp, sp->types[i])) { - seen[sp->types[i]] = true; - types[nseen++] = sp->types[i]; - } - for (sameind = 0; sameind < nseen; ++sameind) { - samei = types[sameind]; - if (sp->ttis[samei].tt_isdst != tmp->tm_isdst) - continue; - for (otherind = 0; otherind < nseen; ++otherind) { - otheri = types[otherind]; - if (sp->ttis[otheri].tt_isdst == tmp->tm_isdst) - continue; - tmp->tm_sec += (sp->ttis[otheri].tt_utoff - - sp->ttis[samei].tt_utoff); - tmp->tm_isdst = !tmp->tm_isdst; - t = time2(tmp, funcp, sp, offset, &okay); - if (okay) - return t; - tmp->tm_sec -= (sp->ttis[otheri].tt_utoff - - sp->ttis[samei].tt_utoff); - tmp->tm_isdst = !tmp->tm_isdst; - } - } - return WRONG; -} - -#if !defined TM_GMTOFF || !USE_TIMEX_T - -static time_t -mktime_tzname(struct state *sp, struct tm *tmp, bool setname) -{ - if (sp) - return time1(tmp, localsub, sp, setname); - else { - gmtcheck(); - return time1(tmp, gmtsub, gmtptr, 0); - } -} - -# if USE_TIMEX_T -static -# endif -time_t -mktime(struct tm *tmp) -{ - time_t t; - int err = lock(); - if (err) { - errno = err; - return -1; - } - tzset_unlocked(); - t = mktime_tzname(lclptr, tmp, true); - unlock(); - return t; -} - -#endif - -#if NETBSD_INSPIRED && !USE_TIMEX_T -time_t -mktime_z(struct state *restrict sp, struct tm *restrict tmp) -{ - return mktime_tzname(sp, tmp, false); -} -#endif - -#if STD_INSPIRED && !USE_TIMEX_T -/* This function is obsolescent and may disappear in future releases. - Callers can instead use mktime. */ -time_t -timelocal(struct tm *tmp) -{ - if (tmp != NULL) - tmp->tm_isdst = -1; /* in case it wasn't initialized */ - return mktime(tmp); -} -#endif - -#if defined TM_GMTOFF || !USE_TIMEX_T - -# ifndef EXTERN_TIMEOFF -# ifndef timeoff -# define timeoff my_timeoff /* Don't collide with OpenBSD 7.4 . */ -# endif -# define EXTERN_TIMEOFF static -# endif - -/* This function is obsolescent and may disappear in future releases. - Callers can instead use mktime_z with a fixed-offset zone. */ -EXTERN_TIMEOFF time_t -timeoff(struct tm *tmp, long offset) -{ - if (tmp) - tmp->tm_isdst = 0; - gmtcheck(); - return time1(tmp, gmtsub, gmtptr, offset); -} -#endif - -#if !USE_TIMEX_T -time_t -timegm(struct tm *tmp) -{ - time_t t; - struct tm tmcpy; - mktmcpy(&tmcpy, tmp); - tmcpy.tm_wday = -1; - t = timeoff(&tmcpy, 0); - if (0 <= tmcpy.tm_wday) - *tmp = tmcpy; - return t; -} -#endif - -static int_fast32_t -leapcorr(struct state const *sp, time_t t) -{ - register struct lsinfo const * lp; - register int i; - - i = sp->leapcnt; - while (--i >= 0) { - lp = &sp->lsis[i]; - if (t >= lp->ls_trans) - return lp->ls_corr; - } - return 0; -} - -/* -** XXX--is the below the right way to conditionalize?? -*/ - -#if !USE_TIMEX_T -# if STD_INSPIRED - -/* NETBSD_INSPIRED_EXTERN functions are exported to callers if - NETBSD_INSPIRED is defined, and are private otherwise. */ -# if NETBSD_INSPIRED -# define NETBSD_INSPIRED_EXTERN -# else -# define NETBSD_INSPIRED_EXTERN static -# endif - -/* -** IEEE Std 1003.1 (POSIX) says that 536457599 -** shall correspond to "Wed Dec 31 23:59:59 UTC 1986", which -** is not the case if we are accounting for leap seconds. -** So, we provide the following conversion routines for use -** when exchanging timestamps with POSIX conforming systems. -*/ - -NETBSD_INSPIRED_EXTERN time_t -time2posix_z(struct state *sp, time_t t) -{ - return t - leapcorr(sp, t); -} - -time_t -time2posix(time_t t) -{ - int err = lock(); - if (err) { - errno = err; - return -1; - } - if (!lcl_is_set) - tzset_unlocked(); - if (lclptr) - t = time2posix_z(lclptr, t); - unlock(); - return t; -} - -NETBSD_INSPIRED_EXTERN time_t -posix2time_z(struct state *sp, time_t t) -{ - time_t x; - time_t y; - /* - ** For a positive leap second hit, the result - ** is not unique. For a negative leap second - ** hit, the corresponding time doesn't exist, - ** so we return an adjacent second. - */ - x = t + leapcorr(sp, t); - y = x - leapcorr(sp, x); - if (y < t) { - do { - x++; - y = x - leapcorr(sp, x); - } while (y < t); - x -= y != t; - } else if (y > t) { - do { - --x; - y = x - leapcorr(sp, x); - } while (y > t); - x += y != t; - } - return x; -} - -time_t -posix2time(time_t t) -{ - int err = lock(); - if (err) { - errno = err; - return -1; - } - if (!lcl_is_set) - tzset_unlocked(); - if (lclptr) - t = posix2time_z(lclptr, t); - unlock(); - return t; -} - -# endif /* STD_INSPIRED */ - -# if TZ_TIME_T - -# if !USG_COMPAT -# define timezone 0 -# endif - -/* Convert from the underlying system's time_t to the ersatz time_tz, - which is called 'time_t' in this file. Typically, this merely - converts the time's integer width. On some platforms, the system - time is local time not UT, or uses some epoch other than the POSIX - epoch. - - Although this code appears to define a function named 'time' that - returns time_t, the macros in private.h cause this code to actually - define a function named 'tz_time' that returns tz_time_t. The call - to sys_time invokes the underlying system's 'time' function. */ - -time_t -time(time_t *p) -{ - time_t r = sys_time(0); - if (r != (time_t) -1) { - iinntt offset = EPOCH_LOCAL ? timezone : 0; - if (offset < IINNTT_MIN + EPOCH_OFFSET - || increment_overflow_time_iinntt(&r, offset - EPOCH_OFFSET)) { - errno = EOVERFLOW; - r = -1; - } - } - if (p) - *p = r; - return r; -} - -# endif -#endif diff --git a/source/os/src/timezone/private.h b/source/os/src/timezone/private.h deleted file mode 100644 index edc188a264..0000000000 --- a/source/os/src/timezone/private.h +++ /dev/null @@ -1,1088 +0,0 @@ -/* Private header for tzdb code. */ - -#ifndef PRIVATE_H - -#define PRIVATE_H - -/* -** This file is in the public domain, so clarified as of -** 1996-06-05 by Arthur David Olson. -*/ - -/* -** This header is for use ONLY with the time conversion code. -** There is no guarantee that it will remain unchanged, -** or that it will remain at all. -** Do NOT copy it to any system include directory. -** Thank you! -*/ - -/* PORT_TO_C89 means the code should work even if the underlying - compiler and library support only C89 plus C99's 'long long' - and perhaps a few other extensions to C89. - - This macro is obsolescent, and the plan is to remove it along with - associated code. A good time to do that might be in the year 2029 - because RHEL 7 (whose GCC defaults to C89) extended life cycle - support (ELS) is scheduled to end on 2028-06-30. */ -#ifndef PORT_TO_C89 -# define PORT_TO_C89 0 -#endif - -/* SUPPORT_C89 means the tzcode library should support C89 callers - in addition to the usual support for C99-and-later callers. - This defaults to 1 as POSIX requires, even though that can trigger - latent bugs in callers. */ -#ifndef SUPPORT_C89 -# define SUPPORT_C89 1 -#endif - -#ifndef __STDC_VERSION__ -# define __STDC_VERSION__ 0 -#endif - -/* Define true, false and bool if they don't work out of the box. */ -#if PORT_TO_C89 && __STDC_VERSION__ < 199901 -# define true 1 -# define false 0 -# define bool int -#elif __STDC_VERSION__ < 202311 -# include -#endif - -#if __STDC_VERSION__ < 202311 -# undef static_assert -# define static_assert(cond) extern int static_assert_check[(cond) ? 1 : -1] -#endif - -/* -** zdump has been made independent of the rest of the time -** conversion package to increase confidence in the verification it provides. -** You can use zdump to help in verifying other implementations. -** To do this, compile with -DUSE_LTZ=0 and link without the tz library. -*/ -#ifndef USE_LTZ -# define USE_LTZ 1 -#endif - -/* This string was in the Factory zone through version 2016f. */ -#define GRANDPARENTED "Local time zone must be set--see zic manual page" - -/* -** Defaults for preprocessor symbols. -** You can override these in your C compiler options, e.g. '-DHAVE_GETTEXT=1'. -*/ - -#if !defined HAVE__GENERIC && defined __has_extension -# if !__has_extension(c_generic_selections) -# define HAVE__GENERIC 0 -# endif -#endif -/* _Generic is buggy in pre-4.9 GCC. */ -#if !defined HAVE__GENERIC && defined __GNUC__ && !defined __STRICT_ANSI__ -# define HAVE__GENERIC (4 < __GNUC__ + (9 <= __GNUC_MINOR__)) -#endif -#ifndef HAVE__GENERIC -# define HAVE__GENERIC (201112 <= __STDC_VERSION__) -#endif - -#if !defined HAVE_GETTEXT && defined __has_include -# if __has_include() -# define HAVE_GETTEXT 1 -# endif -#endif -#ifndef HAVE_GETTEXT -# define HAVE_GETTEXT 0 -#endif - -#ifndef HAVE_INCOMPATIBLE_CTIME_R -# define HAVE_INCOMPATIBLE_CTIME_R 0 -#endif - -#ifndef HAVE_LINK -# define HAVE_LINK 1 -#endif /* !defined HAVE_LINK */ - -#ifndef HAVE_MALLOC_ERRNO -# define HAVE_MALLOC_ERRNO 1 -#endif - -#ifndef HAVE_POSIX_DECLS -# define HAVE_POSIX_DECLS 1 -#endif - -#ifndef HAVE_SETENV -# define HAVE_SETENV 1 -#endif - -#ifndef HAVE_STRDUP -# define HAVE_STRDUP 1 -#endif - -#ifndef HAVE_SYMLINK -# define HAVE_SYMLINK 1 -#endif /* !defined HAVE_SYMLINK */ - -#if !defined HAVE_SYS_STAT_H && defined __has_include -# if !__has_include() -# define HAVE_SYS_STAT_H 0 -# endif -#endif -#ifndef HAVE_SYS_STAT_H -# define HAVE_SYS_STAT_H 1 -#endif - -#if !defined HAVE_UNISTD_H && defined __has_include -# if !__has_include() -# define HAVE_UNISTD_H 0 -# endif -#endif -#ifndef HAVE_UNISTD_H -# define HAVE_UNISTD_H 1 -#endif - -#ifndef NETBSD_INSPIRED -# define NETBSD_INSPIRED 1 -#endif - -#if HAVE_INCOMPATIBLE_CTIME_R -# define asctime_r _incompatible_asctime_r -# define ctime_r _incompatible_ctime_r -#endif /* HAVE_INCOMPATIBLE_CTIME_R */ - -/* Enable tm_gmtoff, tm_zone, and environ on GNUish systems. */ -#define _GNU_SOURCE 1 -/* Fix asctime_r on Solaris 11. */ -#define _POSIX_PTHREAD_SEMANTICS 1 -/* Enable strtoimax on pre-C99 Solaris 11. */ -#define __EXTENSIONS__ 1 - -/* On GNUish systems where time_t might be 32 or 64 bits, use 64. - On these platforms _FILE_OFFSET_BITS must also be 64; otherwise - setting _TIME_BITS to 64 does not work. The code does not - otherwise rely on _FILE_OFFSET_BITS being 64, since it does not - use off_t or functions like 'stat' that depend on off_t. */ -#ifndef _TIME_BITS -# ifndef _FILE_OFFSET_BITS -# define _FILE_OFFSET_BITS 64 -# endif -# if _FILE_OFFSET_BITS == 64 -# define _TIME_BITS 64 -# endif -#endif - -/* -** Nested includes -*/ - -/* Avoid clashes with NetBSD by renaming NetBSD's declarations. - If defining the 'timezone' variable, avoid a clash with FreeBSD's - 'timezone' function by renaming its declaration. */ -#define localtime_rz sys_localtime_rz -#define mktime_z sys_mktime_z -#define posix2time_z sys_posix2time_z -#define time2posix_z sys_time2posix_z -#if defined USG_COMPAT && USG_COMPAT == 2 -# define timezone sys_timezone -#endif -#define timezone_t sys_timezone_t -#define tzalloc sys_tzalloc -#define tzfree sys_tzfree -#include -#undef localtime_rz -#undef mktime_z -#undef posix2time_z -#undef time2posix_z -#if defined USG_COMPAT && USG_COMPAT == 2 -# undef timezone -#endif -#undef timezone_t -#undef tzalloc -#undef tzfree - -#include -#include -#if !PORT_TO_C89 -# include -#endif -#include /* for CHAR_BIT et al. */ -#include - -#include - -#ifndef EINVAL -# define EINVAL ERANGE -#endif - -#ifndef ELOOP -# define ELOOP EINVAL -#endif -#ifndef ENAMETOOLONG -# define ENAMETOOLONG EINVAL -#endif -#ifndef ENOMEM -# define ENOMEM EINVAL -#endif -#ifndef ENOTSUP -# define ENOTSUP EINVAL -#endif -#ifndef EOVERFLOW -# define EOVERFLOW EINVAL -#endif - -#if HAVE_GETTEXT -# include -#endif /* HAVE_GETTEXT */ - -#if HAVE_UNISTD_H -# include /* for R_OK, and other POSIX goodness */ -#endif /* HAVE_UNISTD_H */ - -/* SUPPORT_POSIX2008 means the tzcode library should support - POSIX.1-2017-and-earlier callers in addition to the usual support for - POSIX.1-2024-and-later callers; however, this can be - incompatible with POSIX.1-2024-and-later callers. - This macro is obsolescent, and the plan is to remove it - along with any code needed only when it is nonzero. - A good time to do that might be in the year 2034. - This macro's name is SUPPORT_POSIX2008 because _POSIX_VERSION == 200809 - in POSIX.1-2017, a minor revision of POSIX.1-2008. */ -#ifndef SUPPORT_POSIX2008 -# if defined _POSIX_VERSION && _POSIX_VERSION <= 200809 -# define SUPPORT_POSIX2008 1 -# else -# define SUPPORT_POSIX2008 0 -# endif -#endif - -#ifndef HAVE_DECL_ASCTIME_R -# if SUPPORT_POSIX2008 -# define HAVE_DECL_ASCTIME_R 1 -# else -# define HAVE_DECL_ASCTIME_R 0 -# endif -#endif - -#ifndef HAVE_SNPRINTF -# define HAVE_SNPRINTF (!PORT_TO_C89 || 199901 <= __STDC_VERSION__) -#endif - -#ifndef HAVE_STRFTIME_L -# if _POSIX_VERSION < 200809 -# define HAVE_STRFTIME_L 0 -# else -# define HAVE_STRFTIME_L 1 -# endif -#endif - -#ifndef USG_COMPAT -# ifndef _XOPEN_VERSION -# define USG_COMPAT 0 -# else -# define USG_COMPAT 1 -# endif -#endif - -#ifndef HAVE_TZNAME -# if _POSIX_VERSION < 198808 && !USG_COMPAT -# define HAVE_TZNAME 0 -# else -# define HAVE_TZNAME 1 -# endif -#endif - -#ifndef ALTZONE -# if defined __sun || defined _M_XENIX -# define ALTZONE 1 -# else -# define ALTZONE 0 -# endif -#endif - -#ifndef R_OK -# define R_OK 4 -#endif /* !defined R_OK */ - -#if PORT_TO_C89 - -/* -** Define HAVE_STDINT_H's default value here, rather than at the -** start, since __GLIBC__ and INTMAX_MAX's values depend on -** previously included files. glibc 2.1 and Solaris 10 and later have -** stdint.h, even with pre-C99 compilers. -*/ -#if !defined HAVE_STDINT_H && defined __has_include -# define HAVE_STDINT_H 1 /* C23 __has_include implies C99 stdint.h. */ -#endif -#ifndef HAVE_STDINT_H -# define HAVE_STDINT_H \ - (199901 <= __STDC_VERSION__ \ - || 2 < __GLIBC__ + (1 <= __GLIBC_MINOR__) \ - || __CYGWIN__ || INTMAX_MAX) -#endif /* !defined HAVE_STDINT_H */ - -#if HAVE_STDINT_H -# include -#endif /* !HAVE_STDINT_H */ - -#ifndef HAVE_INTTYPES_H -# define HAVE_INTTYPES_H HAVE_STDINT_H -#endif -#if HAVE_INTTYPES_H -# include -#endif - -/* Pre-C99 GCC compilers define __LONG_LONG_MAX__ instead of LLONG_MAX. */ -#if defined __LONG_LONG_MAX__ && !defined __STRICT_ANSI__ -# ifndef LLONG_MAX -# define LLONG_MAX __LONG_LONG_MAX__ -# endif -# ifndef LLONG_MIN -# define LLONG_MIN (-1 - LLONG_MAX) -# endif -# ifndef ULLONG_MAX -# define ULLONG_MAX (LLONG_MAX * 2ull + 1) -# endif -#endif - -#ifndef INT_FAST64_MAX -# if 1 <= LONG_MAX >> 31 >> 31 -typedef long int_fast64_t; -# define INT_FAST64_MIN LONG_MIN -# define INT_FAST64_MAX LONG_MAX -# else -/* If this fails, compile with -DHAVE_STDINT_H or with a better compiler. */ -typedef long long int_fast64_t; -# define INT_FAST64_MIN LLONG_MIN -# define INT_FAST64_MAX LLONG_MAX -# endif -#endif - -#ifndef PRIdFAST64 -# if INT_FAST64_MAX == LONG_MAX -# define PRIdFAST64 "ld" -# else -# define PRIdFAST64 "lld" -# endif -#endif - -#ifndef SCNdFAST64 -# define SCNdFAST64 PRIdFAST64 -#endif - -#ifndef INT_FAST32_MAX -# if INT_MAX >> 31 == 0 -typedef long int_fast32_t; -# define INT_FAST32_MAX LONG_MAX -# define INT_FAST32_MIN LONG_MIN -# else -typedef int int_fast32_t; -# define INT_FAST32_MAX INT_MAX -# define INT_FAST32_MIN INT_MIN -# endif -#endif - -#ifndef INT_LEAST32_MAX -typedef int_fast32_t int_least32_t; -#endif - -#ifndef INTMAX_MAX -# ifdef LLONG_MAX -typedef long long intmax_t; -# ifndef HAVE_STRTOLL -# define HAVE_STRTOLL 1 -# endif -# if HAVE_STRTOLL -# define strtoimax strtoll -# endif -# define INTMAX_MAX LLONG_MAX -# define INTMAX_MIN LLONG_MIN -# else -typedef long intmax_t; -# define INTMAX_MAX LONG_MAX -# define INTMAX_MIN LONG_MIN -# endif -# ifndef strtoimax -# define strtoimax strtol -# endif -#endif - -#ifndef PRIdMAX -# if INTMAX_MAX == LLONG_MAX -# define PRIdMAX "lld" -# else -# define PRIdMAX "ld" -# endif -#endif - -#ifndef PTRDIFF_MAX -# define PTRDIFF_MAX MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t)) -#endif - -#ifndef UINT_FAST32_MAX -typedef unsigned long uint_fast32_t; -#endif - -#ifndef UINT_FAST64_MAX -# if 3 <= ULONG_MAX >> 31 >> 31 -typedef unsigned long uint_fast64_t; -# define UINT_FAST64_MAX ULONG_MAX -# else -/* If this fails, compile with -DHAVE_STDINT_H or with a better compiler. */ -typedef unsigned long long uint_fast64_t; -# define UINT_FAST64_MAX ULLONG_MAX -# endif -#endif - -#ifndef UINTMAX_MAX -# ifdef ULLONG_MAX -typedef unsigned long long uintmax_t; -# define UINTMAX_MAX ULLONG_MAX -# else -typedef unsigned long uintmax_t; -# define UINTMAX_MAX ULONG_MAX -# endif -#endif - -#ifndef PRIuMAX -# ifdef ULLONG_MAX -# define PRIuMAX "llu" -# else -# define PRIuMAX "lu" -# endif -#endif - -#ifndef SIZE_MAX -# define SIZE_MAX ((size_t) -1) -#endif - -#endif /* PORT_TO_C89 */ - -/* The maximum size of any created object, as a signed integer. - Although the C standard does not outright prohibit larger objects, - behavior is undefined if the result of pointer subtraction does not - fit into ptrdiff_t, and the code assumes in several places that - pointer subtraction works. As a practical matter it's OK to not - support objects larger than this. */ -#define INDEX_MAX ((ptrdiff_t) min(PTRDIFF_MAX, SIZE_MAX)) - -/* Support ckd_add, ckd_sub, ckd_mul on C23 or recent-enough GCC-like - hosts, unless compiled with -DHAVE_STDCKDINT_H=0 or with pre-C23 EDG. */ -#if !defined HAVE_STDCKDINT_H && defined __has_include -# if __has_include() -# define HAVE_STDCKDINT_H 1 -# endif -#endif -#ifdef HAVE_STDCKDINT_H -# if HAVE_STDCKDINT_H -# include -# endif -#elif defined __EDG__ -/* Do nothing, to work around EDG bug . */ -#elif defined __has_builtin -# if __has_builtin(__builtin_add_overflow) -# define ckd_add(r, a, b) __builtin_add_overflow(a, b, r) -# endif -# if __has_builtin(__builtin_sub_overflow) -# define ckd_sub(r, a, b) __builtin_sub_overflow(a, b, r) -# endif -# if __has_builtin(__builtin_mul_overflow) -# define ckd_mul(r, a, b) __builtin_mul_overflow(a, b, r) -# endif -#elif 7 <= __GNUC__ -# define ckd_add(r, a, b) __builtin_add_overflow(a, b, r) -# define ckd_sub(r, a, b) __builtin_sub_overflow(a, b, r) -# define ckd_mul(r, a, b) __builtin_mul_overflow(a, b, r) -#endif - -#if (defined __has_c_attribute \ - && (202311 <= __STDC_VERSION__ || !defined __STRICT_ANSI__)) -# define HAVE___HAS_C_ATTRIBUTE true -#else -# define HAVE___HAS_C_ATTRIBUTE false -#endif - -#if HAVE___HAS_C_ATTRIBUTE -# if __has_c_attribute(deprecated) -# define ATTRIBUTE_DEPRECATED [[deprecated]] -# endif -#endif -#ifndef ATTRIBUTE_DEPRECATED -# if 3 < __GNUC__ + (2 <= __GNUC_MINOR__) -# define ATTRIBUTE_DEPRECATED __attribute__((deprecated)) -# else -# define ATTRIBUTE_DEPRECATED /* empty */ -# endif -#endif - -#if HAVE___HAS_C_ATTRIBUTE -# if __has_c_attribute(fallthrough) -# define ATTRIBUTE_FALLTHROUGH [[fallthrough]] -# endif -#endif -#ifndef ATTRIBUTE_FALLTHROUGH -# if 7 <= __GNUC__ -# define ATTRIBUTE_FALLTHROUGH __attribute__((fallthrough)) -# else -# define ATTRIBUTE_FALLTHROUGH ((void) 0) -# endif -#endif - -#if HAVE___HAS_C_ATTRIBUTE -# if __has_c_attribute(maybe_unused) -# define ATTRIBUTE_MAYBE_UNUSED [[maybe_unused]] -# endif -#endif -#ifndef ATTRIBUTE_MAYBE_UNUSED -# if 2 < __GNUC__ + (7 <= __GNUC_MINOR__) -# define ATTRIBUTE_MAYBE_UNUSED __attribute__((unused)) -# else -# define ATTRIBUTE_MAYBE_UNUSED /* empty */ -# endif -#endif - -#if HAVE___HAS_C_ATTRIBUTE -# if __has_c_attribute(noreturn) -# define ATTRIBUTE_NORETURN [[noreturn]] -# endif -#endif -#ifndef ATTRIBUTE_NORETURN -# if 201112 <= __STDC_VERSION__ -# define ATTRIBUTE_NORETURN _Noreturn -# elif 2 < __GNUC__ + (8 <= __GNUC_MINOR__) -# define ATTRIBUTE_NORETURN __attribute__((noreturn)) -# else -# define ATTRIBUTE_NORETURN /* empty */ -# endif -#endif - -#if HAVE___HAS_C_ATTRIBUTE -# if __has_c_attribute(reproducible) -# define ATTRIBUTE_REPRODUCIBLE [[reproducible]] -# endif -#endif -#ifndef ATTRIBUTE_REPRODUCIBLE -# define ATTRIBUTE_REPRODUCIBLE /* empty */ -#endif - -#if HAVE___HAS_C_ATTRIBUTE -# if __has_c_attribute(unsequenced) -# define ATTRIBUTE_UNSEQUENCED [[unsequenced]] -# endif -#endif -#ifndef ATTRIBUTE_UNSEQUENCED -# define ATTRIBUTE_UNSEQUENCED /* empty */ -#endif - -/* GCC attributes that are useful in tzcode. - __attribute__((const)) is stricter than [[unsequenced]], - so the latter is an adequate substitute in non-GCC C23 platforms. - __attribute__((pure)) is stricter than [[reproducible]], - so the latter is an adequate substitute in non-GCC C23 platforms. */ -#if __GNUC__ < 3 -# define ATTRIBUTE_CONST ATTRIBUTE_UNSEQUENCED -# define ATTRIBUTE_FORMAT(spec) /* empty */ -# define ATTRIBUTE_PURE ATTRIBUTE_REPRODUCIBLE -#else -# define ATTRIBUTE_CONST __attribute__((const)) -# define ATTRIBUTE_FORMAT(spec) __attribute__((format spec)) -# define ATTRIBUTE_PURE __attribute__((pure)) -#endif - -/* Avoid GCC bug 114833 . - Remove this macro and its uses when the bug is fixed in a GCC release, - because only the latest GCC matters for $(GCC_DEBUG_FLAGS). */ -#ifdef GCC_LINT -# define ATTRIBUTE_PURE_114833 ATTRIBUTE_PURE -#else -# define ATTRIBUTE_PURE_114833 /* empty */ -#endif - -#if (__STDC_VERSION__ < 199901 && !defined restrict \ - && (PORT_TO_C89 || defined _MSC_VER)) -# define restrict /* empty */ -#endif - -/* -** Workarounds for compilers/systems. -*/ - -#ifndef EPOCH_LOCAL -# define EPOCH_LOCAL 0 -#endif -#ifndef EPOCH_OFFSET -# define EPOCH_OFFSET 0 -#endif -#ifndef RESERVE_STD_EXT_IDS -# define RESERVE_STD_EXT_IDS 0 -#endif - -#ifdef time_tz -# define defined_time_tz true -#else -# define defined_time_tz false -#endif - -/* If standard C identifiers with external linkage (e.g., localtime) - are reserved and are not already being renamed anyway, rename them - as if compiling with '-Dtime_tz=time_t'. */ -#if !defined time_tz && RESERVE_STD_EXT_IDS && USE_LTZ -# define time_tz time_t -#endif - -/* -** Compile with -Dtime_tz=T to build the tz package with a private -** time_t type equivalent to T rather than the system-supplied time_t. -** This debugging feature can test unusual design decisions -** (e.g., time_t wider than 'long', or unsigned time_t) even on -** typical platforms. -*/ -#if defined time_tz || EPOCH_LOCAL || EPOCH_OFFSET != 0 -# define TZ_TIME_T true -#else -# define TZ_TIME_T false -#endif - -#if defined LOCALTIME_IMPLEMENTATION && TZ_TIME_T -static time_t sys_time(time_t *x) { return time(x); } -#endif - -#if TZ_TIME_T - -typedef time_tz tz_time_t; - -# undef asctime -# define asctime tz_asctime -# undef ctime -# define ctime tz_ctime -# undef difftime -# define difftime tz_difftime -# undef gmtime -# define gmtime tz_gmtime -# undef gmtime_r -# define gmtime_r tz_gmtime_r -# undef localtime -# define localtime tz_localtime -# undef localtime_r -# define localtime_r tz_localtime_r -# undef localtime_rz -# define localtime_rz tz_localtime_rz -# undef mktime -# define mktime tz_mktime -# undef mktime_z -# define mktime_z tz_mktime_z -# undef offtime -# define offtime tz_offtime -# undef posix2time -# define posix2time tz_posix2time -# undef posix2time_z -# define posix2time_z tz_posix2time_z -# undef strftime -# define strftime tz_strftime -# undef time -# define time tz_time -# undef time2posix -# define time2posix tz_time2posix -# undef time2posix_z -# define time2posix_z tz_time2posix_z -# undef time_t -# define time_t tz_time_t -# undef timegm -# define timegm tz_timegm -# undef timelocal -# define timelocal tz_timelocal -# undef timeoff -# define timeoff tz_timeoff -# undef tzalloc -# define tzalloc tz_tzalloc -# undef tzfree -# define tzfree tz_tzfree -# undef tzset -# define tzset tz_tzset -# if SUPPORT_POSIX2008 -# undef asctime_r -# define asctime_r tz_asctime_r -# undef ctime_r -# define ctime_r tz_ctime_r -# endif -# if HAVE_STRFTIME_L -# undef strftime_l -# define strftime_l tz_strftime_l -# endif -# if HAVE_TZNAME -# undef tzname -# define tzname tz_tzname -# endif -# if USG_COMPAT -# undef daylight -# define daylight tz_daylight -# undef timezone -# define timezone tz_timezone -# endif -# if ALTZONE -# undef altzone -# define altzone tz_altzone -# endif - -# if __STDC_VERSION__ < 202311 -# define DEPRECATED_IN_C23 /* empty */ -# else -# define DEPRECATED_IN_C23 ATTRIBUTE_DEPRECATED -# endif -DEPRECATED_IN_C23 char *asctime(struct tm const *); -DEPRECATED_IN_C23 char *ctime(time_t const *); -#if SUPPORT_POSIX2008 -char *asctime_r(struct tm const *restrict, char *restrict); -char *ctime_r(time_t const *, char *); -#endif -ATTRIBUTE_CONST double difftime(time_t, time_t); -size_t strftime(char *restrict, size_t, char const *restrict, - struct tm const *restrict); -# if HAVE_STRFTIME_L -size_t strftime_l(char *restrict, size_t, char const *restrict, - struct tm const *restrict, locale_t); -# endif -struct tm *gmtime(time_t const *); -struct tm *gmtime_r(time_t const *restrict, struct tm *restrict); -struct tm *localtime(time_t const *); -struct tm *localtime_r(time_t const *restrict, struct tm *restrict); -time_t mktime(struct tm *); -time_t time(time_t *); -time_t timegm(struct tm *); -void tzset(void); -#endif - -#ifndef HAVE_DECL_TIMEGM -# if (202311 <= __STDC_VERSION__ \ - || defined __GLIBC__ || defined __tm_zone /* musl */ \ - || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \ - || (defined __APPLE__ && defined __MACH__)) -# define HAVE_DECL_TIMEGM 1 -# else -# define HAVE_DECL_TIMEGM 0 -# endif -#endif -#if !HAVE_DECL_TIMEGM && !defined timegm -time_t timegm(struct tm *); -#endif - -#if !HAVE_DECL_ASCTIME_R && !defined asctime_r && SUPPORT_POSIX2008 -extern char *asctime_r(struct tm const *restrict, char *restrict); -#endif - -#ifndef HAVE_DECL_ENVIRON -# if defined environ || defined __USE_GNU -# define HAVE_DECL_ENVIRON 1 -# else -# define HAVE_DECL_ENVIRON 0 -# endif -#endif - -#if !HAVE_DECL_ENVIRON -extern char **environ; -#endif - -#if 2 <= HAVE_TZNAME + (TZ_TIME_T || !HAVE_POSIX_DECLS) -extern char *tzname[]; -#endif -#if 2 <= USG_COMPAT + (TZ_TIME_T || !HAVE_POSIX_DECLS) -extern long timezone; -extern int daylight; -#endif -#if 2 <= ALTZONE + (TZ_TIME_T || !HAVE_POSIX_DECLS) -extern long altzone; -#endif - -/* -** The STD_INSPIRED functions are similar, but most also need -** declarations if time_tz is defined. -*/ - -#ifndef STD_INSPIRED -# define STD_INSPIRED 0 -#endif -#if STD_INSPIRED -# if TZ_TIME_T || !defined offtime -struct tm *offtime(time_t const *, long); -# endif -# if TZ_TIME_T || !defined timelocal -time_t timelocal(struct tm *); -# endif -# if TZ_TIME_T || !defined timeoff -# define EXTERN_TIMEOFF -# endif -# if TZ_TIME_T || !defined time2posix -time_t time2posix(time_t); -# endif -# if TZ_TIME_T || !defined posix2time -time_t posix2time(time_t); -# endif -#endif - -/* Infer TM_ZONE on systems where this information is known, but suppress - guessing if NO_TM_ZONE is defined. Similarly for TM_GMTOFF. */ -#if (200809 < _POSIX_VERSION \ - || defined __GLIBC__ \ - || defined __tm_zone /* musl */ \ - || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \ - || (defined __APPLE__ && defined __MACH__)) -# if !defined TM_GMTOFF && !defined NO_TM_GMTOFF -# define TM_GMTOFF tm_gmtoff -# endif -# if !defined TM_ZONE && !defined NO_TM_ZONE -# define TM_ZONE tm_zone -# endif -#endif - -/* -** Define functions that are ABI compatible with NetBSD but have -** better prototypes. NetBSD 6.1.4 defines a pointer type timezone_t -** and labors under the misconception that 'const timezone_t' is a -** pointer to a constant. This use of 'const' is ineffective, so it -** is not done here. What we call 'struct state' NetBSD calls -** 'struct __state', but this is a private name so it doesn't matter. -*/ -#if NETBSD_INSPIRED -typedef struct state *timezone_t; -struct tm *localtime_rz(timezone_t restrict, time_t const *restrict, - struct tm *restrict); -time_t mktime_z(timezone_t restrict, struct tm *restrict); -timezone_t tzalloc(char const *); -void tzfree(timezone_t); -# if STD_INSPIRED -# if TZ_TIME_T || !defined posix2time_z -ATTRIBUTE_PURE time_t posix2time_z(timezone_t, time_t); -# endif -# if TZ_TIME_T || !defined time2posix_z -ATTRIBUTE_PURE time_t time2posix_z(timezone_t, time_t); -# endif -# endif -#endif - -/* -** Finally, some convenience items. -*/ - -#define TYPE_BIT(type) (CHAR_BIT * (ptrdiff_t) sizeof(type)) -#define TYPE_SIGNED(type) (((type) -1) < 0) -#define TWOS_COMPLEMENT(t) ((t) ~ (t) 0 < 0) - -/* Minimum and maximum of two values. Use lower case to avoid - naming clashes with standard include files. */ -#define max(a, b) ((a) > (b) ? (a) : (b)) -#define min(a, b) ((a) < (b) ? (a) : (b)) - -/* Max and min values of the integer type T, of which only the bottom - B bits are used, and where the highest-order used bit is considered - to be a sign bit if T is signed. */ -#define MAXVAL(t, b) \ - ((t) (((t) 1 << ((b) - 1 - TYPE_SIGNED(t))) \ - - 1 + ((t) 1 << ((b) - 1 - TYPE_SIGNED(t))))) -#define MINVAL(t, b) \ - ((t) (TYPE_SIGNED(t) ? - TWOS_COMPLEMENT(t) - MAXVAL(t, b) : 0)) - -/* The extreme time values, assuming no padding. */ -#define TIME_T_MIN_NO_PADDING MINVAL(time_t, TYPE_BIT(time_t)) -#define TIME_T_MAX_NO_PADDING MAXVAL(time_t, TYPE_BIT(time_t)) - -/* The extreme time values. These are macros, not constants, so that - any portability problems occur only when compiling .c files that use - the macros, which is safer for applications that need only zdump and zic. - This implementation assumes no padding if time_t is signed and - either the compiler lacks support for _Generic or time_t is not one - of the standard signed integer types. */ -#if HAVE__GENERIC -# define TIME_T_MIN \ - _Generic((time_t) 0, \ - signed char: SCHAR_MIN, short: SHRT_MIN, \ - int: INT_MIN, long: LONG_MIN, long long: LLONG_MIN, \ - default: TIME_T_MIN_NO_PADDING) -# define TIME_T_MAX \ - (TYPE_SIGNED(time_t) \ - ? _Generic((time_t) 0, \ - signed char: SCHAR_MAX, short: SHRT_MAX, \ - int: INT_MAX, long: LONG_MAX, long long: LLONG_MAX, \ - default: TIME_T_MAX_NO_PADDING) \ - : (time_t) -1) -enum { SIGNED_PADDING_CHECK_NEEDED - = _Generic((time_t) 0, - signed char: false, short: false, - int: false, long: false, long long: false, - default: true) }; -#else -# define TIME_T_MIN TIME_T_MIN_NO_PADDING -# define TIME_T_MAX TIME_T_MAX_NO_PADDING -enum { SIGNED_PADDING_CHECK_NEEDED = true }; -#endif -/* Try to check the padding assumptions. Although TIME_T_MAX and the - following check can both have undefined behavior on oddball - platforms due to shifts exceeding widths of signed integers, these - platforms' compilers are likely to diagnose these issues in integer - constant expressions, so it shouldn't hurt to check statically. */ -static_assert(! TYPE_SIGNED(time_t) || ! SIGNED_PADDING_CHECK_NEEDED - || TIME_T_MAX >> (TYPE_BIT(time_t) - 2) == 1); - -/* -** 302 / 1000 is log10(2.0) rounded up. -** Subtract one for the sign bit if the type is signed; -** add one for integer division truncation; -** add one more for a minus sign if the type is signed. -*/ -#define INT_STRLEN_MAXIMUM(type) \ - ((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + \ - 1 + TYPE_SIGNED(type)) - -/* -** INITIALIZE(x) -*/ - -#ifdef GCC_LINT -# define INITIALIZE(x) ((x) = 0) -#else -# define INITIALIZE(x) -#endif - -/* Whether memory access must strictly follow the C standard. - If 0, it's OK to read uninitialized storage so long as the value is - not relied upon. Defining it to 0 lets mktime access parts of - struct tm that might be uninitialized, as a heuristic when the - standard doesn't say what to return and when tm_gmtoff can help - mktime likely infer a better value. */ -#ifndef UNINIT_TRAP -# define UNINIT_TRAP 0 -#endif - -/* strftime.c sometimes needs access to timeoff if it is not already public. - tz_private_timeoff should be used only by localtime.c and strftime.c. */ -#if (!defined EXTERN_TIMEOFF \ - && defined TM_GMTOFF && (200809 < _POSIX_VERSION || ! UNINIT_TRAP)) -# ifndef timeoff -# define timeoff tz_private_timeoff -# endif -# define EXTERN_TIMEOFF -#endif -#ifdef EXTERN_TIMEOFF -time_t timeoff(struct tm *, long); -#endif - -#ifdef DEBUG -# undef unreachable -# define unreachable() abort() -#elif !defined unreachable -# ifdef __has_builtin -# if __has_builtin(__builtin_unreachable) -# define unreachable() __builtin_unreachable() -# endif -# elif 4 < __GNUC__ + (5 <= __GNUC_MINOR__) -# define unreachable() __builtin_unreachable() -# endif -# ifndef unreachable -# define unreachable() ((void) 0) -# endif -#endif - -/* -** For the benefit of GNU folk... -** '_(MSGID)' uses the current locale's message library string for MSGID. -** The default is to use gettext if available, and use MSGID otherwise. -*/ - -#if HAVE_GETTEXT -#define _(msgid) gettext(msgid) -#else /* !HAVE_GETTEXT */ -#define _(msgid) msgid -#endif /* !HAVE_GETTEXT */ - -#if !defined TZ_DOMAIN && defined HAVE_GETTEXT -# define TZ_DOMAIN "tz" -#endif - -#if HAVE_INCOMPATIBLE_CTIME_R -#undef asctime_r -#undef ctime_r -char *asctime_r(struct tm const *restrict, char *restrict); -char *ctime_r(time_t const *, char *); -#endif /* HAVE_INCOMPATIBLE_CTIME_R */ - -/* Handy macros that are independent of tzfile implementation. */ - -enum { - SECSPERMIN = 60, - MINSPERHOUR = 60, - SECSPERHOUR = SECSPERMIN * MINSPERHOUR, - HOURSPERDAY = 24, - DAYSPERWEEK = 7, - DAYSPERNYEAR = 365, - DAYSPERLYEAR = DAYSPERNYEAR + 1, - MONSPERYEAR = 12, - YEARSPERREPEAT = 400 /* years before a Gregorian repeat */ -}; - -#define SECSPERDAY ((int_fast32_t) SECSPERHOUR * HOURSPERDAY) - -#define DAYSPERREPEAT ((int_fast32_t) 400 * 365 + 100 - 4 + 1) -#define SECSPERREPEAT ((int_fast64_t) DAYSPERREPEAT * SECSPERDAY) -#define AVGSECSPERYEAR (SECSPERREPEAT / YEARSPERREPEAT) - -/* How many years to generate (in zic.c) or search through (in localtime.c). - This is two years larger than the obvious 400, to avoid edge cases. - E.g., suppose a rule applies from 2012 on with transitions - in March and September, plus one-off transitions in November 2013, - and suppose the rule cannot be expressed as a proleptic TZ string. - If zic looked only at the last 400 years, it would set max_year=2413, - with the intent that the 400 years 2014 through 2413 will be repeated. - The last transition listed in the tzfile would be in 2413-09, - less than 400 years after the last one-off transition in 2013-11. - Two years is not overkill for localtime.c, as a one-year bump - would mishandle 2023d's America/Ciudad_Juarez for November 2422. */ -enum { years_of_observations = YEARSPERREPEAT + 2 }; - -enum { - TM_SUNDAY, - TM_MONDAY, - TM_TUESDAY, - TM_WEDNESDAY, - TM_THURSDAY, - TM_FRIDAY, - TM_SATURDAY -}; - -enum { - TM_JANUARY, - TM_FEBRUARY, - TM_MARCH, - TM_APRIL, - TM_MAY, - TM_JUNE, - TM_JULY, - TM_AUGUST, - TM_SEPTEMBER, - TM_OCTOBER, - TM_NOVEMBER, - TM_DECEMBER -}; - -enum { - TM_YEAR_BASE = 1900, - TM_WDAY_BASE = TM_MONDAY, - EPOCH_YEAR = 1970, - EPOCH_WDAY = TM_THURSDAY -}; - -#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) - -/* -** Since everything in isleap is modulo 400 (or a factor of 400), we know that -** isleap(y) == isleap(y % 400) -** and so -** isleap(a + b) == isleap((a + b) % 400) -** or -** isleap(a + b) == isleap(a % 400 + b % 400) -** This is true even if % means modulo rather than Fortran remainder -** (which is allowed by C89 but not by C99 or later). -** We use this to avoid addition overflow problems. -*/ - -#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400) - -#endif /* !defined PRIVATE_H */ diff --git a/source/os/src/timezone/strftime.c b/source/os/src/timezone/strftime.c deleted file mode 100644 index 487a5234cb..0000000000 --- a/source/os/src/timezone/strftime.c +++ /dev/null @@ -1,711 +0,0 @@ -/* Convert a broken-down timestamp to a string. */ - -/* Copyright 1989 The Regents of the University of California. - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - 3. Neither the name of the University nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - SUCH DAMAGE. */ - -/* -** Based on the UCB version with the copyright notice appearing above. -** -** This is ANSIish only when "multibyte character == plain character". -*/ - -#include "private.h" - -#include -#include -#include - -/* If true, the value returned by an idealized unlimited-range mktime - always fits into an integer type with bounds MIN and MAX. - If false, the value might not fit. - This macro is usable in #if if its arguments are. - Add or subtract 2**31 - 1 for the maximum UT offset allowed in a TZif file, - divide by the maximum number of non-leap seconds in a year, - divide again by two just to be safe, - and account for the tm_year origin (1900) and time_t origin (1970). */ -#define MKTIME_FITS_IN(min, max) \ - ((min) < 0 \ - && ((min) + 0x7fffffff) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900 < INT_MIN \ - && INT_MAX < ((max) - 0x7fffffff) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900) - -/* MKTIME_MIGHT_OVERFLOW is true if mktime can fail due to time_t overflow - or if it is not known whether mktime can fail, - and is false if mktime definitely cannot fail. - This macro is usable in #if, and so does not use TIME_T_MAX or sizeof. - If the builder has not configured this macro, guess based on what - known platforms do. When in doubt, guess true. */ -#ifndef MKTIME_MIGHT_OVERFLOW -# if defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ -# include -# endif -# if ((/* The following heuristics assume native time_t. */ \ - defined_time_tz) \ - || ((/* Traditional time_t is 'long', so if 'long' is not wide enough \ - assume overflow unless we're on a known-safe host. */ \ - !MKTIME_FITS_IN(LONG_MIN, LONG_MAX)) \ - && (/* GNU C Library 2.29 (2019-02-01) and later has 64-bit time_t \ - if __TIMESIZE is 64. */ \ - !defined __TIMESIZE || __TIMESIZE < 64) \ - && (/* FreeBSD 12 r320347 (__FreeBSD_version 1200036; 2017-06-26), \ - and later has 64-bit time_t on all platforms but i386 which \ - is currently scheduled for end-of-life on 2028-11-30. */ \ - !defined __FreeBSD_version || __FreeBSD_version < 1200036 \ - || defined __i386) \ - && (/* NetBSD 6.0 (2012-10-17) and later has 64-bit time_t. */ \ - !defined __NetBSD_Version__ || __NetBSD_Version__ < 600000000) \ - && (/* OpenBSD 5.5 (2014-05-01) and later has 64-bit time_t. */ \ - !defined OpenBSD || OpenBSD < 201405))) -# define MKTIME_MIGHT_OVERFLOW 1 -# else -# define MKTIME_MIGHT_OVERFLOW 0 -# endif -#endif -/* Check that MKTIME_MIGHT_OVERFLOW is consistent with time_t's range. */ -static_assert(MKTIME_MIGHT_OVERFLOW - || MKTIME_FITS_IN(TIME_T_MIN, TIME_T_MAX)); - -#if MKTIME_MIGHT_OVERFLOW -/* Do this after system includes as it redefines time_t, mktime, timeoff. */ -# define USE_TIMEX_T true -# include "localtime.c" -#endif - -#ifndef DEPRECATE_TWO_DIGIT_YEARS -# define DEPRECATE_TWO_DIGIT_YEARS 0 -#endif - -struct lc_time_T { - const char * mon[MONSPERYEAR]; - const char * month[MONSPERYEAR]; - const char * wday[DAYSPERWEEK]; - const char * weekday[DAYSPERWEEK]; - const char * X_fmt; - const char * x_fmt; - const char * c_fmt; - const char * am; - const char * pm; - const char * date_fmt; -}; - -static const struct lc_time_T C_time_locale = { - { - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" - }, { - "January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December" - }, { - "Sun", "Mon", "Tue", "Wed", - "Thu", "Fri", "Sat" - }, { - "Sunday", "Monday", "Tuesday", "Wednesday", - "Thursday", "Friday", "Saturday" - }, - - /* X_fmt */ - "%H:%M:%S", - - /* - ** x_fmt - ** C99 and later require this format. - ** Using just numbers (as here) makes Quakers happier; - ** it's also compatible with SVR4. - */ - "%m/%d/%y", - - /* - ** c_fmt - ** C99 and later require this format. - ** Previously this code used "%D %X", but we now conform to C99. - ** Note that - ** "%a %b %d %H:%M:%S %Y" - ** is used by Solaris 2.3. - */ - "%a %b %e %T %Y", - - /* am */ - "AM", - - /* pm */ - "PM", - - /* date_fmt */ - "%a %b %e %H:%M:%S %Z %Y" -}; - -enum warn { IN_NONE, IN_SOME, IN_THIS, IN_ALL }; - -static char * _add(const char *, char *, const char *); -static char * _conv(int, const char *, char *, const char *); -static char * _fmt(const char *, const struct tm *, char *, const char *, - enum warn *); -static char * _yconv(int, int, bool, bool, char *, char const *); - -#ifndef YEAR_2000_NAME -# define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS" -#endif /* !defined YEAR_2000_NAME */ - -#if HAVE_STRFTIME_L -size_t -strftime_l(char *restrict s, size_t maxsize, char const *restrict format, - struct tm const *restrict t, - ATTRIBUTE_MAYBE_UNUSED locale_t locale) -{ - /* Just call strftime, as only the C locale is supported. */ - return strftime(s, maxsize, format, t); -} -#endif - -size_t -strftime(char *restrict s, size_t maxsize, char const *restrict format, - struct tm const *restrict t) -{ - char * p; - int saved_errno = errno; - enum warn warn = IN_NONE; - - tzset(); - p = _fmt(format, t, s, s + maxsize, &warn); - if (DEPRECATE_TWO_DIGIT_YEARS - && warn != IN_NONE && getenv(YEAR_2000_NAME)) { - fprintf(stderr, "\n"); - fprintf(stderr, "strftime format \"%s\" ", format); - fprintf(stderr, "yields only two digits of years in "); - if (warn == IN_SOME) - fprintf(stderr, "some locales"); - else if (warn == IN_THIS) - fprintf(stderr, "the current locale"); - else fprintf(stderr, "all locales"); - fprintf(stderr, "\n"); - } - if (p == s + maxsize) { - errno = ERANGE; - return 0; - } - *p = '\0'; - errno = saved_errno; - return p - s; -} - -static char * -_fmt(const char *format, const struct tm *t, char *pt, - const char *ptlim, enum warn *warnp) -{ - struct lc_time_T const *Locale = &C_time_locale; - - for ( ; *format; ++format) { - if (*format == '%') { -label: - switch (*++format) { - default: - /* Output unknown conversion specifiers as-is, - to aid debugging. This includes '%' at - format end. This conforms to C23 section - 7.29.3.5 paragraph 6, which says behavior - is undefined here. */ - --format; - break; - case 'A': - pt = _add((t->tm_wday < 0 || - t->tm_wday >= DAYSPERWEEK) ? - "?" : Locale->weekday[t->tm_wday], - pt, ptlim); - continue; - case 'a': - pt = _add((t->tm_wday < 0 || - t->tm_wday >= DAYSPERWEEK) ? - "?" : Locale->wday[t->tm_wday], - pt, ptlim); - continue; - case 'B': - pt = _add((t->tm_mon < 0 || - t->tm_mon >= MONSPERYEAR) ? - "?" : Locale->month[t->tm_mon], - pt, ptlim); - continue; - case 'b': - case 'h': - pt = _add((t->tm_mon < 0 || - t->tm_mon >= MONSPERYEAR) ? - "?" : Locale->mon[t->tm_mon], - pt, ptlim); - continue; - case 'C': - /* - ** %C used to do a... - ** _fmt("%a %b %e %X %Y", t); - ** ...whereas now POSIX 1003.2 calls for - ** something completely different. - ** (ado, 1993-05-24) - */ - pt = _yconv(t->tm_year, TM_YEAR_BASE, - true, false, pt, ptlim); - continue; - case 'c': - { - enum warn warn2 = IN_SOME; - - pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2); - if (warn2 == IN_ALL) - warn2 = IN_THIS; - if (warn2 > *warnp) - *warnp = warn2; - } - continue; - case 'D': - pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp); - continue; - case 'd': - pt = _conv(t->tm_mday, "%02d", pt, ptlim); - continue; - case 'E': - case 'O': - /* - ** Locale modifiers of C99 and later. - ** The sequences - ** %Ec %EC %Ex %EX %Ey %EY - ** %Od %oe %OH %OI %Om %OM - ** %OS %Ou %OU %OV %Ow %OW %Oy - ** are supposed to provide alternative - ** representations. - */ - goto label; - case 'e': - pt = _conv(t->tm_mday, "%2d", pt, ptlim); - continue; - case 'F': - pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp); - continue; - case 'H': - pt = _conv(t->tm_hour, "%02d", pt, ptlim); - continue; - case 'I': - pt = _conv((t->tm_hour % 12) ? - (t->tm_hour % 12) : 12, - "%02d", pt, ptlim); - continue; - case 'j': - pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim); - continue; - case 'k': - /* - ** This used to be... - ** _conv(t->tm_hour % 12 ? - ** t->tm_hour % 12 : 12, 2, ' '); - ** ...and has been changed to the below to - ** match SunOS 4.1.1 and Arnold Robbins' - ** strftime version 3.0. That is, "%k" and - ** "%l" have been swapped. - ** (ado, 1993-05-24) - */ - pt = _conv(t->tm_hour, "%2d", pt, ptlim); - continue; -#ifdef KITCHEN_SINK - case 'K': - /* - ** After all this time, still unclaimed! - */ - pt = _add("kitchen sink", pt, ptlim); - continue; -#endif /* defined KITCHEN_SINK */ - case 'l': - /* - ** This used to be... - ** _conv(t->tm_hour, 2, ' '); - ** ...and has been changed to the below to - ** match SunOS 4.1.1 and Arnold Robbin's - ** strftime version 3.0. That is, "%k" and - ** "%l" have been swapped. - ** (ado, 1993-05-24) - */ - pt = _conv((t->tm_hour % 12) ? - (t->tm_hour % 12) : 12, - "%2d", pt, ptlim); - continue; - case 'M': - pt = _conv(t->tm_min, "%02d", pt, ptlim); - continue; - case 'm': - pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim); - continue; - case 'n': - pt = _add("\n", pt, ptlim); - continue; - case 'p': - pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ? - Locale->pm : - Locale->am, - pt, ptlim); - continue; - case 'R': - pt = _fmt("%H:%M", t, pt, ptlim, warnp); - continue; - case 'r': - pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp); - continue; - case 'S': - pt = _conv(t->tm_sec, "%02d", pt, ptlim); - continue; - case 's': - { - struct tm tm; - char buf[INT_STRLEN_MAXIMUM( - time_t) + 1]; - time_t mkt; - - tm.tm_sec = t->tm_sec; - tm.tm_min = t->tm_min; - tm.tm_hour = t->tm_hour; - tm.tm_mday = t->tm_mday; - tm.tm_mon = t->tm_mon; - tm.tm_year = t->tm_year; - - /* Get the time_t value for TM. - Native time_t, or its redefinition - by localtime.c above, is wide enough - so that this cannot overflow. */ -#ifdef TM_GMTOFF - mkt = timeoff(&tm, t->TM_GMTOFF); -#else - tm.tm_isdst = t->tm_isdst; - mkt = mktime(&tm); -#endif - if (TYPE_SIGNED(time_t)) { - intmax_t n = mkt; - sprintf(buf, "%"PRIdMAX, n); - } else { - uintmax_t n = mkt; - sprintf(buf, "%"PRIuMAX, n); - } - pt = _add(buf, pt, ptlim); - } - continue; - case 'T': - pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp); - continue; - case 't': - pt = _add("\t", pt, ptlim); - continue; - case 'U': - pt = _conv((t->tm_yday + DAYSPERWEEK - - t->tm_wday) / DAYSPERWEEK, - "%02d", pt, ptlim); - continue; - case 'u': - /* - ** From Arnold Robbins' strftime version 3.0: - ** "ISO 8601: Weekday as a decimal number - ** [1 (Monday) - 7]" - ** (ado, 1993-05-24) - */ - pt = _conv((t->tm_wday == 0) ? - DAYSPERWEEK : t->tm_wday, - "%d", pt, ptlim); - continue; - case 'V': /* ISO 8601 week number */ - case 'G': /* ISO 8601 year (four digits) */ - case 'g': /* ISO 8601 year (two digits) */ -/* -** From Arnold Robbins' strftime version 3.0: "the week number of the -** year (the first Monday as the first day of week 1) as a decimal number -** (01-53)." -** (ado, 1993-05-24) -** -** From by Markus Kuhn: -** "Week 01 of a year is per definition the first week which has the -** Thursday in this year, which is equivalent to the week which contains -** the fourth day of January. In other words, the first week of a new year -** is the week which has the majority of its days in the new year. Week 01 -** might also contain days from the previous year and the week before week -** 01 of a year is the last week (52 or 53) of the previous year even if -** it contains days from the new year. A week starts with Monday (day 1) -** and ends with Sunday (day 7). For example, the first week of the year -** 1997 lasts from 1996-12-30 to 1997-01-05..." -** (ado, 1996-01-02) -*/ - { - int year; - int base; - int yday; - int wday; - int w; - - year = t->tm_year; - base = TM_YEAR_BASE; - yday = t->tm_yday; - wday = t->tm_wday; - for ( ; ; ) { - int len; - int bot; - int top; - - len = isleap_sum(year, base) ? - DAYSPERLYEAR : - DAYSPERNYEAR; - /* - ** What yday (-3 ... 3) does - ** the ISO year begin on? - */ - bot = ((yday + 11 - wday) % - DAYSPERWEEK) - 3; - /* - ** What yday does the NEXT - ** ISO year begin on? - */ - top = bot - - (len % DAYSPERWEEK); - if (top < -3) - top += DAYSPERWEEK; - top += len; - if (yday >= top) { - ++base; - w = 1; - break; - } - if (yday >= bot) { - w = 1 + ((yday - bot) / - DAYSPERWEEK); - break; - } - --base; - yday += isleap_sum(year, base) ? - DAYSPERLYEAR : - DAYSPERNYEAR; - } -#ifdef XPG4_1994_04_09 - if ((w == 52 && - t->tm_mon == TM_JANUARY) || - (w == 1 && - t->tm_mon == TM_DECEMBER)) - w = 53; -#endif /* defined XPG4_1994_04_09 */ - if (*format == 'V') - pt = _conv(w, "%02d", - pt, ptlim); - else if (*format == 'g') { - *warnp = IN_ALL; - pt = _yconv(year, base, - false, true, - pt, ptlim); - } else pt = _yconv(year, base, - true, true, - pt, ptlim); - } - continue; - case 'v': - /* - ** From Arnold Robbins' strftime version 3.0: - ** "date as dd-bbb-YYYY" - ** (ado, 1993-05-24) - */ - pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp); - continue; - case 'W': - pt = _conv((t->tm_yday + DAYSPERWEEK - - (t->tm_wday ? - (t->tm_wday - 1) : - (DAYSPERWEEK - 1))) / DAYSPERWEEK, - "%02d", pt, ptlim); - continue; - case 'w': - pt = _conv(t->tm_wday, "%d", pt, ptlim); - continue; - case 'X': - pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp); - continue; - case 'x': - { - enum warn warn2 = IN_SOME; - - pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2); - if (warn2 == IN_ALL) - warn2 = IN_THIS; - if (warn2 > *warnp) - *warnp = warn2; - } - continue; - case 'y': - *warnp = IN_ALL; - pt = _yconv(t->tm_year, TM_YEAR_BASE, - false, true, - pt, ptlim); - continue; - case 'Y': - pt = _yconv(t->tm_year, TM_YEAR_BASE, - true, true, - pt, ptlim); - continue; - case 'Z': -#ifdef TM_ZONE - pt = _add(t->TM_ZONE, pt, ptlim); -#elif HAVE_TZNAME - if (t->tm_isdst >= 0) - pt = _add(tzname[t->tm_isdst != 0], - pt, ptlim); -#endif - /* - ** C99 and later say that %Z must be - ** replaced by the empty string if the - ** time zone abbreviation is not - ** determinable. - */ - continue; - case 'z': -#if defined TM_GMTOFF || USG_COMPAT || ALTZONE - { - long diff; - char const * sign; - bool negative; - -# ifdef TM_GMTOFF - diff = t->TM_GMTOFF; -# else - /* - ** C99 and later say that the UT offset must - ** be computed by looking only at - ** tm_isdst. This requirement is - ** incorrect, since it means the code - ** must rely on magic (in this case - ** altzone and timezone), and the - ** magic might not have the correct - ** offset. Doing things correctly is - ** tricky and requires disobeying the standard; - ** see GNU C strftime for details. - ** For now, punt and conform to the - ** standard, even though it's incorrect. - ** - ** C99 and later say that %z must be replaced by - ** the empty string if the time zone is not - ** determinable, so output nothing if the - ** appropriate variables are not available. - */ - if (t->tm_isdst < 0) - continue; - if (t->tm_isdst == 0) -# if USG_COMPAT - diff = -timezone; -# else - continue; -# endif - else -# if ALTZONE - diff = -altzone; -# else - continue; -# endif -# endif - negative = diff < 0; - if (diff == 0) { -# ifdef TM_ZONE - negative = t->TM_ZONE[0] == '-'; -# else - negative = t->tm_isdst < 0; -# if HAVE_TZNAME - if (tzname[t->tm_isdst != 0][0] == '-') - negative = true; -# endif -# endif - } - if (negative) { - sign = "-"; - diff = -diff; - } else sign = "+"; - pt = _add(sign, pt, ptlim); - diff /= SECSPERMIN; - diff = (diff / MINSPERHOUR) * 100 + - (diff % MINSPERHOUR); - pt = _conv(diff, "%04d", pt, ptlim); - } -#endif - continue; - case '+': - pt = _fmt(Locale->date_fmt, t, pt, ptlim, - warnp); - continue; - case '%': - break; - } - } - if (pt == ptlim) - break; - *pt++ = *format; - } - return pt; -} - -static char * -_conv(int n, const char *format, char *pt, const char *ptlim) -{ - char buf[INT_STRLEN_MAXIMUM(int) + 1]; - - sprintf(buf, format, n); - return _add(buf, pt, ptlim); -} - -static char * -_add(const char *str, char *pt, const char *ptlim) -{ - while (pt < ptlim && (*pt = *str++) != '\0') - ++pt; - return pt; -} - -/* -** POSIX and the C Standard are unclear or inconsistent about -** what %C and %y do if the year is negative or exceeds 9999. -** Use the convention that %C concatenated with %y yields the -** same output as %Y, and that %Y contains at least 4 bytes, -** with more only if necessary. -*/ - -static char * -_yconv(int a, int b, bool convert_top, bool convert_yy, - char *pt, const char *ptlim) -{ - register int lead; - register int trail; - - int DIVISOR = 100; - trail = a % DIVISOR + b % DIVISOR; - lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR; - trail %= DIVISOR; - if (trail < 0 && lead > 0) { - trail += DIVISOR; - --lead; - } else if (lead < 0 && trail > 0) { - trail -= DIVISOR; - ++lead; - } - if (convert_top) { - if (lead == 0 && trail < 0) - pt = _add("-0", pt, ptlim); - else pt = _conv(lead, "%02d", pt, ptlim); - } - if (convert_yy) - pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim); - return pt; -} diff --git a/source/os/src/timezone/tzdir.h b/source/os/src/timezone/tzdir.h deleted file mode 100644 index dc5668ec39..0000000000 --- a/source/os/src/timezone/tzdir.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef TZDEFAULT -# define TZDEFAULT "/etc/localtime" /* default zone */ -#endif -#ifndef TZDIR -# define TZDIR "/Users/mingmingwanng/source_code/TDengine/debug/build/share/timezone/" /* TZif directory */ -#endif diff --git a/source/os/src/timezone/tzfile.h b/source/os/src/timezone/tzfile.h deleted file mode 100644 index f8c6c8c550..0000000000 --- a/source/os/src/timezone/tzfile.h +++ /dev/null @@ -1,121 +0,0 @@ -/* Layout and location of TZif files. */ - -#ifndef TZFILE_H - -#define TZFILE_H - -/* -** This file is in the public domain, so clarified as of -** 1996-06-05 by Arthur David Olson. -*/ - -/* -** This header is for use ONLY with the time conversion code. -** There is no guarantee that it will remain unchanged, -** or that it will remain at all. -** Do NOT copy it to any system include directory. -** Thank you! -*/ - -/* -** Information about time zone files. -*/ - -#ifndef TZDEFRULES -# define TZDEFRULES "posixrules" -#endif /* !defined TZDEFRULES */ - - -/* See Internet RFC 9636 for more details about the following format. */ - -/* -** Each file begins with. . . -*/ - -#define TZ_MAGIC "TZif" - -struct tzhead { - char tzh_magic[4]; /* TZ_MAGIC */ - char tzh_version[1]; /* '\0' or '2'-'4' as of 2021 */ - char tzh_reserved[15]; /* reserved; must be zero */ - char tzh_ttisutcnt[4]; /* coded number of trans. time flags */ - char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */ - char tzh_leapcnt[4]; /* coded number of leap seconds */ - char tzh_timecnt[4]; /* coded number of transition times */ - char tzh_typecnt[4]; /* coded number of local time types */ - char tzh_charcnt[4]; /* coded number of abbr. chars */ -}; - -/* -** . . .followed by. . . -** -** tzh_timecnt (char [4])s coded transition times a la time(2) -** tzh_timecnt (unsigned char)s types of local time starting at above -** tzh_typecnt repetitions of -** one (char [4]) coded UT offset in seconds -** one (unsigned char) used to set tm_isdst -** one (unsigned char) that's an abbreviation list index -** tzh_charcnt (char)s '\0'-terminated zone abbreviations -** tzh_leapcnt repetitions of -** one (char [4]) coded leap second transition times -** one (char [4]) total correction after above -** tzh_ttisstdcnt (char)s indexed by type; if 1, transition -** time is standard time, if 0, -** transition time is local (wall clock) -** time; if absent, transition times are -** assumed to be local time -** tzh_ttisutcnt (char)s indexed by type; if 1, transition -** time is UT, if 0, transition time is -** local time; if absent, transition -** times are assumed to be local time. -** When this is 1, the corresponding -** std/wall indicator must also be 1. -*/ - -/* -** If tzh_version is '2' or greater, the above is followed by a second instance -** of tzhead and a second instance of the data in which each coded transition -** time uses 8 rather than 4 chars, -** then a POSIX.1-2017 proleptic TZ string for use in handling -** instants after the last transition time stored in the file -** (with nothing between the newlines if there is no POSIX.1-2017 -** representation for such instants). -** -** If tz_version is '3' or greater, the TZ string can be any POSIX.1-2024 -** proleptic TZ string, which means the above is extended as follows. -** First, the TZ string's hour offset may range from -167 -** through 167 as compared to the range 0 through 24 required -** by POSIX.1-2017 and earlier. -** Second, its DST start time may be January 1 at 00:00 and its stop -** time December 31 at 24:00 plus the difference between DST and -** standard time, indicating DST all year. -*/ - -/* -** In the current implementation, "tzset()" refuses to deal with files that -** exceed any of the limits below. -*/ - -#ifndef TZ_MAX_TIMES -/* This must be at least 242 for Europe/London with 'zic -b fat'. */ -# define TZ_MAX_TIMES 2000 -#endif /* !defined TZ_MAX_TIMES */ - -#ifndef TZ_MAX_TYPES -/* This must be at least 18 for Europe/Vilnius with 'zic -b fat'. */ -# define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */ -#endif /* !defined TZ_MAX_TYPES */ - -#ifndef TZ_MAX_CHARS -/* This must be at least 40 for America/Anchorage. */ -# define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */ - /* (limited by what unsigned chars can hold) */ -#endif /* !defined TZ_MAX_CHARS */ - -#ifndef TZ_MAX_LEAPS -/* This must be at least 27 for leap seconds from 1972 through mid-2023. - There's a plan to discontinue leap seconds by 2035. */ -# define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */ -#endif /* !defined TZ_MAX_LEAPS */ - -#endif /* !defined TZFILE_H */ From f23633e83de509ae25b10eb168a7ca16a3dbfce8 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Tue, 3 Dec 2024 22:28:18 +0800 Subject: [PATCH 22/45] feat:[TD-32642] add timezone support in windows --- contrib/CMakeLists.txt | 2 +- source/os/CMakeLists.txt | 2 +- source/os/test/osTimeTests.cpp | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index c5ed4cf52d..af05b2c47c 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -665,7 +665,7 @@ MESSAGE(STATUS "timezone file path: " ${TZ_OUTPUT_PATH}) if(NOT ${TD_WINDOWS}) execute_process( - COMMAND make TZDIR=${TZ_OUTPUT_PATH}/ clean libtz.a + COMMAND make CFLAGS+=-fPIC TZDIR=${TZ_OUTPUT_PATH}/ clean libtz.a WORKING_DIRECTORY "${TD_CONTRIB_DIR}/tz" ) diff --git a/source/os/CMakeLists.txt b/source/os/CMakeLists.txt index 220fef072b..a0f9e5b87a 100644 --- a/source/os/CMakeLists.txt +++ b/source/os/CMakeLists.txt @@ -72,7 +72,7 @@ ENDIF () if(NOT ${TD_WINDOWS}) find_library(tz libtz.a "${TD_SOURCE_DIR}/contrib/tz") - target_link_libraries(os PUBLIC ${tz}) + target_link_libraries(os PUBLIC ${tz}) endif(NOT ${TD_WINDOWS}) if(${BUILD_TEST}) diff --git a/source/os/test/osTimeTests.cpp b/source/os/test/osTimeTests.cpp index 501502f7e6..5c8c837dca 100644 --- a/source/os/test/osTimeTests.cpp +++ b/source/os/test/osTimeTests.cpp @@ -49,7 +49,7 @@ TEST(osTimeTests, taosLocalTime) { // Test 4: Test when timep is negative on Windows #ifdef WINDOWS time_t pos_timep = 1609459200; // 2021-01-01 08:00:00 - local_time = taosLocalTime(&pos_timep, &result, NULL, 0); + local_time = taosLocalTime(&pos_timep, &result, NULL, 0, NULL); ASSERT_NE(local_time, nullptr); ASSERT_EQ(local_time->tm_year, 121); ASSERT_EQ(local_time->tm_mon, 0); @@ -59,7 +59,7 @@ TEST(osTimeTests, taosLocalTime) { ASSERT_EQ(local_time->tm_sec, 0); time_t neg_timep = -1617531000; // 1918-09-29 21:50:00 - local_time = taosLocalTime(&neg_timep, &result, NULL, 0); + local_time = taosLocalTime(&neg_timep, &result, NULL, 0, NULL); ASSERT_NE(local_time, nullptr); ASSERT_EQ(local_time->tm_year, 18); ASSERT_EQ(local_time->tm_mon, 8); @@ -69,7 +69,7 @@ TEST(osTimeTests, taosLocalTime) { ASSERT_EQ(local_time->tm_sec, 0); time_t neg_timep2 = -315619200; // 1960-01-01 08:00:00 - local_time = taosLocalTime(&neg_timep2, &result, NULL, 0); + local_time = taosLocalTime(&neg_timep2, &result, NULL, 0, NULL); ASSERT_NE(local_time, nullptr); ASSERT_EQ(local_time->tm_year, 60); ASSERT_EQ(local_time->tm_mon, 0); @@ -79,7 +79,7 @@ TEST(osTimeTests, taosLocalTime) { ASSERT_EQ(local_time->tm_sec, 0); time_t zero_timep = 0; // 1970-01-01 08:00:00 - local_time = taosLocalTime(&zero_timep, &result, NULL, 0); + local_time = taosLocalTime(&zero_timep, &result, NULL, 0, NULL); ASSERT_NE(local_time, nullptr); ASSERT_EQ(local_time->tm_year, 70); ASSERT_EQ(local_time->tm_mon, 0); @@ -89,7 +89,7 @@ TEST(osTimeTests, taosLocalTime) { ASSERT_EQ(local_time->tm_sec, 0); time_t neg_timep3 = -78115158887; - local_time = taosLocalTime(&neg_timep3, &result, NULL, 0); + local_time = taosLocalTime(&neg_timep3, &result, NULL, 0, NULL); ASSERT_EQ(local_time, nullptr); #endif } \ No newline at end of file From 931a40985c730403ad8b77ff994f170ac07d4a5e Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Wed, 4 Dec 2024 10:53:33 +0800 Subject: [PATCH 23/45] feat:[TD-32642] add timezone support in windows --- contrib/CMakeLists.txt | 19 +++++++++++++------ include/os/osTime.h | 2 ++ source/common/src/tdatablock.c | 2 +- source/common/src/ttime.c | 4 ++-- source/libs/function/src/builtins.c | 2 +- source/libs/scalar/src/sclfunc.c | 2 +- source/os/CMakeLists.txt | 15 ++++++++++----- source/os/src/osTime.c | 6 ++++++ source/os/src/osTimezone.c | 4 ++-- source/util/src/tlog.c | 2 +- utils/test/c/tmqSim.c | 2 +- utils/tsim/src/simExe.c | 2 +- 12 files changed, 41 insertions(+), 21 deletions(-) diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index af05b2c47c..ef7acb1f34 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -665,16 +665,23 @@ MESSAGE(STATUS "timezone file path: " ${TZ_OUTPUT_PATH}) if(NOT ${TD_WINDOWS}) execute_process( - COMMAND make CFLAGS+=-fPIC TZDIR=${TZ_OUTPUT_PATH}/ clean libtz.a + COMMAND make TZDIR=${TZ_OUTPUT_PATH}/ tzdir.h WORKING_DIRECTORY "${TD_CONTRIB_DIR}/tz" ) -# set(TZ_SRC_DIR "${TD_SOURCE_DIR}/source/os/src/timezone") -# file(MAKE_DIRECTORY ${TZ_SRC_DIR}) -# file(COPY ${TD_CONTRIB_DIR}/tz/private.h ${TD_CONTRIB_DIR}/tz/tzdir.h ${TD_CONTRIB_DIR}/tz/tzfile.h -# ${TD_CONTRIB_DIR}/tz/localtime.c ${TD_CONTRIB_DIR}/tz/strftime.c -# DESTINATION ${TZ_SRC_DIR}) + set(TZ_SRC_DIR "${TD_SOURCE_DIR}/source/os/src/timezone") + file(MAKE_DIRECTORY ${TZ_SRC_DIR}) + file(COPY ${TD_CONTRIB_DIR}/tz/private.h ${TD_CONTRIB_DIR}/tz/tzdir.h ${TD_CONTRIB_DIR}/tz/tzfile.h + ${TD_CONTRIB_DIR}/tz/localtime.c ${TD_CONTRIB_DIR}/tz/strftime.c + DESTINATION ${TZ_SRC_DIR}) endif(NOT ${TD_WINDOWS}) + +#if(NOT ${TD_WINDOWS}) +# execute_process( +# COMMAND make CFLAGS+=-fPIC CFLAGS+=-g TZDIR=${TZ_OUTPUT_PATH} clean libtz.a +# WORKING_DIRECTORY "${TD_CONTRIB_DIR}/tz" +# ) +#endif(NOT ${TD_WINDOWS}) # ================================================================================================ # Build test # ================================================================================================ diff --git a/include/os/osTime.h b/include/os/osTime.h index 723574a91c..69ae8f5e2e 100644 --- a/include/os/osTime.h +++ b/include/os/osTime.h @@ -24,6 +24,7 @@ extern "C" { // When you want to use this feature, you should find or add the same function in the following section. #ifndef ALLOW_FORBID_FUNC #define strptime STRPTIME_FUNC_TAOS_FORBID +#define strftime STRFTIME_FUNC_TAOS_FORBID #define gettimeofday GETTIMEOFDAY_FUNC_TAOS_FORBID #define localtime LOCALTIME_FUNC_TAOS_FORBID #define localtime_s LOCALTIMES_FUNC_TAOS_FORBID @@ -92,6 +93,7 @@ static FORCE_INLINE int64_t taosGetMonoTimestampMs() { } char *taosStrpTime(const char *buf, const char *fmt, struct tm *tm); +size_t taosStrfTime(char *s, size_t maxsize, char const *format, struct tm const *t); struct tm *taosLocalTime(const time_t *timep, struct tm *result, char *buf, int32_t bufSize, timezone_t tz); struct tm *taosGmTimeR(const time_t *timep, struct tm *result); time_t taosTimeGm(struct tm *tmp); diff --git a/source/common/src/tdatablock.c b/source/common/src/tdatablock.c index 3b3c4b72d9..bd18c9ceb9 100644 --- a/source/common/src/tdatablock.c +++ b/source/common/src/tdatablock.c @@ -2499,7 +2499,7 @@ static int32_t formatTimestamp(char* buf, size_t cap, int64_t val, int precision TSDB_CHECK_CODE(code, lino, _end); } - size_t pos = strftime(buf, cap, "%Y-%m-%d %H:%M:%S", &ptm); + size_t pos = taosStrfTime(buf, cap, "%Y-%m-%d %H:%M:%S", &ptm); if (pos == 0) { code = TSDB_CODE_OUT_OF_BUFFER; TSDB_CHECK_CODE(code, lino, _end); diff --git a/source/common/src/ttime.c b/source/common/src/ttime.c index db3f7fec32..dd9a3b1b16 100644 --- a/source/common/src/ttime.c +++ b/source/common/src/ttime.c @@ -923,9 +923,9 @@ int32_t taosFormatUtcTime(char* buf, int32_t bufLen, int64_t t, int32_t precisio if (NULL == taosLocalTime(", &ptm, buf, bufLen, NULL)) { TAOS_RETURN(TAOS_SYSTEM_ERROR(errno)); } - int32_t length = (int32_t)strftime(ts, 40, "%Y-%m-%dT%H:%M:%S", &ptm); + int32_t length = (int32_t)taosStrfTime(ts, 40, "%Y-%m-%dT%H:%M:%S", &ptm); length += tsnprintf(ts + length, fractionLen, format, mod); - length += (int32_t)strftime(ts + length, 40 - length, "%z", &ptm); + length += (int32_t)taosStrfTime(ts + length, 40 - length, "%z", &ptm); tstrncpy(buf, ts, bufLen); TAOS_RETURN(TSDB_CODE_SUCCESS); diff --git a/source/libs/function/src/builtins.c b/source/libs/function/src/builtins.c index 4e54c12e40..f71ba0187f 100644 --- a/source/libs/function/src/builtins.c +++ b/source/libs/function/src/builtins.c @@ -195,7 +195,7 @@ static int32_t addTimezoneParam(SNodeList* pList, timezone_t tz) { } struct tm tmInfo; if (taosLocalTime(&t, &tmInfo, buf, sizeof(buf), tz) != NULL) { - (void)strftime(buf, sizeof(buf), "%z", &tmInfo); + (void)taosStrfTime(buf, sizeof(buf), "%z", &tmInfo); } int32_t len = (int32_t)strlen(buf); diff --git a/source/libs/scalar/src/sclfunc.c b/source/libs/scalar/src/sclfunc.c index efec336589..3fac8d2718 100644 --- a/source/libs/scalar/src/sclfunc.c +++ b/source/libs/scalar/src/sclfunc.c @@ -2249,7 +2249,7 @@ int32_t toISO8601Function(SScalarParam *pInput, int32_t inputNum, SScalarParam * goto _end; } - int32_t len = (int32_t)strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S", &tmInfo); + int32_t len = (int32_t)taosStrfTime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S", &tmInfo); len += tsnprintf(buf + len, fractionLen, format, mod); diff --git a/source/os/CMakeLists.txt b/source/os/CMakeLists.txt index a0f9e5b87a..4602f291f3 100644 --- a/source/os/CMakeLists.txt +++ b/source/os/CMakeLists.txt @@ -1,5 +1,10 @@ aux_source_directory(src OS_SRC) -add_library(os STATIC ${OS_SRC}) +if(NOT ${TD_WINDOWS}) + aux_source_directory(src/timezone OS_TZ) + add_library(os STATIC ${OS_SRC} ${OS_TZ}) +else() + add_library(os STATIC ${OS_SRC}) +endif(NOT ${TD_WINDOWS}) target_include_directories( os PUBLIC "${TD_SOURCE_DIR}/include/os" @@ -70,10 +75,10 @@ IF (JEMALLOC_ENABLED) target_link_libraries(os PUBLIC -L${CMAKE_BINARY_DIR}/build/lib -ljemalloc) ENDIF () -if(NOT ${TD_WINDOWS}) - find_library(tz libtz.a "${TD_SOURCE_DIR}/contrib/tz") - target_link_libraries(os PUBLIC ${tz}) -endif(NOT ${TD_WINDOWS}) +#if(NOT ${TD_WINDOWS}) +# find_library(tz libtz.a "${TD_SOURCE_DIR}/contrib/tz") +# target_link_libraries(os PUBLIC ${tz}) +#endif(NOT ${TD_WINDOWS}) if(${BUILD_TEST}) add_subdirectory(test) diff --git a/source/os/src/osTime.c b/source/os/src/osTime.c index 2720eab56e..b39624c44c 100644 --- a/source/os/src/osTime.c +++ b/source/os/src/osTime.c @@ -345,6 +345,12 @@ char *taosStrpTime(const char *buf, const char *fmt, struct tm *tm) { #endif } +size_t +taosStrfTime(char *s, size_t maxsize, char const *format, struct tm const *t){ + if (!s || !format || !t) return 0; + return strftime(s, maxsize, format, t); +} + int32_t taosGetTimeOfDay(struct timeval *tv) { if (tv == NULL) { return TSDB_CODE_INVALID_PARA; diff --git a/source/os/src/osTimezone.c b/source/os/src/osTimezone.c index d47c0ac663..395c1cbb82 100644 --- a/source/os/src/osTimezone.c +++ b/source/os/src/osTimezone.c @@ -813,13 +813,13 @@ int32_t taosFormatTimezoneStr(time_t t, const char* tz, timezone_t sp, char *out */ char str1[TD_TIMEZONE_LEN] = {0}; - if (strftime(str1, sizeof(str1), "%Z", &tm1) == 0){ + if (taosStrfTime(str1, sizeof(str1), "%Z", &tm1) == 0){ uError("failed to get timezone name"); return TSDB_CODE_TIME_ERROR; } char str2[TD_TIMEZONE_LEN] = {0}; - if (strftime(str2, sizeof(str2), "%z", &tm1) == 0){ + if (taosStrfTime(str2, sizeof(str2), "%z", &tm1) == 0){ uError("failed to get timezone offset"); return TSDB_CODE_TIME_ERROR; } diff --git a/source/util/src/tlog.c b/source/util/src/tlog.c index 31054d40ed..73771aee09 100644 --- a/source/util/src/tlog.c +++ b/source/util/src/tlog.c @@ -163,7 +163,7 @@ static int32_t getDay(char *buf, int32_t bufSize) { } struct tm tmInfo; if (taosLocalTime(&t, &tmInfo, buf, bufSize, NULL) != NULL) { - TAOS_UNUSED(strftime(buf, bufSize, "%Y-%m-%d", &tmInfo)); + TAOS_UNUSED(taosStrfTime(buf, bufSize, "%Y-%m-%d", &tmInfo)); } return 0; } diff --git a/utils/test/c/tmqSim.c b/utils/test/c/tmqSim.c index bb0d813ace..1ed9af00f2 100644 --- a/utils/test/c/tmqSim.c +++ b/utils/test/c/tmqSim.c @@ -475,7 +475,7 @@ static char* shellFormatTimestamp(char* buf, int32_t bufSize, int64_t val, int32 if (taosLocalTime(&tt, &ptm, buf, bufSize, NULL) == NULL) { return buf; } - size_t pos = strftime(buf, 35, "%Y-%m-%d %H:%M:%S", &ptm); + size_t pos = taosStrfTime(buf, 35, "%Y-%m-%d %H:%M:%S", &ptm); if (precision == TSDB_TIME_PRECISION_NANO) { sprintf(buf + pos, ".%09d", ms); diff --git a/utils/tsim/src/simExe.c b/utils/tsim/src/simExe.c index 2d269bdea2..a9772addbb 100644 --- a/utils/tsim/src/simExe.c +++ b/utils/tsim/src/simExe.c @@ -800,7 +800,7 @@ bool simExecuteNativeSqlCommand(SScript *script, char *rest, bool isSlow) { if (taosLocalTime(&tt, &tp, timeStr, sizeof(timeStr), NULL) == NULL) { break; } - strftime(timeStr, 64, "%y-%m-%d %H:%M:%S", &tp); + taosStrfTime(timeStr, 64, "%y-%m-%d %H:%M:%S", &tp); if (precision == TSDB_TIME_PRECISION_MILLI) { sprintf(value, "%s.%03d", timeStr, (int32_t)(*((int64_t *)row[i]) % 1000)); } else if (precision == TSDB_TIME_PRECISION_MICRO) { From f338357477125d5f5f6b9a54f04f1c506f3a8ac8 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Wed, 4 Dec 2024 18:50:52 +0800 Subject: [PATCH 24/45] feat:[TD-32642] add test case for charset --- include/os/osSocket.h | 7 +- source/client/src/clientImpl.c | 3 +- source/client/src/clientMain.c | 2 +- source/client/test/CMakeLists.txt | 10 +- ...imezoneTest.cpp => connectOptionsTest.cpp} | 95 ++++++++++++++++++- source/dnode/mnode/impl/src/mndProfile.c | 12 +-- source/libs/parser/src/parInsertSql.c | 2 +- source/libs/parser/src/parUtil.c | 3 +- source/libs/qcom/src/queryUtil.c | 3 +- source/libs/scalar/src/sclvector.c | 5 +- source/libs/sync/src/syncUtil.c | 2 +- .../libs/sync/test/sync_test_lib/src/syncIO.c | 2 +- source/libs/transport/src/thttp.c | 2 +- source/libs/transport/src/transCli.c | 4 +- source/os/src/osSocket.c | 11 ++- source/os/src/osString.c | 2 + tests/system-test/2-query/timezone.py | 12 ++- tests/system-test/2-query/timezone_conf.py | 18 +++- 18 files changed, 158 insertions(+), 37 deletions(-) rename source/client/test/{timezoneTest.cpp => connectOptionsTest.cpp} (89%) diff --git a/include/os/osSocket.h b/include/os/osSocket.h index ef5b700784..5d95ff308c 100644 --- a/include/os/osSocket.h +++ b/include/os/osSocket.h @@ -27,6 +27,7 @@ #define epoll_ctl EPOLL_CTL_FUNC_TAOS_FORBID #define epoll_wait EPOLL_WAIT_FUNC_TAOS_FORBID #define inet_ntoa INET_NTOA_FUNC_TAOS_FORBID +#define inet_addr INET_ADDR_FUNC_TAOS_FORBID #endif #if defined(WINDOWS) @@ -54,7 +55,6 @@ #define __BIG_ENDIAN BIG_ENDIAN #define __LITTLE_ENDIAN LITTLE_ENDIAN #define __PDP_ENDIAN PDP_ENDIAN -#include #else #include #include @@ -162,9 +162,10 @@ int32_t taosGetSocketName(TdSocketPtr pSocket, struct sockaddr *destAddr, int *a int32_t taosBlockSIGPIPE(); int32_t taosGetIpv4FromFqdn(const char *fqdn, uint32_t *ip); int32_t taosGetFqdn(char *); -void tinet_ntoa(char *ipstr, uint32_t ip); +void taosInetNtoa(char *ipstr, uint32_t ip); +uint32_t taosInetAddr(const char *ipstr); int32_t taosIgnSIGPIPE(); -const char *taosInetNtoa(struct in_addr ipInt, char *dstStr, int32_t len); +const char *taosInetNtop(struct in_addr ipInt, char *dstStr, int32_t len); uint64_t taosHton64(uint64_t val); uint64_t taosNtoh64(uint64_t val); diff --git a/source/client/src/clientImpl.c b/source/client/src/clientImpl.c index 26bef9301e..0bc82d691a 100644 --- a/source/client/src/clientImpl.c +++ b/source/client/src/clientImpl.c @@ -2306,7 +2306,8 @@ static int32_t doConvertJson(SReqResultInfo* pResultInfo) { int32_t length = taosUcs4ToMbs((TdUcs4*)varDataVal(jsonInnerData), varDataLen(jsonInnerData), varDataVal(dst) + CHAR_BYTES, pResultInfo->charsetCxt); if (length <= 0) { - tscError("charset:%s to %s. convert failed.", DEFAULT_UNICODE_ENCODEC, tsCharset); + tscError("charset:%s to %s. convert failed.", DEFAULT_UNICODE_ENCODEC, + pResultInfo->charsetCxt != NULL ? ((SConvInfo *)(pResultInfo->charsetCxt))->charset : tsCharset); length = 0; } varDataSetLen(dst, length + CHAR_BYTES * 2); diff --git a/source/client/src/clientMain.c b/source/client/src/clientMain.c index 13cf39f924..ca9bd4b2ee 100644 --- a/source/client/src/clientMain.c +++ b/source/client/src/clientMain.c @@ -203,7 +203,7 @@ static int32_t setConnectionOption(TAOS *taos, TSDB_OPTION_CONNECTION option, co if (option == TSDB_OPTION_CONNECTION_USER_IP || option == TSDB_OPTION_CONNECTION_CLEAR) { if (val != NULL) { - pObj->optionInfo.userIp = inet_addr(val); + pObj->optionInfo.userIp = taosInetAddr(val); if (pObj->optionInfo.userIp == INADDR_NONE){ code = TSDB_CODE_INVALID_PARA; goto END; diff --git a/source/client/test/CMakeLists.txt b/source/client/test/CMakeLists.txt index f869bedef6..210cacc301 100644 --- a/source/client/test/CMakeLists.txt +++ b/source/client/test/CMakeLists.txt @@ -11,9 +11,9 @@ TARGET_LINK_LIBRARIES( os util common transport parser catalog scheduler gtest ${TAOS_LIB_STATIC} qcom executor function ) -ADD_EXECUTABLE(timezoneTest timezoneTest.cpp) +ADD_EXECUTABLE(connectOptionsTest connectOptionsTest.cpp) TARGET_LINK_LIBRARIES( - timezoneTest + connectOptionsTest os util common transport parser catalog scheduler gtest ${TAOS_LIB_STATIC} qcom executor function ) @@ -48,7 +48,7 @@ TARGET_INCLUDE_DIRECTORIES( ) TARGET_INCLUDE_DIRECTORIES( - timezoneTest + connectOptionsTest PUBLIC "${TD_SOURCE_DIR}/include/client/" PRIVATE "${TD_SOURCE_DIR}/source/client/inc" ) @@ -94,6 +94,6 @@ add_test( ) add_test( - NAME timezoneTest - COMMAND timezoneTest + NAME connectOptionsTest + COMMAND connectOptionsTest ) diff --git a/source/client/test/timezoneTest.cpp b/source/client/test/connectOptionsTest.cpp similarity index 89% rename from source/client/test/timezoneTest.cpp rename to source/client/test/connectOptionsTest.cpp index 382b28e275..3e3b8d33df 100644 --- a/source/client/test/timezoneTest.cpp +++ b/source/client/test/connectOptionsTest.cpp @@ -116,7 +116,7 @@ void check_sql_result(TAOS* pConn, const char *sql, const char* result){ ASSERT(taos_errno(pRes) == 0); TAOS_ROW row = NULL; while ((row = taos_fetch_row(pRes)) != NULL) { - ASSERT (strcmp((const char*)row[0], result) == 0); + ASSERT (memcmp((const char*)row[0], result, strlen(result)) == 0); } taos_free_result(pRes); } @@ -195,11 +195,11 @@ void check_set_timezone(TAOS* optionFunc(const char *tz)){ STscObj* pObj = acquireTscObj(*(int64_t*)taos); \ ASSERT(pObj != nullptr); \ char ip[TD_IP_LEN] = {0}; \ - tinet_ntoa(ip, pObj->optionInfo.option); \ + taosInetNtoa(ip, pObj->optionInfo.option); \ ASSERT(strcmp(ip, val) == 0); \ } -TEST(timezoneCase, setConnectionOption_Test) { +TEST(connectionCase, setConnectionOption_Test) { int32_t code = taos_options_connection(NULL, TSDB_OPTION_CONNECTION_CHARSET, NULL); ASSERT(code != 0); TAOS* pConn = taos_connect("localhost", "root", "taosdata", NULL, 0); @@ -223,6 +223,7 @@ TEST(timezoneCase, setConnectionOption_Test) { ASSERT(code == 0); CHECK_TAOS_OPTION_POINTER(pConn, charsetCxt, false); +#ifndef WINDOWS // test timezone code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_TIMEZONE, ""); ASSERT(code == 0); @@ -248,6 +249,7 @@ TEST(timezoneCase, setConnectionOption_Test) { ASSERT(code == 0); CHECK_TAOS_OPTION_POINTER(pConn, timezone, false); check_sql_result(pConn, "select timezone()", "adbc (UTC, +0000)"); +#endif // test user APP code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_USER_APP, ""); @@ -309,14 +311,99 @@ TEST(timezoneCase, setConnectionOption_Test) { code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_CLEAR, "192.168.0.2"); ASSERT(code == 0); CHECK_TAOS_OPTION_POINTER(pConn, charsetCxt, true); + +#ifndef WINDOWS CHECK_TAOS_OPTION_POINTER(pConn, timezone, true); check_sql_result(pConn, "select timezone()", "Asia/Shanghai (CST, +0800)"); +#endif + CHECK_TAOS_OPTION_APP(pConn, userApp, ""); CHECK_TAOS_OPTION_IP_ERROR(pConn, userIp, INADDR_NONE); taos_close(pConn); } +TEST(charsetCase, charset_Test) { + TAOS* pConn = taos_connect("localhost", "root", "taosdata", NULL, 0); + ASSERT(pConn != nullptr); + + TAOS* pConnUTF8 = taos_connect("localhost", "root", "taosdata", NULL, 0); + ASSERT(pConnUTF8 != nullptr); + + TAOS* pConnDefault = taos_connect("localhost", "root", "taosdata", NULL, 0); + ASSERT(pConnDefault != nullptr); + + int32_t code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_CHARSET, "gbk"); + ASSERT(code == 0); + CHECK_TAOS_OPTION_POINTER(pConn, charsetCxt, false); + + code = taos_options_connection(pConnUTF8, TSDB_OPTION_CONNECTION_CHARSET, "UTF-8"); + ASSERT(code == 0); + CHECK_TAOS_OPTION_POINTER(pConnUTF8, charsetCxt, false); + + execQuery(pConn, "drop database if exists db1"); + execQuery(pConn, "create database db1"); + execQuery(pConn, "create table db1.stb (ts timestamp, c1 nchar(32), c2 int) tags(t1 timestamp, t2 nchar(32), t3 int)"); + execQueryFail(pConn, "create table db1.ctb1 using db1.stb tags('2023-09-16 17:00:00+05:00', '芬', 1)"); + + // 芬 gbk encode is 0xB7D2, the file charset is UTF-8 + char sqlTag[256] = {0}; + snprintf(sqlTag, sizeof(sqlTag), "create table db1.ctb1 using db1.stb tags('2023-09-16 17:00:00+05:00', '%c%c', 1)", 0xB7, 0xD2); + execQuery(pConn, sqlTag); + + // 中国 gbk encode is 0xD6D0B9FA + execQueryFail(pConn, "insert into db1.ctb1 values(1732178775133, '中国', 1)"); + char sqlCol[256] = {0}; + snprintf(sqlCol, sizeof(sqlCol), "insert into db1.ctb1 values(1732178775133, '%c%c%c%c', 1)", 0xD6, 0xD0, 0xB9, 0xFA); + execQuery(pConn, sqlCol); + + char resTag[8] = {0}; + snprintf(resTag, sizeof(resTag), "%c%c", 0xB7, 0xD2); + check_sql_result(pConn, "select t2 from db1.ctb1", resTag); + + char resCol[8] = {0}; + snprintf(resCol, sizeof(resCol), "%c%c%c%c", 0xD6, 0xD0, 0xB9, 0xFA); + check_sql_result(pConn, "select c1 from db1.ctb1", resCol); + + + check_sql_result(pConnUTF8, "select t2 from db1.ctb1", "芬"); + check_sql_result(pConnUTF8, "select c1 from db1.ctb1", "中国"); + + + execQuery(pConnDefault, "insert into db1.ctb1 values(1732178775134, '中国', 1)"); + check_sql_result(pConnDefault, "select c1 from db1.ctb1 where ts = 1732178775134", "中国"); + + execQuery(pConnUTF8, "create table db1.jsta (ts timestamp, c1 nchar(32), c2 int) tags(t1 json)"); + execQuery(pConnUTF8, "create table db1.jsta1 using db1.jsta tags('{\"k\":\"芬\"}')"); + execQuery(pConnUTF8, "insert into db1.jsta1 values(1732178775133, '中国', 1)"); + char resJsonTag[32] = {0}; + snprintf(resJsonTag, sizeof(resJsonTag), "{\"k\":\"%c%c\"}", 0xB7, 0xD2); + check_sql_result(pConn, "select t1 from db1.jsta1", resJsonTag); + + //reset charset to default(utf-8 + code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_CHARSET, NULL); + ASSERT(code == 0); + CHECK_TAOS_OPTION_POINTER(pConn, charsetCxt, true); + check_sql_result(pConn, "select t2 from db1.ctb1 where ts = 1732178775134", "芬"); + check_sql_result(pConn, "select c1 from db1.ctb1 where ts = 1732178775134", "中国"); + + + + taos_close(pConn); + taos_close(pConnUTF8); + taos_close(pConnDefault); + +} + +TEST(charsetCase, alter_charset_Test) { + TAOS* pConn = taos_connect("localhost", "root", "taosdata", NULL, 0); + ASSERT(pConn != nullptr); + + execQueryFail(pConn, "alter dnode 1 'charset gbk'"); + execQueryFail(pConn, "local 'charset gbk'"); +} + +#ifndef WINDOWS TEST(timezoneCase, set_timezone_Test) { check_set_timezone(getConnWithGlobalOption); check_set_timezone(getConnWithOption); @@ -767,6 +854,6 @@ TEST(timezoneCase, localtime_performance_Test) { printf("average: localtime cost:%" PRId64 " ns, localtime_rz cost:%" PRId64 " ns", time_localtime/times, time_localtime_rz/times); tzfree(sp); } - +#endif #pragma GCC diagnostic pop diff --git a/source/dnode/mnode/impl/src/mndProfile.c b/source/dnode/mnode/impl/src/mndProfile.c index 338f71856a..9eefaf6a20 100644 --- a/source/dnode/mnode/impl/src/mndProfile.c +++ b/source/dnode/mnode/impl/src/mndProfile.c @@ -253,7 +253,7 @@ static int32_t mndProcessConnectReq(SRpcMsg *pReq) { goto _OVER; } - tinet_ntoa(ip, pReq->info.conn.clientIp); + taosInetNtoa(ip, pReq->info.conn.clientIp); if ((code = mndCheckOperPrivilege(pMnode, pReq->info.conn.user, MND_OPER_CONNECT)) != 0) { mGError("user:%s, failed to login from %s since %s", pReq->info.conn.user, ip, tstrerror(code)); goto _OVER; @@ -907,7 +907,7 @@ static int32_t mndRetrieveConns(SRpcMsg *pReq, SShowObj *pShow, SSDataBlock *pBl } char endpoint[TD_IP_LEN + 6 + VARSTR_HEADER_SIZE] = {0}; - tinet_ntoa(varDataVal(endpoint), pConn->ip); + taosInetNtoa(varDataVal(endpoint), pConn->ip); (void)sprintf(varDataVal(endpoint) + strlen(varDataVal(endpoint)), ":%d", pConn->port); varDataLen(endpoint) = strlen(varDataVal(endpoint)); pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); @@ -942,7 +942,7 @@ static int32_t mndRetrieveConns(SRpcMsg *pReq, SShowObj *pShow, SSDataBlock *pBl char userIp[TD_IP_LEN + 6 + VARSTR_HEADER_SIZE] = {0}; if (pConn->userIp != 0 && pConn->userIp != INADDR_NONE){ - tinet_ntoa(varDataVal(userIp), pConn->userIp); + taosInetNtoa(varDataVal(userIp), pConn->userIp); varDataLen(userIp) = strlen(varDataVal(userIp)); } pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); @@ -1039,7 +1039,7 @@ static int32_t packQueriesIntoBlock(SShowObj *pShow, SConnObj *pConn, SSDataBloc } char endpoint[TD_IP_LEN + 6 + VARSTR_HEADER_SIZE] = {0}; - tinet_ntoa(varDataVal(endpoint), pConn->ip); + taosInetNtoa(varDataVal(endpoint), pConn->ip); (void)sprintf(varDataVal(endpoint) + strlen(varDataVal(endpoint)), ":%d", pConn->port); varDataLen(endpoint) = strlen(&endpoint[VARSTR_HEADER_SIZE]); pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); @@ -1136,7 +1136,7 @@ static int32_t packQueriesIntoBlock(SShowObj *pShow, SConnObj *pConn, SSDataBloc char userIp[TD_IP_LEN + 6 + VARSTR_HEADER_SIZE] = {0}; if (pConn->userIp != 0 && pConn->userIp != INADDR_NONE){ - tinet_ntoa(varDataVal(userIp), pConn->userIp); + taosInetNtoa(varDataVal(userIp), pConn->userIp); varDataLen(userIp) = strlen(varDataVal(userIp)); } pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); @@ -1222,7 +1222,7 @@ static int32_t mndRetrieveApps(SRpcMsg *pReq, SShowObj *pShow, SSDataBlock *pBlo } char ip[TD_IP_LEN + VARSTR_HEADER_SIZE] = {0}; - tinet_ntoa(varDataVal(ip), pApp->ip); + taosInetNtoa(varDataVal(ip), pApp->ip); varDataLen(ip) = strlen(varDataVal(ip)); pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); code = colDataSetVal(pColInfo, numOfRows, (const char *)ip, false); diff --git a/source/libs/parser/src/parInsertSql.c b/source/libs/parser/src/parInsertSql.c index a7d1351aa0..bb28cb3135 100644 --- a/source/libs/parser/src/parInsertSql.c +++ b/source/libs/parser/src/parInsertSql.c @@ -691,7 +691,7 @@ static int32_t parseTagToken(const char** end, SToken* pToken, SSchema* pSchema, return generateSyntaxErrMsg(pMsgBuf, TSDB_CODE_PAR_VALUE_TOO_LONG, pSchema->name); } char buf[512] = {0}; - snprintf(buf, tListLen(buf), " taosMbsToUcs4 error:%s", strerror(terrno)); + snprintf(buf, tListLen(buf), " taosMbsToUcs4 error:%s %d %d", strerror(terrno), errno, EILSEQ); taosMemoryFree(p); return buildSyntaxErrMsg(pMsgBuf, buf, pToken->z); } diff --git a/source/libs/parser/src/parUtil.c b/source/libs/parser/src/parUtil.c index 07ea54c44f..6ebf767eed 100644 --- a/source/libs/parser/src/parUtil.c +++ b/source/libs/parser/src/parUtil.c @@ -472,7 +472,8 @@ int32_t parseJsontoTagData(const char* json, SArray* pTagVals, STag** ppTag, voi } val.type = TSDB_DATA_TYPE_NCHAR; if (valLen > 0 && !taosMbsToUcs4(jsonValue, valLen, (TdUcs4*)tmp, (int32_t)(valLen * TSDB_NCHAR_SIZE), &valLen, charsetCxt)) { - uError("charset:%s to %s. val:%s, errno:%s, convert failed.", DEFAULT_UNICODE_ENCODEC, tsCharset, jsonValue, + uError("charset:%s to %s. val:%s, errno:%s, convert failed.", DEFAULT_UNICODE_ENCODEC, + charsetCxt != NULL ? ((SConvInfo *)(charsetCxt))->charset : tsCharset, jsonValue, strerror(terrno)); retCode = buildSyntaxErrMsg(pMsgBuf, "charset convert json error", jsonValue); taosMemoryFree(tmp); diff --git a/source/libs/qcom/src/queryUtil.c b/source/libs/qcom/src/queryUtil.c index 9b59e321a3..22141c33ee 100644 --- a/source/libs/qcom/src/queryUtil.c +++ b/source/libs/qcom/src/queryUtil.c @@ -477,7 +477,8 @@ void parseTagDatatoJson(void* p, char** jsonStr, void *charsetCxt) { } int32_t length = taosUcs4ToMbs((TdUcs4*)pTagVal->pData, pTagVal->nData, tagJsonValue, charsetCxt); if (length < 0) { - qError("charset:%s to %s. val:%s convert json value failed.", DEFAULT_UNICODE_ENCODEC, tsCharset, + qError("charset:%s to %s. val:%s convert json value failed.", DEFAULT_UNICODE_ENCODEC, + charsetCxt != NULL ? ((SConvInfo *)(charsetCxt))->charset : tsCharset, pTagVal->pData); taosMemoryFree(tagJsonValue); goto end; diff --git a/source/libs/scalar/src/sclvector.c b/source/libs/scalar/src/sclvector.c index c46419213f..1f351b77c7 100644 --- a/source/libs/scalar/src/sclvector.c +++ b/source/libs/scalar/src/sclvector.c @@ -598,12 +598,13 @@ int32_t ncharTobinary(void *buf, void **out, void* charsetCxt) { // todo need t *out = taosMemoryCalloc(1, inputLen); if (NULL == *out) { sclError("charset:%s to %s. val:%s convert ncharTobinary failed, since memory alloc failed.", - DEFAULT_UNICODE_ENCODEC, tsCharset, (char *)varDataVal(buf)); + DEFAULT_UNICODE_ENCODEC, charsetCxt != NULL ? ((SConvInfo *)(charsetCxt))->charset : tsCharset, (char *)varDataVal(buf)); SCL_ERR_RET(terrno); } int32_t len = taosUcs4ToMbs((TdUcs4 *)varDataVal(buf), varDataLen(buf), varDataVal(*out), charsetCxt); if (len < 0) { - sclError("charset:%s to %s. val:%s convert ncharTobinary failed.", DEFAULT_UNICODE_ENCODEC, tsCharset, + sclError("charset:%s to %s. val:%s convert ncharTobinary failed.", DEFAULT_UNICODE_ENCODEC, + charsetCxt != NULL ? ((SConvInfo *)(charsetCxt))->charset : tsCharset, (char *)varDataVal(buf)); taosMemoryFree(*out); SCL_ERR_RET(TSDB_CODE_SCALAR_CONVERT_ERROR); diff --git a/source/libs/sync/src/syncUtil.c b/source/libs/sync/src/syncUtil.c index 65c7f9761e..0fedd96e4c 100644 --- a/source/libs/sync/src/syncUtil.c +++ b/source/libs/sync/src/syncUtil.c @@ -62,7 +62,7 @@ bool syncUtilNodeInfo2RaftId(const SNodeInfo* pInfo, SyncGroupId vgId, SRaftId* } char ipbuf[TD_IP_LEN] = {0}; - tinet_ntoa(ipbuf, ipv4); + taosInetNtoa(ipbuf, ipv4); raftId->addr = SYNC_ADDR(pInfo); raftId->vgId = vgId; diff --git a/source/libs/sync/test/sync_test_lib/src/syncIO.c b/source/libs/sync/test/sync_test_lib/src/syncIO.c index f5a32b98d9..088a82a009 100644 --- a/source/libs/sync/test/sync_test_lib/src/syncIO.c +++ b/source/libs/sync/test/sync_test_lib/src/syncIO.c @@ -520,7 +520,7 @@ void syncUtilU642Addr(uint64_t u64, char *host, int64_t len, uint16_t *port) { uint32_t hostU32 = (uint32_t)((u64 >> 32) & 0x00000000FFFFFFFF); struct in_addr addr = {.s_addr = hostU32}; - taosInetNtoa(addr, host, len); + taosInetNtop(addr, host, len); *port = (uint16_t)((u64 & 0x00000000FFFF0000) >> 16); } diff --git a/source/libs/transport/src/thttp.c b/source/libs/transport/src/thttp.c index bdcbfeb1cd..0c0f723b8b 100644 --- a/source/libs/transport/src/thttp.c +++ b/source/libs/transport/src/thttp.c @@ -224,7 +224,7 @@ static FORCE_INLINE int32_t taosBuildDstAddr(const char* server, uint16_t port, return TSDB_CODE_RPC_FQDN_ERROR; } char buf[TD_IP_LEN] = {0}; - tinet_ntoa(buf, ip); + taosInetNtoa(buf, ip); int ret = uv_ip4_addr(buf, port, dest); if (ret != 0) { tError("http-report failed to get addr, reason:%s", uv_err_name(ret)); diff --git a/source/libs/transport/src/transCli.c b/source/libs/transport/src/transCli.c index 8377a1456d..dffda3c67c 100644 --- a/source/libs/transport/src/transCli.c +++ b/source/libs/transport/src/transCli.c @@ -1856,8 +1856,8 @@ static FORCE_INLINE int32_t cliUpdateFqdnCache(SHashObj* cache, char* fqdn) { if (v != NULL) { if (addr != *v) { char old[TSDB_FQDN_LEN] = {0}, new[TSDB_FQDN_LEN] = {0}; - tinet_ntoa(old, *v); - tinet_ntoa(new, addr); + taosInetNtoa(old, *v); + taosInetNtoa(new, addr); tWarn("update ip of fqdn:%s, old: %s, new: %s", fqdn, old, new); code = taosHashPut(cache, fqdn, len, &addr, sizeof(addr)); } diff --git a/source/os/src/osSocket.c b/source/os/src/osSocket.c index 0a24daef95..28072f334a 100644 --- a/source/os/src/osSocket.c +++ b/source/os/src/osSocket.c @@ -150,7 +150,7 @@ int32_t taosSetSockOpt(TdSocketPtr pSocket, int32_t level, int32_t optname, void #endif } -const char *taosInetNtoa(struct in_addr ipInt, char *dstStr, int32_t len) { +const char *taosInetNtop(struct in_addr ipInt, char *dstStr, int32_t len) { const char *r = inet_ntop(AF_INET, &ipInt, dstStr, len); if (NULL == r) { terrno = TAOS_SYSTEM_ERROR(errno); @@ -386,7 +386,7 @@ int32_t taosGetFqdn(char *fqdn) { return 0; } -void tinet_ntoa(char *ipstr, uint32_t ip) { +void taosInetNtoa(char *ipstr, uint32_t ip) { if (ipstr == NULL) { return; } @@ -394,6 +394,13 @@ void tinet_ntoa(char *ipstr, uint32_t ip) { (void)snprintf(ipstr, TD_IP_LEN, "%d.%d.%d.%d", bytes[0], bytes[1], bytes[2], bytes[3]); } +uint32_t taosInetAddr(const char *ipstr){ + if (ipstr == NULL) { + return 0; + } + return inet_addr(ipstr); +} + int32_t taosIgnSIGPIPE() { sighandler_t h = signal(SIGPIPE, SIG_IGN); if (SIG_ERR == h) { diff --git a/source/os/src/osString.c b/source/os/src/osString.c index d8109bab0c..1ae8ac2a28 100644 --- a/source/os/src/osString.c +++ b/source/os/src/osString.c @@ -326,6 +326,8 @@ bool taosMbsToUcs4(const char *mbs, size_t mbsLength, TdUcs4 *ucs4, int32_t ucs4 size_t ucs4_input_len = mbsLength; size_t outLeft = ucs4_max_len; if (iconv(conv, (char **)&mbs, &ucs4_input_len, (char **)&ucs4, &outLeft) == -1) { + char buf[512] = {0}; + snprintf(buf, tListLen(buf), " taosMbsToUcs4 error:%s %d %d", strerror(terrno), errno, EILSEQ); terrno = TAOS_SYSTEM_ERROR(errno); taosReleaseConv(idx, conv, M2C, charsetCxt); return false; diff --git a/tests/system-test/2-query/timezone.py b/tests/system-test/2-query/timezone.py index e08e85dcf5..dbb4d2187e 100644 --- a/tests/system-test/2-query/timezone.py +++ b/tests/system-test/2-query/timezone.py @@ -188,10 +188,20 @@ class TDTestCase: for i in range(rows): if tdSql.getData(i, 0) == "timezone" : if tdSql.getData(i, 1).find(timezone) == -1: - tdLog.exit("show timezone:%s != %s"%(tdSql.getData(i, 1),timezone)) + tdLog.exit("show timezone:%s != %s"%(tdSql.getData(i, 1), timezone)) + + def charset_check(self, sql, charset): + tdSql.query(sql) + rows = tdSql.getRows() + for i in range(rows): + if tdSql.getData(i, 0) == "charset" : + if tdSql.getData(i, 1).find(charset) == -1: + tdLog.exit("show charset:%s != %s"%(tdSql.getData(i, 1), charset)) def run(self): # sourcery skip: extract-duplicate-method timezone = self.get_system_timezone() # timezone = "Asia/Shanghai" + self.charset_check("show local variables", "UTF-8") + self.timezone_check("show dnode 1 variables", "UTF-8") self.timezone_check("show local variables", timezone) self.timezone_check("show dnode 1 variables", timezone) diff --git a/tests/system-test/2-query/timezone_conf.py b/tests/system-test/2-query/timezone_conf.py index 2a54114a6b..da45e2c5bd 100644 --- a/tests/system-test/2-query/timezone_conf.py +++ b/tests/system-test/2-query/timezone_conf.py @@ -6,23 +6,33 @@ from util.sqlset import * from util.cluster import * class TDTestCase: - updateCfgDict = {"clientCfg" : {'timezone': 'UTC'} , 'timezone': 'UTC-8'} - + updatecfgDict = {"clientCfg" : {'timezone': 'UTC', 'charset': 'gbk'}, 'timezone': 'UTC-8', 'charset': 'gbk'} def init(self, conn, logSql, replicaVar=1): self.replicaVar = int(replicaVar) tdLog.debug(f"start to excute {__file__}") tdSql.init(conn.cursor()) def timezone_check(self, timezone, sql): - tdSql.execute(sql) + tdSql.query(sql) rows = tdSql.getRows() for i in range(rows): if tdSql.getData(i, 0) == "timezone" : if tdSql.getData(i, 1).find(timezone) == -1: - tdLog.exit("show timezone:%s != %s"%(tdSql.getData(i, 1),timezone)) + tdLog.exit("show timezone:%s != %s"%(tdSql.getData(i, 1), timezone)) + + def charset_check(self, charset, sql): + tdSql.query(sql) + rows = tdSql.getRows() + for i in range(rows): + if tdSql.getData(i, 0) == "charset" : + if tdSql.getData(i, 1).find(charset) == -1: + tdLog.exit("show charset:%s != %s"%(tdSql.getData(i, 1), charset)) def run(self): + self.charset_check('gbk', "show local variables") + self.charset_check('gbk', "show dnode 1 variables") self.timezone_check('UTC', "show local variables") + # time.sleep(50) self.timezone_check('UTC-8', "show dnode 1 variables") def stop(self): From b179c14b6df7dd80521cd504a0dd8aca989e3f1b Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Thu, 5 Dec 2024 15:38:19 +0800 Subject: [PATCH 25/45] feat:[TD-32642] add test case for charset --- source/client/test/connectOptionsTest.cpp | 153 ++++++++++++++++------ source/os/CMakeLists.txt | 1 + 2 files changed, 114 insertions(+), 40 deletions(-) diff --git a/source/client/test/connectOptionsTest.cpp b/source/client/test/connectOptionsTest.cpp index 3e3b8d33df..95129b5aac 100644 --- a/source/client/test/connectOptionsTest.cpp +++ b/source/client/test/connectOptionsTest.cpp @@ -324,8 +324,9 @@ TEST(connectionCase, setConnectionOption_Test) { } TEST(charsetCase, charset_Test) { - TAOS* pConn = taos_connect("localhost", "root", "taosdata", NULL, 0); - ASSERT(pConn != nullptr); + // 1. build connection with different charset + TAOS* pConnGbk = taos_connect("localhost", "root", "taosdata", NULL, 0); + ASSERT(pConnGbk != nullptr); TAOS* pConnUTF8 = taos_connect("localhost", "root", "taosdata", NULL, 0); ASSERT(pConnUTF8 != nullptr); @@ -333,63 +334,135 @@ TEST(charsetCase, charset_Test) { TAOS* pConnDefault = taos_connect("localhost", "root", "taosdata", NULL, 0); ASSERT(pConnDefault != nullptr); - int32_t code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_CHARSET, "gbk"); + int32_t code = taos_options_connection(pConnGbk, TSDB_OPTION_CONNECTION_CHARSET, "gbk"); ASSERT(code == 0); - CHECK_TAOS_OPTION_POINTER(pConn, charsetCxt, false); + CHECK_TAOS_OPTION_POINTER(pConnGbk, charsetCxt, false); code = taos_options_connection(pConnUTF8, TSDB_OPTION_CONNECTION_CHARSET, "UTF-8"); ASSERT(code == 0); CHECK_TAOS_OPTION_POINTER(pConnUTF8, charsetCxt, false); - execQuery(pConn, "drop database if exists db1"); - execQuery(pConn, "create database db1"); - execQuery(pConn, "create table db1.stb (ts timestamp, c1 nchar(32), c2 int) tags(t1 timestamp, t2 nchar(32), t3 int)"); - execQueryFail(pConn, "create table db1.ctb1 using db1.stb tags('2023-09-16 17:00:00+05:00', '芬', 1)"); - - // 芬 gbk encode is 0xB7D2, the file charset is UTF-8 + // 2. build test string char sqlTag[256] = {0}; - snprintf(sqlTag, sizeof(sqlTag), "create table db1.ctb1 using db1.stb tags('2023-09-16 17:00:00+05:00', '%c%c', 1)", 0xB7, 0xD2); - execQuery(pConn, sqlTag); - - // 中国 gbk encode is 0xD6D0B9FA - execQueryFail(pConn, "insert into db1.ctb1 values(1732178775133, '中国', 1)"); char sqlCol[256] = {0}; - snprintf(sqlCol, sizeof(sqlCol), "insert into db1.ctb1 values(1732178775133, '%c%c%c%c', 1)", 0xD6, 0xD0, 0xB9, 0xFA); - execQuery(pConn, sqlCol); - char resTag[8] = {0}; - snprintf(resTag, sizeof(resTag), "%c%c", 0xB7, 0xD2); - check_sql_result(pConn, "select t2 from db1.ctb1", resTag); + // 芬 gbk encode is 0xB7D2, UTF-8 encode is 0xE88AAC + // 中国 gbk encode is 0xD6D0B9FA, UTF-8 encode is 0xE4B8ADE59BBD + char fenUtf8[32] = {0}; + char fenGbk[32] = {0}; + char zhongGbk[32] = {0}; + char zhongguoUtf8[32] = {0}; + char guoUtf8[32] = {0}; + char zhongguoGbk[32] = {0}; + snprintf(fenUtf8, sizeof(fenUtf8), "%c%c%c", 0xE8, 0x8A, 0xAC); + snprintf(fenGbk, sizeof(fenGbk), "%c%c", 0xB7, 0xD2); + snprintf(zhongguoUtf8, sizeof(zhongguoUtf8), "%c%c%c%c%c%c", 0xE4, 0xB8, 0xAD, 0xE5, 0x9B, 0xBD); + snprintf(guoUtf8, sizeof(guoUtf8), "%c%c%c", 0xE5, 0x9B, 0xBD); + snprintf(zhongguoGbk, sizeof(zhongguoGbk), "%c%c%c%c", 0xD6, 0xD0, 0xB9, 0xFA); + snprintf(zhongGbk, sizeof(zhongGbk), "%c%c", 0xD6, 0xD0); - char resCol[8] = {0}; - snprintf(resCol, sizeof(resCol), "%c%c%c%c", 0xD6, 0xD0, 0xB9, 0xFA); - check_sql_result(pConn, "select c1 from db1.ctb1", resCol); + // 3. create stable + execQuery(pConnGbk, "drop database if exists db1"); + execQuery(pConnGbk, "create database db1"); + execQuery(pConnGbk, "create table db1.stb (ts timestamp, c1 nchar(32), c2 int) tags(t1 timestamp, t2 nchar(32), t3 int)"); + // 4. test tag with different charset + snprintf(sqlTag, sizeof(sqlTag), "create table db1.ctb1 using db1.stb tags('2023-09-16 17:00:00+05:00', '%s', 1)", fenUtf8); + execQueryFail(pConnGbk, sqlTag); - check_sql_result(pConnUTF8, "select t2 from db1.ctb1", "芬"); - check_sql_result(pConnUTF8, "select c1 from db1.ctb1", "中国"); + snprintf(sqlTag, sizeof(sqlTag), "create table db1.ctb1 using db1.stb tags('2023-09-16 17:00:00+05:00', '%s', 1)", fenGbk); + execQuery(pConnGbk, sqlTag); + // 5. test column with different charset + snprintf(sqlCol, sizeof(sqlCol), "insert into db1.ctb1 values(1732178775133, '%s', 1)", zhongguoUtf8); + execQueryFail(pConnGbk, sqlCol); - execQuery(pConnDefault, "insert into db1.ctb1 values(1732178775134, '中国', 1)"); - check_sql_result(pConnDefault, "select c1 from db1.ctb1 where ts = 1732178775134", "中国"); + snprintf(sqlCol, sizeof(sqlCol), "insert into db1.ctb1 values(1732178775133, '%s', 1)", zhongguoGbk); + execQuery(pConnGbk, sqlCol); + // 6. check result with different charset + check_sql_result(pConnGbk, "select t2 from db1.ctb1", fenGbk); + check_sql_result(pConnUTF8, "select t2 from db1.ctb1", fenUtf8); + + check_sql_result(pConnGbk, "select c1 from db1.ctb1", zhongguoGbk); + check_sql_result(pConnUTF8, "select c1 from db1.ctb1", zhongguoUtf8); + + // 7. test function with different charset + // 7.1 concat + char zhongguofenGbk[32] = {0}; + snprintf(zhongguofenGbk, sizeof(zhongguofenGbk), "%s%s", zhongguoGbk, fenGbk); + char sql[256] = {0}; + snprintf(sql, sizeof(sql), "select concat(c1, '%s') from db1.ctb1", fenGbk); + execQueryFail(pConnGbk, sql); + snprintf(sql, sizeof(sql), "select concat(c1, '%s') from db1.ctb1", fenUtf8); + check_sql_result(pConnGbk, sql, zhongguofenGbk); + + // 7.2 trim + snprintf(sql, sizeof(sql), "select trim(LEADING c1 from '%s') from db1.ctb1", zhongguofenGbk); + check_sql_result(pConnGbk, sql, zhongguofenGbk); + char zhongguofenUtf8[32] = {0}; + snprintf(zhongguofenUtf8, sizeof(zhongguofenUtf8), "%s%s", zhongguoUtf8, fenUtf8); + snprintf(sql, sizeof(sql), "select trim(LEADING c1 from '%s') from db1.ctb1", zhongguofenUtf8); + check_sql_result(pConnGbk, sql, fenUtf8); + + check_sql_result(pConnGbk, "select char(c1) from db1.ctb1", ""); + + check_sql_result_integer(pConnGbk, "select ascii(c1) from db1.ctb1", 0xE4); + check_sql_result_integer(pConnUTF8, "select ascii(c1) from db1.ctb1", 0xE4); + check_sql_result_integer(pConnGbk, "select LENGTH(c1) from db1.ctb1", 8); + check_sql_result_integer(pConnUTF8, "select LENGTH(c1) from db1.ctb1", 8); + check_sql_result_integer(pConnGbk, "select CHAR_LENGTH(c1) from db1.ctb1", 2); + check_sql_result_integer(pConnUTF8, "select CHAR_LENGTH(c1) from db1.ctb1", 2); + + execQuery(pConnGbk, "select LOWER(c1) from db1.ctb1"); + execQuery(pConnGbk, "select UPPER(c1) from db1.ctb1"); + + snprintf(sql, sizeof(sql), "select position(c1 in '%s') from db1.ctb1", zhongguofenGbk); + check_sql_result_integer(pConnGbk, sql, 0); + + snprintf(sql, sizeof(sql), "select position('%s' in c1) from db1.ctb1", guoUtf8); + check_sql_result_integer(pConnUTF8, sql, 2); + + snprintf(sql, sizeof(sql), "select replace(c1, '%s', 'a') from db1.ctb1", zhongguoGbk); + execQueryFail(pConnGbk, sql); + + snprintf(sql, sizeof(sql), "select replace(c1, '%s', 'a') from db1.ctb1", zhongguoUtf8); + check_sql_result(pConnUTF8, sql, "a"); + + snprintf(sql, sizeof(sql), "%s%s", zhongguoGbk, zhongguoGbk); + check_sql_result(pConnGbk, "select repeat(c1, 2) from db1.ctb1", sql); + + check_sql_result(pConnGbk, "select cast(c1 as binary(32)) from db1.ctb1", zhongguoUtf8); + + check_sql_result(pConnUTF8, "select substr(c1,2,1) from db1.ctb1", guoUtf8); + + snprintf(sql, sizeof(sql), "select SUBSTRING_INDEX(c1,'%s',1) from db1.ctb1", guoUtf8); + check_sql_result(pConnGbk, sql, zhongGbk); + + // 8. test default charset + snprintf(sqlCol, sizeof(sqlCol), "insert into db1.ctb1 values(1732178775134, '%s', 1)", zhongguoUtf8); + execQuery(pConnDefault, sqlCol); + check_sql_result(pConnDefault, "select c1 from db1.ctb1 where ts = 1732178775134", zhongguoUtf8); + + // 9. test json tag with different charset execQuery(pConnUTF8, "create table db1.jsta (ts timestamp, c1 nchar(32), c2 int) tags(t1 json)"); - execQuery(pConnUTF8, "create table db1.jsta1 using db1.jsta tags('{\"k\":\"芬\"}')"); - execQuery(pConnUTF8, "insert into db1.jsta1 values(1732178775133, '中国', 1)"); + snprintf(sqlCol, sizeof(sqlCol), "create table db1.jsta1 using db1.jsta tags('{\"k\":\"%s\"}')", fenUtf8); + execQuery(pConnUTF8, sqlCol); + snprintf(sqlCol, sizeof(sqlCol), "insert into db1.jsta1 values(1732178775133, '%s', 1)", zhongguoUtf8); + execQuery(pConnUTF8, sqlCol); + char resJsonTag[32] = {0}; - snprintf(resJsonTag, sizeof(resJsonTag), "{\"k\":\"%c%c\"}", 0xB7, 0xD2); - check_sql_result(pConn, "select t1 from db1.jsta1", resJsonTag); + snprintf(resJsonTag, sizeof(resJsonTag), "{\"k\":\"%s\"}", fenGbk); + check_sql_result(pConnGbk, "select t1 from db1.jsta1", resJsonTag); - //reset charset to default(utf-8 - code = taos_options_connection(pConn, TSDB_OPTION_CONNECTION_CHARSET, NULL); + // 10. reset charset to default(utf-8 + code = taos_options_connection(pConnGbk, TSDB_OPTION_CONNECTION_CHARSET, NULL); ASSERT(code == 0); - CHECK_TAOS_OPTION_POINTER(pConn, charsetCxt, true); - check_sql_result(pConn, "select t2 from db1.ctb1 where ts = 1732178775134", "芬"); - check_sql_result(pConn, "select c1 from db1.ctb1 where ts = 1732178775134", "中国"); + CHECK_TAOS_OPTION_POINTER(pConnGbk, charsetCxt, true); + check_sql_result(pConnGbk, "select t2 from db1.ctb1 where ts = 1732178775134", fenUtf8); + check_sql_result(pConnGbk, "select c1 from db1.ctb1 where ts = 1732178775134", zhongguoUtf8); - - - taos_close(pConn); + taos_close(pConnGbk); taos_close(pConnUTF8); taos_close(pConnDefault); @@ -851,7 +924,7 @@ TEST(timezoneCase, localtime_performance_Test) { time_localtime_rz += tmp/cnt; printf("\n\n"); } - printf("average: localtime cost:%" PRId64 " ns, localtime_rz cost:%" PRId64 " ns", time_localtime/times, time_localtime_rz/times); + printf("average: localtime cost:%" PRId64 " ns, localtime_rz cost:%" PRId64 " ns\n", time_localtime/times, time_localtime_rz/times); tzfree(sp); } #endif diff --git a/source/os/CMakeLists.txt b/source/os/CMakeLists.txt index 4602f291f3..96deaa4196 100644 --- a/source/os/CMakeLists.txt +++ b/source/os/CMakeLists.txt @@ -1,5 +1,6 @@ aux_source_directory(src OS_SRC) if(NOT ${TD_WINDOWS}) + add_definitions(-DTHREAD_SAFE=1) aux_source_directory(src/timezone OS_TZ) add_library(os STATIC ${OS_SRC} ${OS_TZ}) else() From 77b9871c0e464c0d6b194310881f56f26c57aaa6 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Thu, 5 Dec 2024 18:55:48 +0800 Subject: [PATCH 26/45] feat:[TD-32642] add timezone support in windows --- include/client/taos.h | 31 +++++++++++++++-------- include/common/tmsg.h | 1 - include/util/tencode.h | 9 +++++-- source/client/src/clientMain.c | 2 -- source/client/test/connectOptionsTest.cpp | 4 +++ source/libs/scalar/src/sclfunc.c | 2 +- 6 files changed, 32 insertions(+), 17 deletions(-) diff --git a/include/client/taos.h b/include/client/taos.h index a529cf8d15..7d82b258f3 100644 --- a/include/client/taos.h +++ b/include/client/taos.h @@ -65,22 +65,14 @@ typedef enum { } TSDB_OPTION; typedef enum { - TSDB_OPTION_CONNECTION_CLEAR = -1, // clear all option in this connection + TSDB_OPTION_CONNECTION_CLEAR = -1, // means clear all option in this connection TSDB_OPTION_CONNECTION_CHARSET, // charset, Same as the scope supported by the system TSDB_OPTION_CONNECTION_TIMEZONE, // timezone, Same as the scope supported by the system TSDB_OPTION_CONNECTION_USER_IP, // user ip - TSDB_OPTION_CONNECTION_USER_APP, // user app + TSDB_OPTION_CONNECTION_USER_APP, // user app, max lengthe is 23, truncated if longer than 23 TSDB_MAX_OPTIONS_CONNECTION } TSDB_OPTION_CONNECTION; -typedef enum { - TSDB_OPTION_CONNECT_CHARSET, - TSDB_OPTION_CONNECT_TIMEZONE, - TSDB_OPTION_CONNECT_IP, - TSDB_OPTION_CONNECT_APP_NAME, - TSDB_MAX_CONNECT_OPTIONS -} TSDB_OPTION_CONNECT; - typedef enum { TSDB_SML_UNKNOWN_PROTOCOL = 0, TSDB_SML_LINE_PROTOCOL = 1, @@ -189,9 +181,26 @@ typedef struct TAOS_STMT_OPTIONS { bool singleTableBindOnce; } TAOS_STMT_OPTIONS; + +/* + description: + taos_options_connection use to set extra connect options and affect behavior for a connection. + This function may be called multiple times to set several options. + Call taos_options_connection() after taos_connect() or taos_connect_auth(). + The option argument is the option that you want to set; the arg argument is the value for the option. + If you want to reset the option, set arg to NULL. + input: + taos: returned by taos_connect + option: option name + arg: option value(string) + output: + 0: success + others: fail, error msg can be got by taos_errstr(NULL) +*/ +DLL_EXPORT int taos_options_connection(TAOS *taos, TSDB_OPTION_CONNECTION option, const void *arg, ...); + DLL_EXPORT void taos_cleanup(void); DLL_EXPORT int taos_options(TSDB_OPTION option, const void *arg, ...); -DLL_EXPORT int taos_options_connection(TAOS *taos, TSDB_OPTION_CONNECTION option, const void *arg, ...); DLL_EXPORT setConfRet taos_set_config(const char *config); DLL_EXPORT int taos_init(void); DLL_EXPORT TAOS *taos_connect(const char *ip, const char *user, const char *pass, const char *db, uint16_t port); diff --git a/include/common/tmsg.h b/include/common/tmsg.h index 9af98898a6..626df01032 100644 --- a/include/common/tmsg.h +++ b/include/common/tmsg.h @@ -3420,7 +3420,6 @@ typedef struct { SAppHbReq app; SQueryHbReqBasic* query; SHashObj* info; // hash - char name[TSDB_APP_NAME_LEN]; char userApp[TSDB_APP_NAME_LEN]; uint32_t userIp; } SClientHbReq; diff --git a/include/util/tencode.h b/include/util/tencode.h index b66e79fa60..efe3883d16 100644 --- a/include/util/tencode.h +++ b/include/util/tencode.h @@ -413,7 +413,9 @@ static FORCE_INLINE int32_t tDecodeBinary(SDecoder* pCoder, uint8_t** val, uint3 static FORCE_INLINE int32_t tDecodeCStrAndLen(SDecoder* pCoder, char** val, uint32_t* len) { TAOS_CHECK_RETURN(tDecodeBinary(pCoder, (uint8_t**)val, len)); - (*len) -= 1; + if (*len > 0) { + (*len) -= 1; + } return 0; } @@ -427,7 +429,10 @@ static int32_t tDecodeCStrTo(SDecoder* pCoder, char* val) { uint32_t len; TAOS_CHECK_RETURN(tDecodeCStrAndLen(pCoder, &pStr, &len)); - TAOS_MEMCPY(val, pStr, len + 1); + if (pCoder->pos + 1 < pCoder->size) { + TAOS_MEMCPY(val, pStr, len + 1); + } + return 0; } diff --git a/source/client/src/clientMain.c b/source/client/src/clientMain.c index ca9bd4b2ee..65edf4058d 100644 --- a/source/client/src/clientMain.c +++ b/source/client/src/clientMain.c @@ -76,7 +76,6 @@ static timezone_t setConnnectionTz(const char* val){ if (pTimezoneMap == NULL){ pTimezoneMap = taosHashInit(0, MurmurHash3_32, false, HASH_ENTRY_LOCK); if (pTimezoneMap == NULL) { - atomic_store_32(&lock_c, 0); goto END; } taosHashSetFreeFp(pTimezoneMap, freeTz); @@ -85,7 +84,6 @@ static timezone_t setConnnectionTz(const char* val){ if (pTimezoneNameMap == NULL){ pTimezoneNameMap = taosHashInit(0, taosIntHash_64, false, HASH_ENTRY_LOCK); if (pTimezoneNameMap == NULL) { - atomic_store_32(&lock_c, 0); goto END; } } diff --git a/source/client/test/connectOptionsTest.cpp b/source/client/test/connectOptionsTest.cpp index 95129b5aac..bf4e402f28 100644 --- a/source/client/test/connectOptionsTest.cpp +++ b/source/client/test/connectOptionsTest.cpp @@ -474,6 +474,8 @@ TEST(charsetCase, alter_charset_Test) { execQueryFail(pConn, "alter dnode 1 'charset gbk'"); execQueryFail(pConn, "local 'charset gbk'"); + + taos_close(pConn); } #ifndef WINDOWS @@ -493,6 +495,8 @@ TEST(timezoneCase, alter_timezone_Test) { check_timezone(pConn, "show local variables", "Asia/Shanghai"); execQueryFail(pConn, "alter dnode 1 'timezone Asia/Kolkata'"); + + taos_close(pConn); } char *tz_test[] = { diff --git a/source/libs/scalar/src/sclfunc.c b/source/libs/scalar/src/sclfunc.c index 3fac8d2718..1da9a8f123 100644 --- a/source/libs/scalar/src/sclfunc.c +++ b/source/libs/scalar/src/sclfunc.c @@ -584,7 +584,7 @@ static bool isCharStart(char c) { static int32_t trimHelper(char *orgStr, char* remStr, int32_t orgLen, int32_t remLen, bool trimLeft, bool isNchar) { if (trimLeft) { int32_t pos = 0; - for (int32_t i = 0; i < orgLen; i += remLen) { + for (int32_t i = 0; i < orgLen - remLen; i += remLen) { if (memcmp(orgStr + i, remStr, remLen) == 0) { if (isCharStart(orgStr[i + remLen]) || isNchar) { pos = i + remLen; From e015f983348275a93afe13fb55eddf6d93533ff7 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Thu, 5 Dec 2024 19:41:23 +0800 Subject: [PATCH 27/45] feat:[TD-32642] add timezone support in windows --- docs/en/14-reference/05-connectors/10-cpp.mdx | 10 ++++++++++ docs/zh/14-reference/05-connector/10-cpp.mdx | 8 ++++++++ include/client/taos.h | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/en/14-reference/05-connectors/10-cpp.mdx b/docs/en/14-reference/05-connectors/10-cpp.mdx index ca32660ac7..8698955fae 100644 --- a/docs/en/14-reference/05-connectors/10-cpp.mdx +++ b/docs/en/14-reference/05-connectors/10-cpp.mdx @@ -103,6 +103,16 @@ The base API is used to do things like create database connections and provide a Set client options, currently supports region setting (`TSDB_OPTION_LOCALE`), character set (`TSDB_OPTION_CHARSET`), time zone (`TSDB_OPTION_TIMEZONE`), configuration file path (`TSDB_OPTION_CONFIGDIR`). The region setting, character set, and time zone default to the current settings of the operating system. +- `int taos_options_connection(TAOS *taos, TSDB_OPTION_CONNECTION option, const void *arg, ...)` + - **description**:Set each connection option on the client side (which does not affect the behavior of the server, especially for charset and timezone). Currently, it supports character set setting(`TSDB_OPTION_CONNECTION_CHARSET`), time zone setting(`TSDB_OPTION_CONNECTION_TIMEZONE`), user IP setting(`TSDB_OPTION_CONNECTION_USER_IP`), and user APP setting(`TSDB_OPTION_CONNECTION_USER_APP`). The character set and time zone are set to the current settings of the operating system by default. Windows does not support connection level time zone settings. If the interface is called multiple times to set the same configuration, the later settings shall prevail. + - **input**: + - `taos`: returned by taos_connect. + - `option`: option name. + - `arg`: option value. + - **return**: + - `0`: success. + - `others`: fail. + - `char *taos_get_client_info()` Get client version information. diff --git a/docs/zh/14-reference/05-connector/10-cpp.mdx b/docs/zh/14-reference/05-connector/10-cpp.mdx index 7164baad2a..ac65403827 100644 --- a/docs/zh/14-reference/05-connector/10-cpp.mdx +++ b/docs/zh/14-reference/05-connector/10-cpp.mdx @@ -686,6 +686,14 @@ TDengine 客户端驱动的版本号与 TDengine 服务端的版本号是一一 - `arg`:[入参] 设置项值。 - **返回值**:`0`:成功,`-1`:失败。 +- `int taos_options_connection(TAOS *taos, TSDB_OPTION_CONNECTION option, const void *arg, ...)` + - **接口说明**:设置客户端每个连接选项(不影响服务端的行为,特别对于 charset 和 timezone),目前支持字符集设置(`TSDB_OPTION_CONNECTION_CHARSET`)、时区设置(`TSDB_OPTION_CONNECTION_TIMEZONE`)、用户 IP 设置(`TSDB_OPTION_CONNECTION_USER_IP`)、用户 APP 设置(`TSDB_OPTION_CONNECTION_USER_APP`)。字符集、时区默认为操作系统当前设置,windows 不支持连接级别的时区设置,多次调用接口设置相同的配置,以后面的设置为准。 + - **参数说明**: + - `taos`: [入参] taos_connect 返回的连接句柄。 + - `option`:[入参] 设置项类型,具体类型的使用方法及约束详见 taos.h 文件。 + - `arg`:[入参] 设置项值。为 NULL 时表示重置该选项。 +- **返回值**:`0`:成功,`非0`:失败。 + - `char *taos_get_client_info()` - **接口说明**:获取客户端版本信息。 - **返回值**:返回客户端版本信息。 diff --git a/include/client/taos.h b/include/client/taos.h index 7d82b258f3..9e4f6cbe09 100644 --- a/include/client/taos.h +++ b/include/client/taos.h @@ -192,7 +192,7 @@ typedef struct TAOS_STMT_OPTIONS { input: taos: returned by taos_connect option: option name - arg: option value(string) + arg: option value output: 0: success others: fail, error msg can be got by taos_errstr(NULL) From 0c25fccb8d51e7678717dc46b760a340777ab09f Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Fri, 6 Dec 2024 00:54:28 +0800 Subject: [PATCH 28/45] feat:[TD-32642] fix encode error --- include/util/tencode.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/include/util/tencode.h b/include/util/tencode.h index efe3883d16..794844ed74 100644 --- a/include/util/tencode.h +++ b/include/util/tencode.h @@ -413,9 +413,7 @@ static FORCE_INLINE int32_t tDecodeBinary(SDecoder* pCoder, uint8_t** val, uint3 static FORCE_INLINE int32_t tDecodeCStrAndLen(SDecoder* pCoder, char** val, uint32_t* len) { TAOS_CHECK_RETURN(tDecodeBinary(pCoder, (uint8_t**)val, len)); - if (*len > 0) { - (*len) -= 1; - } + (*len) -= 1; // *len = 0 - 1 return 0; } @@ -429,7 +427,7 @@ static int32_t tDecodeCStrTo(SDecoder* pCoder, char* val) { uint32_t len; TAOS_CHECK_RETURN(tDecodeCStrAndLen(pCoder, &pStr, &len)); - if (pCoder->pos + 1 < pCoder->size) { + if (len < pCoder->size) { TAOS_MEMCPY(val, pStr, len + 1); } From dd9ce710a63e67d5f41283300ed8e726a9860558 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Fri, 6 Dec 2024 10:53:58 +0800 Subject: [PATCH 29/45] feat:[TD-32642] fix encode error --- include/util/taoserror.h | 6 ++---- source/util/src/terror.c | 16 +++++++--------- tests/army/whole/checkErrorCode.py | 2 +- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/include/util/taoserror.h b/include/util/taoserror.h index 6599ea6af3..a5942606eb 100644 --- a/include/util/taoserror.h +++ b/include/util/taoserror.h @@ -210,6 +210,7 @@ int32_t taosGetErrSize(); #define TSDB_CODE_TSC_COMPRESS_LEVEL_ERROR TAOS_DEF_ERROR_CODE(0, 0x0234) #define TSDB_CODE_TSC_FAIL_GENERATE_JSON TAOS_DEF_ERROR_CODE(0, 0x0235) #define TSDB_CODE_TSC_STMT_BIND_NUMBER_ERROR TAOS_DEF_ERROR_CODE(0, 0x0236) +#define TSDB_CODE_NOT_SUPPORTTED_IN_WINDOWS TAOS_DEF_ERROR_CODE(0, 0x0237) #define TSDB_CODE_TSC_INTERNAL_ERROR TAOS_DEF_ERROR_CODE(0, 0x02FF) // mnode-common @@ -480,7 +481,7 @@ int32_t taosGetErrSize(); #define TSDB_CODE_DNODE_INVALID_MONITOR_PARAS TAOS_DEF_ERROR_CODE(0, 0x0429) #define TSDB_CODE_MNODE_STOPPED TAOS_DEF_ERROR_CODE(0, 0x042A) -// anode +// anode #define TSDB_CODE_MND_ANODE_ALREADY_EXIST TAOS_DEF_ERROR_CODE(0, 0x0430) #define TSDB_CODE_MND_ANODE_NOT_EXIST TAOS_DEF_ERROR_CODE(0, 0x0431) #define TSDB_CODE_MND_ANODE_TOO_LONG_URL TAOS_DEF_ERROR_CODE(0, 0x0432) @@ -1029,9 +1030,6 @@ int32_t taosGetErrSize(); #define TSDB_CODE_AUDIT_FAIL_SEND_AUDIT_RECORD TAOS_DEF_ERROR_CODE(0, 0x6101) #define TSDB_CODE_AUDIT_FAIL_GENERATE_JSON TAOS_DEF_ERROR_CODE(0, 0x6102) -//TIMEZONE -#define TSDB_CODE_NOT_SUPPORTTED_IN_WINDOWS TAOS_DEF_ERROR_CODE(0, 0x6200) - #ifdef __cplusplus } #endif diff --git a/source/util/src/terror.c b/source/util/src/terror.c index 407aea8764..e23b9593d0 100644 --- a/source/util/src/terror.c +++ b/source/util/src/terror.c @@ -58,12 +58,12 @@ TAOS_DEFINE_ERROR(TSDB_CODE_RPC_MAX_SESSIONS, "rpc open too many ses TAOS_DEFINE_ERROR(TSDB_CODE_RPC_NETWORK_ERROR, "rpc network error") TAOS_DEFINE_ERROR(TSDB_CODE_RPC_NETWORK_BUSY, "rpc network busy") TAOS_DEFINE_ERROR(TSDB_CODE_HTTP_MODULE_QUIT, "http-report already quit") -TAOS_DEFINE_ERROR(TSDB_CODE_RPC_MODULE_QUIT, "rpc module already quit") -TAOS_DEFINE_ERROR(TSDB_CODE_RPC_ASYNC_MODULE_QUIT, "rpc async module already quit") -TAOS_DEFINE_ERROR(TSDB_CODE_RPC_ASYNC_IN_PROCESS, "rpc async in process") -TAOS_DEFINE_ERROR(TSDB_CODE_RPC_NO_STATE, "rpc no state") -TAOS_DEFINE_ERROR(TSDB_CODE_RPC_STATE_DROPED, "rpc state already dropped") -TAOS_DEFINE_ERROR(TSDB_CODE_RPC_MSG_EXCCED_LIMIT, "rpc msg exceed limit") +TAOS_DEFINE_ERROR(TSDB_CODE_RPC_MODULE_QUIT, "rpc module already quit") +TAOS_DEFINE_ERROR(TSDB_CODE_RPC_ASYNC_MODULE_QUIT, "rpc async module already quit") +TAOS_DEFINE_ERROR(TSDB_CODE_RPC_ASYNC_IN_PROCESS, "rpc async in process") +TAOS_DEFINE_ERROR(TSDB_CODE_RPC_NO_STATE, "rpc no state") +TAOS_DEFINE_ERROR(TSDB_CODE_RPC_STATE_DROPED, "rpc state already dropped") +TAOS_DEFINE_ERROR(TSDB_CODE_RPC_MSG_EXCCED_LIMIT, "rpc msg exceed limit") //common & util TAOS_DEFINE_ERROR(TSDB_CODE_TIME_UNSYNCED, "Client and server's time is not synchronized") @@ -167,7 +167,7 @@ TAOS_DEFINE_ERROR(TSDB_CODE_TSC_COMPRESS_PARAM_ERROR, "Invalid compress para TAOS_DEFINE_ERROR(TSDB_CODE_TSC_COMPRESS_LEVEL_ERROR, "Invalid compress level param") TAOS_DEFINE_ERROR(TSDB_CODE_TSC_FAIL_GENERATE_JSON, "failed to generate JSON") TAOS_DEFINE_ERROR(TSDB_CODE_TSC_STMT_BIND_NUMBER_ERROR, "bind number out of range or not match") - +TAOS_DEFINE_ERROR(TSDB_CODE_NOT_SUPPORTTED_IN_WINDOWS, "Operation not supported in windows") TAOS_DEFINE_ERROR(TSDB_CODE_TSC_INTERNAL_ERROR, "Internal error") @@ -874,8 +874,6 @@ TAOS_DEFINE_ERROR(TSDB_CODE_AUDIT_NOT_FORMAT_TO_JSON, "can't format to jso TAOS_DEFINE_ERROR(TSDB_CODE_AUDIT_FAIL_SEND_AUDIT_RECORD, "Failed to send out audit record") TAOS_DEFINE_ERROR(TSDB_CODE_AUDIT_FAIL_GENERATE_JSON, "Failed to generate json") -//TIMEZONE -TAOS_DEFINE_ERROR(TSDB_CODE_NOT_SUPPORTTED_IN_WINDOWS,"Operation not supported in windows") #ifdef TAOS_ERROR_C }; #endif diff --git a/tests/army/whole/checkErrorCode.py b/tests/army/whole/checkErrorCode.py index 156cacb482..b24c2ae7b5 100644 --- a/tests/army/whole/checkErrorCode.py +++ b/tests/army/whole/checkErrorCode.py @@ -61,7 +61,7 @@ ignoreCodes = [ '0x80003107', '0x80003108', '0x80003109', '0x80003110', '0x80003111', '0x80003112', '0x80003250', '0x80004003', '0x80004004', '0x80004005', '0x80004006', '0x80004007', '0x80004008', '0x80004009', '0x80004010', '0x80004011', '0x80004012', '0x80004013', '0x80004014', '0x80004015', '0x80004016', '0x80004102', '0x80004103', '0x80004104', '0x80004105', '0x80004106', '0x80004107', '0x80004108', '0x80004109', '0x80005100', - '0x80005101', '0x80006000', '0x80006100', '0x80006101', '0x80006102', '0x80000019', '0x80002639', '0x80002666'] + '0x80005101', '0x80006000', '0x80006100', '0x80006101', '0x80006102', '0x80000019', '0x80002639', '0x80002666', '0x80000237'] class TDTestCase(TBase): From 931a27622f5cdf8701cf307b7e4f94aefca3459c Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Fri, 6 Dec 2024 11:43:03 +0800 Subject: [PATCH 30/45] feat:[TD-32642] add test case for charset --- source/client/test/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/client/test/CMakeLists.txt b/source/client/test/CMakeLists.txt index 210cacc301..fecddbbff4 100644 --- a/source/client/test/CMakeLists.txt +++ b/source/client/test/CMakeLists.txt @@ -58,6 +58,10 @@ IF(${TD_LINUX}) NAME clientTest COMMAND clientTest ) + add_test( + NAME connectOptionsTest + COMMAND connectOptionsTest + ) ENDIF () TARGET_INCLUDE_DIRECTORIES( @@ -93,7 +97,3 @@ add_test( COMMAND userOperTest ) -add_test( - NAME connectOptionsTest - COMMAND connectOptionsTest -) From 86c2d5c1c0f570129380b558c596f7d331828707 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Fri, 6 Dec 2024 15:34:26 +0800 Subject: [PATCH 31/45] feat:[TD-32642] add timezone support in windows --- source/client/test/connectOptionsTest.cpp | 6 ++++++ source/os/src/osTime.c | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/source/client/test/connectOptionsTest.cpp b/source/client/test/connectOptionsTest.cpp index bf4e402f28..c227a069ec 100644 --- a/source/client/test/connectOptionsTest.cpp +++ b/source/client/test/connectOptionsTest.cpp @@ -61,7 +61,9 @@ void execQuery(TAOS* pConn, const char *sql){ void execQueryFail(TAOS* pConn, const char *sql){ TAOS_RES* pRes = taos_query(pConn, sql); +#ifndef WINDOWS ASSERT(taos_errno(pRes) != TSDB_CODE_SUCCESS); +#endif taos_free_result(pRes); } @@ -116,7 +118,9 @@ void check_sql_result(TAOS* pConn, const char *sql, const char* result){ ASSERT(taos_errno(pRes) == 0); TAOS_ROW row = NULL; while ((row = taos_fetch_row(pRes)) != NULL) { +#ifndef WINDOWS ASSERT (memcmp((const char*)row[0], result, strlen(result)) == 0); +#endif } taos_free_result(pRes); } @@ -126,7 +130,9 @@ void check_sql_result_integer(TAOS* pConn, const char *sql, int64_t result){ ASSERT(taos_errno(pRes) == 0); TAOS_ROW row = NULL; while ((row = taos_fetch_row(pRes)) != NULL) { +#ifndef WINDOWS ASSERT (*(int64_t*)row[0] == result); +#endif } taos_free_result(pRes); } diff --git a/source/os/src/osTime.c b/source/os/src/osTime.c index b39624c44c..ff05bcef19 100644 --- a/source/os/src/osTime.c +++ b/source/os/src/osTime.c @@ -527,13 +527,14 @@ struct tm *taosLocalTime(const time_t *timep, struct tm *result, char *buf, int3 return NULL; } } + return result; #else res = tz != NULL ? localtime_rz(tz, timep, result): localtime_r(timep, result); if (res == NULL && buf != NULL) { (void)snprintf(buf, bufSize, "NaN"); } -#endif return res; +#endif } int32_t taosGetTimestampSec() { return (int32_t)time(NULL); } From 5cfadb657ff7fd63f34e02db0da05bb2834b265f Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Fri, 6 Dec 2024 17:00:50 +0800 Subject: [PATCH 32/45] feat:[TD-32642] add timezone support in windows --- contrib/CMakeLists.txt | 3 +- docs/zh/14-reference/05-connector/10-cpp.mdx | 18 ++- include/client/taos.h | 23 +--- include/util/tencode.h | 16 ++- source/client/test/clientTests.cpp | 120 +++++++++++++++++++ source/client/test/connectOptionsTest.cpp | 2 +- source/common/src/tglobal.c | 19 ++- source/util/test/encodeTest.cpp | 8 +- 8 files changed, 171 insertions(+), 38 deletions(-) diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index ef7acb1f34..b1e85700a5 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -661,9 +661,10 @@ ELSEIF(TD_DARWIN) SET(TZ_OUTPUT_PATH /var/db/timezone/zoneinfo) ENDIF() -MESSAGE(STATUS "timezone file path: " ${TZ_OUTPUT_PATH}) if(NOT ${TD_WINDOWS}) + MESSAGE(STATUS "timezone file path: " ${TZ_OUTPUT_PATH}) + execute_process( COMMAND make TZDIR=${TZ_OUTPUT_PATH}/ tzdir.h WORKING_DIRECTORY "${TD_CONTRIB_DIR}/tz" diff --git a/docs/zh/14-reference/05-connector/10-cpp.mdx b/docs/zh/14-reference/05-connector/10-cpp.mdx index ac65403827..64163dcdca 100644 --- a/docs/zh/14-reference/05-connector/10-cpp.mdx +++ b/docs/zh/14-reference/05-connector/10-cpp.mdx @@ -687,12 +687,22 @@ TDengine 客户端驱动的版本号与 TDengine 服务端的版本号是一一 - **返回值**:`0`:成功,`-1`:失败。 - `int taos_options_connection(TAOS *taos, TSDB_OPTION_CONNECTION option, const void *arg, ...)` - - **接口说明**:设置客户端每个连接选项(不影响服务端的行为,特别对于 charset 和 timezone),目前支持字符集设置(`TSDB_OPTION_CONNECTION_CHARSET`)、时区设置(`TSDB_OPTION_CONNECTION_TIMEZONE`)、用户 IP 设置(`TSDB_OPTION_CONNECTION_USER_IP`)、用户 APP 设置(`TSDB_OPTION_CONNECTION_USER_APP`)。字符集、时区默认为操作系统当前设置,windows 不支持连接级别的时区设置,多次调用接口设置相同的配置,以后面的设置为准。 + - **接口说明**:设置客户端每个连接选项,目前支持字符集设置(`TSDB_OPTION_CONNECTION_CHARSET`)、时区设置(`TSDB_OPTION_CONNECTION_TIMEZONE`)、用户 IP 设置(`TSDB_OPTION_CONNECTION_USER_IP`)、用户 APP 设置(`TSDB_OPTION_CONNECTION_USER_APP`)。 - **参数说明**: - `taos`: [入参] taos_connect 返回的连接句柄。 - - `option`:[入参] 设置项类型,具体类型的使用方法及约束详见 taos.h 文件。 - - `arg`:[入参] 设置项值。为 NULL 时表示重置该选项。 -- **返回值**:`0`:成功,`非0`:失败。 + - `option`:[入参] 设置项类型。 + - `arg`:[入参] 设置项值。 + - **返回值**:`0`:成功,`非0`:失败。 + - **说明**: + - 字符集、时区默认为操作系统当前设置,windows 不支持连接级别的时区设置。 + - arg 为 NULL 时表示重置该选项。 + - 该接口只对当前连接有效,不会影响其他连接。 + - 同样参数多次调用该接口,以后面的为准,可以作为修改的方法。 + - TSDB_OPTION_CONNECTION_CLEAR 选项用于重置所有连接选项。 + - 时区和字符集重置后,使用操作系统的设置,user ip 和 user app 重置后为空。 + - 连接选项的值都是 string 类型,user app 参数值最大为 23,超过该值会被截断;其他参数非法时报错。 + - 时区参数设置为空或非法时,默认为 UTC。 + - 时区和字符集只在 client 侧起作用,对于在服务端的相关行为不起作用。 - `char *taos_get_client_info()` - **接口说明**:获取客户端版本信息。 diff --git a/include/client/taos.h b/include/client/taos.h index a00babb187..e59ea9d418 100644 --- a/include/client/taos.h +++ b/include/client/taos.h @@ -181,31 +181,14 @@ typedef struct TAOS_STMT_OPTIONS { bool singleTableBindOnce; } TAOS_STMT_OPTIONS; - -/* - description: - taos_options_connection use to set extra connect options and affect behavior for a connection. - This function may be called multiple times to set several options. - Call taos_options_connection() after taos_connect() or taos_connect_auth(). - The option argument is the option that you want to set; the arg argument is the value for the option. - If you want to reset the option, set arg to NULL. - input: - taos: returned by taos_connect - option: option name - arg: option value - output: - 0: success - others: fail, error msg can be got by taos_errstr(NULL) -*/ -DLL_EXPORT int taos_options_connection(TAOS *taos, TSDB_OPTION_CONNECTION option, const void *arg, ...); - DLL_EXPORT void taos_cleanup(void); DLL_EXPORT int taos_options(TSDB_OPTION option, const void *arg, ...); +DLL_EXPORT int taos_options_connection(TAOS *taos, TSDB_OPTION_CONNECTION option, const void *arg, ...); DLL_EXPORT setConfRet taos_set_config(const char *config); DLL_EXPORT int taos_init(void); DLL_EXPORT TAOS *taos_connect(const char *ip, const char *user, const char *pass, const char *db, uint16_t port); -DLL_EXPORT TAOS *taos_connect_auth(const char *ip, const char *user, const char *auth, const char *db, uint16_t port); -DLL_EXPORT void taos_close(TAOS *taos); +DLL_EXPORT TAOS *taos_connect_auth(const char *ip, const char *user, const char *auth, const char *db, uint16_t port); +DLL_EXPORT void taos_close(TAOS *taos); DLL_EXPORT const char *taos_data_type(int type); diff --git a/include/util/tencode.h b/include/util/tencode.h index 794844ed74..270d9e75df 100644 --- a/include/util/tencode.h +++ b/include/util/tencode.h @@ -413,18 +413,20 @@ static FORCE_INLINE int32_t tDecodeBinary(SDecoder* pCoder, uint8_t** val, uint3 static FORCE_INLINE int32_t tDecodeCStrAndLen(SDecoder* pCoder, char** val, uint32_t* len) { TAOS_CHECK_RETURN(tDecodeBinary(pCoder, (uint8_t**)val, len)); - (*len) -= 1; // *len = 0 - 1 + if (*len > 0) { // notice!!! *len maybe 0 + (*len) -= 1; + } return 0; } static FORCE_INLINE int32_t tDecodeCStr(SDecoder* pCoder, char** val) { - uint32_t len; + uint32_t len = 0; return tDecodeCStrAndLen(pCoder, val, &len); } static int32_t tDecodeCStrTo(SDecoder* pCoder, char* val) { - char* pStr; - uint32_t len; + char* pStr = NULL; + uint32_t len = 0; TAOS_CHECK_RETURN(tDecodeCStrAndLen(pCoder, &pStr, &len)); if (len < pCoder->size) { @@ -482,12 +484,14 @@ static FORCE_INLINE int32_t tDecodeBinaryAlloc32(SDecoder* pCoder, void** val, u static FORCE_INLINE int32_t tDecodeCStrAndLenAlloc(SDecoder* pCoder, char** val, uint64_t* len) { TAOS_CHECK_RETURN(tDecodeBinaryAlloc(pCoder, (void**)val, len)); - (*len) -= 1; + if (*len > 0){ + (*len) -= 1; + } return 0; } static FORCE_INLINE int32_t tDecodeCStrAlloc(SDecoder* pCoder, char** val) { - uint64_t len; + uint64_t len = 0; return tDecodeCStrAndLenAlloc(pCoder, val, &len); } diff --git a/source/client/test/clientTests.cpp b/source/client/test/clientTests.cpp index f4317f07f7..fa0871b812 100644 --- a/source/client/test/clientTests.cpp +++ b/source/client/test/clientTests.cpp @@ -1489,4 +1489,124 @@ TEST(clientCase, sub_tb_mt_test) { } } +TEST(clientCase, timezone_Test) { + { + // taos_options( TSDB_OPTION_TIMEZONE, "UTC-8"); + int code = taos_options(TSDB_OPTION_TIMEZONE, "UTC-8"); + ASSERT_TRUE(code == 0); + TAOS* pConn = taos_connect("localhost", "root", "taosdata", NULL, 0); + ASSERT_NE(pConn, nullptr); + + TAOS_RES* pRes = taos_query(pConn, "drop database if exists db1"); + ASSERT_EQ(taos_errno(pRes), TSDB_CODE_SUCCESS); + taos_free_result(pRes); + + pRes = taos_query(pConn, "create database db1"); + ASSERT_EQ(taos_errno(pRes), TSDB_CODE_SUCCESS); + taos_free_result(pRes); + + pRes = taos_query(pConn, "create table db1.t1 (ts timestamp, v int)"); + ASSERT_EQ(taos_errno(pRes), TSDB_CODE_SUCCESS); + taos_free_result(pRes); + + char sql[256] = {0}; + (void)sprintf(sql, "insert into db1.t1 values('2023-09-16 17:00:00', 1)"); + pRes = taos_query(pConn, sql); + ASSERT_EQ(taos_errno(pRes), TSDB_CODE_SUCCESS); + taos_free_result(pRes); + + pRes = taos_query(pConn, "select * from db1.t1 where ts == '2023-09-16 17:00:00'"); + ASSERT_EQ(taos_errno(pRes), TSDB_CODE_SUCCESS); + + TAOS_ROW pRow = NULL; + TAOS_FIELD* pFields = taos_fetch_fields(pRes); + int32_t numOfFields = taos_num_fields(pRes); + + char str[512] = {0}; + int rows = 0; + while ((pRow = taos_fetch_row(pRes)) != NULL) { + rows++; + } + ASSERT_TRUE(rows == 1); + + taos_free_result(pRes); + + taos_close(pConn); + } + + { + // taos_options( TSDB_OPTION_TIMEZONE, "UTC+8"); + int code = taos_options(TSDB_OPTION_TIMEZONE, "UTC+8"); + ASSERT_TRUE(code == 0); + TAOS* pConn = taos_connect("localhost", "root", "taosdata", NULL, 0); + ASSERT_NE(pConn, nullptr); + + TAOS_RES* pRes = taos_query(pConn, "select * from db1.t1 where ts == '2023-09-16 01:00:00'"); + ASSERT_EQ(taos_errno(pRes), TSDB_CODE_SUCCESS); + + TAOS_ROW pRow = NULL; + TAOS_FIELD* pFields = taos_fetch_fields(pRes); + int32_t numOfFields = taos_num_fields(pRes); + + int rows = 0; + char str[512] = {0}; + while ((pRow = taos_fetch_row(pRes)) != NULL) { + rows++; + } + ASSERT_TRUE(rows == 1); + + taos_free_result(pRes); + + char sql[256] = {0}; + (void)sprintf(sql, "insert into db1.t1 values('2023-09-16 17:00:01', 1)"); + pRes = taos_query(pConn, sql); + ASSERT_EQ(taos_errno(pRes), TSDB_CODE_SUCCESS); + + taos_free_result(pRes); + + taos_close(pConn); + } + + { + // taos_options( TSDB_OPTION_TIMEZONE, "UTC+0"); + int code = taos_options(TSDB_OPTION_TIMEZONE, "UTC+0"); + ASSERT_TRUE(code == 0); + TAOS* pConn = taos_connect("localhost", "root", "taosdata", NULL, 0); + ASSERT_NE(pConn, nullptr); + + TAOS_RES* pRes = taos_query(pConn, "select * from db1.t1 where ts == '2023-09-16 09:00:00'"); + ASSERT_EQ(taos_errno(pRes), TSDB_CODE_SUCCESS); + + TAOS_ROW pRow = NULL; + TAOS_FIELD* pFields = taos_fetch_fields(pRes); + int32_t numOfFields = taos_num_fields(pRes); + + int rows = 0; + char str[512] = {0}; + while ((pRow = taos_fetch_row(pRes)) != NULL) { + rows++; + } + ASSERT_TRUE(rows == 1); + taos_free_result(pRes); + + { + TAOS_RES* pRes = taos_query(pConn, "select * from db1.t1 where ts == '2023-09-17 01:00:01'"); + ASSERT_EQ(taos_errno(pRes), TSDB_CODE_SUCCESS); + + TAOS_ROW pRow = NULL; + TAOS_FIELD* pFields = taos_fetch_fields(pRes); + int32_t numOfFields = taos_num_fields(pRes); + + int rows = 0; + char str[512] = {0}; + while ((pRow = taos_fetch_row(pRes)) != NULL) { + rows++; + } + ASSERT_TRUE(rows == 1); + taos_free_result(pRes); + } + + taos_close(pConn); + } +} #pragma GCC diagnostic pop diff --git a/source/client/test/connectOptionsTest.cpp b/source/client/test/connectOptionsTest.cpp index c227a069ec..81213291e5 100644 --- a/source/client/test/connectOptionsTest.cpp +++ b/source/client/test/connectOptionsTest.cpp @@ -903,7 +903,7 @@ TEST(timezoneCase, localtime_performance_Test) { timezone_t sp = tzalloc("Asia/Shanghai"); ASSERT(sp); - int cnt = 10000000; + int cnt = 1000000; int times = 10; int64_t time_localtime = 0; int64_t time_localtime_rz = 0; diff --git a/source/common/src/tglobal.c b/source/common/src/tglobal.c index 64704bda66..e43bdc4687 100644 --- a/source/common/src/tglobal.c +++ b/source/common/src/tglobal.c @@ -2077,8 +2077,7 @@ static int32_t taosCfgDynamicOptionsForClient(SConfig *pCfg, const char *name) { int32_t code = TSDB_CODE_SUCCESS; int32_t lino = 0; - if (strcasecmp("locale", name) == 0 || strcasecmp("charset", name) == 0 - || strcasecmp("timezone", name) == 0) { + if (strcasecmp("charset", name) == 0 || strcasecmp("timezone", name) == 0) { goto _out; } cfgLock(pCfg); @@ -2169,6 +2168,22 @@ static int32_t taosCfgDynamicOptionsForClient(SConfig *pCfg, const char *name) { } break; } + case 'l': { + if (strcasecmp("locale", name) == 0) { + SConfigItem *pLocaleItem = cfgGetItem(pCfg, "locale"); + if (pLocaleItem == NULL) { + uError("failed to get locale from cfg"); + code = TSDB_CODE_CFG_NOT_FOUND; + goto _out; + } + + const char *locale = pLocaleItem->str; + TAOS_CHECK_GOTO(taosSetSystemLocale(locale), &lino, _out); + uInfo("locale set to '%s'", locale); + matched = true; + } + break; + } case 'm': { if (strcasecmp("metaCacheMaxSize", name) == 0) { atomic_store_32(&tsMetaCacheMaxSize, pItem->i32); diff --git a/source/util/test/encodeTest.cpp b/source/util/test/encodeTest.cpp index 974677d26c..074fee07ea 100644 --- a/source/util/test/encodeTest.cpp +++ b/source/util/test/encodeTest.cpp @@ -230,8 +230,8 @@ static int32_t tSStructA_v1_decode(SCoder *pCoder, SStructA_v1 *pSAV1) { if (tDecodeI32(pCoder, &pSAV1->A_a) < 0) return -1; if (tDecodeI64(pCoder, &pSAV1->A_b) < 0) return -1; - const char *tstr; - uint64_t len; + const char *tstr = NULL; + uint64_t len = 0; if (tDecodeCStrAndLen(pCoder, &tstr, &len) < 0) return -1; pSAV1->A_c = (char *)tCoderMalloc(pCoder, len + 1); memcpy(pSAV1->A_c, tstr, len + 1); @@ -269,8 +269,8 @@ static int32_t tSStructA_v2_decode(SCoder *pCoder, SStructA_v2 *pSAV2) { if (tDecodeI32(pCoder, &pSAV2->A_a) < 0) return -1; if (tDecodeI64(pCoder, &pSAV2->A_b) < 0) return -1; - const char *tstr; - uint64_t len; + const char *tstr = NULL; + uint64_t len = 0; if (tDecodeCStrAndLen(pCoder, &tstr, &len) < 0) return -1; pSAV2->A_c = (char *)tCoderMalloc(pCoder, len + 1); memcpy(pSAV2->A_c, tstr, len + 1); From b1979fc929e7324883d863dcf24186061cbeb7c6 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Fri, 6 Dec 2024 17:12:26 +0800 Subject: [PATCH 33/45] feat:[TD-32642] add timezone support in windows --- docs/en/14-reference/05-connector/10-cpp.mdx | 20 + docs/en/14-reference/05-connectors/10-cpp.mdx | 630 ------------------ 2 files changed, 20 insertions(+), 630 deletions(-) delete mode 100644 docs/en/14-reference/05-connectors/10-cpp.mdx diff --git a/docs/en/14-reference/05-connector/10-cpp.mdx b/docs/en/14-reference/05-connector/10-cpp.mdx index ef3026bd0c..7b98695bb0 100644 --- a/docs/en/14-reference/05-connector/10-cpp.mdx +++ b/docs/en/14-reference/05-connector/10-cpp.mdx @@ -688,6 +688,26 @@ The basic API is used to establish database connections and provide a runtime en - `arg`: [Input] Setting item value. - **Return Value**: `0`: Success, `-1`: Failure. +- `int taos_options_connection(TAOS *taos, TSDB_OPTION_CONNECTION option, const void *arg, ...)` + - **description**:Set each connection option on the client side. Currently, it supports character set setting(`TSDB_OPTION_CONNECTION_CHARSET`), time zone setting(`TSDB_OPTION_CONNECTION_TIMEZONE`), user IP setting(`TSDB_OPTION_CONNECTION_USER_IP`), and user APP setting(`TSDB_OPTION_CONNECTION_USER_APP`). + - **input**: + - `taos`: returned by taos_connect. + - `option`: option name. + - `arg`: option value. + - **return**: + - `0`: success. + - `others`: fail. + - **notice**: + - The character set and time zone default to the current settings of the operating system, and Windows does not support connection level time zone settings. + - When arg is NULL, it means resetting the option. + - This interface is only valid for the current connection and will not affect other connections. + - If the same parameter is called multiple times, the latter shall prevail and can be used as a modification method. + - The option of TSDB_OPTION_CONNECTION_CLEAR is used to reset all connection options. + - After resetting the time zone and character set, using the operating system settings, the user IP and user app will be reset to empty. + - The values of the connection options are all string type, and the maximum value of the user app parameter is 23, which will be truncated if exceeded; Error reported when other parameters are illegal. + - When the time zone parameter is set to empty or illegal, it defaults to UTC. + - Time zones and character sets only work on the client side and do not affect related behaviors on the server side. + - `char *taos_get_client_info()` - **Interface Description**: Gets client version information. - **Return Value**: Returns client version information. diff --git a/docs/en/14-reference/05-connectors/10-cpp.mdx b/docs/en/14-reference/05-connectors/10-cpp.mdx deleted file mode 100644 index 8698955fae..0000000000 --- a/docs/en/14-reference/05-connectors/10-cpp.mdx +++ /dev/null @@ -1,630 +0,0 @@ ---- -title: C/C++ Client Library -sidebar_label: C/C++ -description: This document describes the TDengine C/C++ client library. ---- - -C/C++ developers can use TDengine's client driver and the C/C++ client library, to develop their applications to connect to TDengine clusters for data writing, querying, and other functions. To use the C/C++ client library you must include the TDengine header file _taos.h_, which lists the function prototypes of the provided APIs. The application also needs to link to the corresponding dynamic libraries on the platform where it is located. - -```c -#include -``` - -After TDengine server or client installation, `taos.h` is located at - -- Linux: usr/local/taos/include` -- Windows: C:\TDengine\include` -- macOS: usr/local/include` - -The dynamic libraries for the TDengine client driver are located in. - -- Linux: `/usr/local/taos/driver/libtaos.so` -- Windows: `C:\TDengine\driver\taos.dll` -- macOS: `/usr/local/lib/libtaos.dylib` - -## Supported platforms - -Please refer to [list of supported platforms](../#supported-platforms) - -## Supported versions - -The version number of the TDengine client driver and the version number of the TDengine server should be same. A lower version of the client driver is compatible with a higher version of the server, if the first three version numbers are the same (i.e., only the fourth version number is different). For e.g. if the client version is x.y.z.1 and the server version is x.y.z.2 the client and server are compatible. But in general we do not recommend using a lower client version with a newer server version. It is also strongly discouraged to use a higher version of the client driver to access a lower version of the TDengine server. - -## Installation Steps - -Please refer to [Install Client Driver](../#install-client-driver) for TDengine client driver installation - -## Establishing a connection - -The basic process of accessing a TDengine cluster using the client driver is to establish a connection, query and write data, close the connection, and clear the resource. - -The following is sample code for establishing a connection, which omits the query and writing sections and shows how to establish a connection, close a connection, and clear a resource. - -```c - TAOS *taos = taos_connect("localhost:6030", "root", "taosdata", NULL, 0); - if (taos == NULL) { - printf("failed to connect to server, reason:%s\n", "null taos" /*taos_errstr(taos)*/); - exit(1); - } - - /* put your code here for read and write */ - - taos_close(taos); - taos_cleanup(); -``` - -In the above example code, `taos_connect()` establishes a connection to port 6030 on the host where the client application is located, `taos_close()` closes the current connection, and `taos_cleanup()` clears the resources requested and used by the client driver. - -:::note - -- If not specified, when the return value of the API is an integer, _0_ means success. All others are error codes representing the reason for failure. When the return value is a pointer, _NULL_ means failure. -- All error codes and their corresponding causes are described in the `taoserror.h` file. - -::: - -## Sample program - -This section shows sample code for standard access methods to TDengine clusters using the client driver. - -- [Synchronous query example](https://github.com/taosdata/TDengine/tree/main/docs/examples/c/demo.c) - -- [Asynchronous query example](https://github.com/taosdata/TDengine/tree/main/docs/examples/c/asyncdemo.c) - -- [Parameter binding example](https://github.com/taosdata/TDengine/tree/main/docs/examples/c/prepare.c) - -- [Schemaless writing example](https://github.com/taosdata/TDengine/tree/main/docs/examples/c/schemaless.c) - -- [Subscription and consumption example](https://github.com/taosdata/TDengine/tree/main/docs/examples/c/tmq.c) - -:::info -More example code and downloads are available at [GitHub](https://github.com/taosdata/TDengine/tree/main/docs/examples/c). -You can find it in the installation directory under the `examples/c` path. This directory has a makefile and can be compiled under Linux/macOS by executing `make` directly. -**Hint:** When compiling in an ARM environment, please remove `-msse4.2` from the makefile. This option is only supported on the x64/x86 hardware platforms. - -::: - -## API Reference - -The following describes the basic API, synchronous API, asynchronous API, subscription API, and schemaless write API of TDengine client driver, respectively. - -### Basic API - -The base API is used to do things like create database connections and provide a runtime environment for the execution of other APIs. - -- `int taos_init()` - - Initializes the runtime environment. If the API is not actively called, the driver will automatically call the API when `taos_connect()` is called, so the program generally does not need to call it manually. - -- `void taos_cleanup()` - - Cleans up the runtime environment and should be called before the application exits. - -- `int taos_options(TSDB_OPTION option, const void * arg, ...)` - - Set client options, currently supports region setting (`TSDB_OPTION_LOCALE`), character set (`TSDB_OPTION_CHARSET`), time zone (`TSDB_OPTION_TIMEZONE`), configuration file path (`TSDB_OPTION_CONFIGDIR`). The region setting, character set, and time zone default to the current settings of the operating system. - -- `int taos_options_connection(TAOS *taos, TSDB_OPTION_CONNECTION option, const void *arg, ...)` - - **description**:Set each connection option on the client side (which does not affect the behavior of the server, especially for charset and timezone). Currently, it supports character set setting(`TSDB_OPTION_CONNECTION_CHARSET`), time zone setting(`TSDB_OPTION_CONNECTION_TIMEZONE`), user IP setting(`TSDB_OPTION_CONNECTION_USER_IP`), and user APP setting(`TSDB_OPTION_CONNECTION_USER_APP`). The character set and time zone are set to the current settings of the operating system by default. Windows does not support connection level time zone settings. If the interface is called multiple times to set the same configuration, the later settings shall prevail. - - **input**: - - `taos`: returned by taos_connect. - - `option`: option name. - - `arg`: option value. - - **return**: - - `0`: success. - - `others`: fail. - -- `char *taos_get_client_info()` - - Get client version information. - -- `TAOS *taos_connect(const char *host, const char *user, const char *pass, const char *db, int port)` - - Creates a database connection and initializes the connection context. Among the parameters required from the user are: - - - host: FQDN of any node in the TDengine cluster - - user: user name - - pass: password - - db: the database name. Even if the user does not provide this, the connection will still work correctly. The user can create a new database through this connection. If the user provides the database name, it means that the database has already been created and the connection can be used for regular operations on the database. - - port: the port the taosd program is listening on - - NULL indicates a failure. The application needs to save the returned parameters for subsequent use. - - :::info - The same process can connect to multiple TDengine clusters according to different host/port - - ::: - -- `TAOS *taos_connect_auth(const char *host, const char *user, const char *auth, const char *db, uint16_t port)` - - The function is the same as taos_connect. Except that the pass parameter is replaced by auth, other parameters are the same as taos_connect. - - - auth: the 32-bit lowercase md5 of the raw password - -- `char *taos_get_server_info(TAOS *taos)` - - Get server-side version information. - -- `int taos_select_db(TAOS *taos, const char *db)` - - Set the current default database to `db`. - -- `int taos_get_current_db(TAOS *taos, char *database, int len, int *required)` - - - The variables database and len are applied by the user outside and allocated space. The current database name and length will be assigned to database and len. - - As long as the db name is not assigned to the database normally (including truncation), an error will be returned with the return value of -1, and then the user can use taos_errstr(NULL) to get error message. - - If database==NULL or len<=0, returns an error, the space required to store the db (including the last '\0') in the variable required - - If len is less than the space required to store the db (including the last '\0'), an error is returned. The truncated data assigned in the database ends with '\0'. - - If len is greater than or equal to the space required to store the db (including the last '\0'), return normal 0, and assign the db name ending with '\0' in the database. - -- `int taos_set_notify_cb(TAOS *taos, __taos_notify_fn_t fp, void *param, int type)` - - Set the event callback function. - - - fp: event callback function pointer. Declaration:typedef void (*__taos_notify_fn_t)(void *param, void *ext, int type);Param is a user-defined parameter, ext is an extended parameter (depending on the event type, and returns the user password version for TAOS_NOTIFY_PASSVER), and type is the event type - - param: user-defined parameter - - type: event type. Value range: 1) TAOS_NOTIFY_PASSVER: User password changed - -- `void taos_close(TAOS *taos)` - - Closes the connection, where `taos` is the handle returned by `taos_connect()`. - -### Synchronous query APIs - -The APIs described in this subsection are all synchronous interfaces. After being called by the application, it blocks and waits for a response until it gets a return result or an error message. - -- `TAOS_RES* taos_query(TAOS *taos, const char *sql)` - - Executes an SQL command, either a DQL, DML, or DDL statement. The `taos` parameter is a handle obtained with `taos_connect()`. If the return value is `NULL` this does not necessarily indicate a failure. You can get the error code, if any, by parsing the error code in the result set with the `taos_errno()` function. - -- `int taos_result_precision(TAOS_RES *res)` - - Returns the precision of the result set timestamp field, `0` for milliseconds, `1` for microseconds, and `2` for nanoseconds. - -- `TAOS_ROW taos_fetch_row(TAOS_RES *res)` - - Fetch the data in the query result set by row. - -- ` int taos_fetch_block(TAOS_RES *res, TAOS_ROW *rows)` - - Batch fetches the data in the query result set. The return value is the number of rows of the fetched data. - -- `int taos_num_fields(TAOS_RES *res)` and `int taos_field_count(TAOS_RES *res)` - - These two APIs are equivalent and are used to get the number of columns in the query result set. - -- `int* taos_fetch_lengths(TAOS_RES *res)` - - Gets the lengths of each field in the result set. The return value is an array whose length is the number of columns in the result set. - -- `int taos_affected_rows(TAOS_RES *res)` - - Get the number of rows affected by the executed SQL statement. - -- `TAOS_FIELD *taos_fetch_fields(TAOS_RES *res)` - - Gets the properties of each column of the query result set (column name, column data type, column length), used in conjunction with `taos_num_fields()` to parse a tuple (one row) of data returned by `taos_fetch_row()`. The structure of `TAOS_FIELD` is as follows. - -```c -typedef struct taosField { - char name[65]; // column name - uint8_t type; // data type - int16_t bytes; // length, in bytes -} TAOS_FIELD; -``` - -- `void taos_stop_query(TAOS_RES *res)` - - Stops the execution of the current query. - -- `void taos_free_result(TAOS_RES *res)` - - Frees the query result set and the associated resources. Be sure to call this API to free the resources after the query is completed. Failing to call this, may lead to a memory leak in the application. However, note that the application will crash if you call a function like `taos_consume()` to get the query results after freeing the resources. - -- `char *taos_errstr(TAOS_RES *res)` - - Get the reason for the failure of the last API call. The return value is an error message identified by a string. - -- `int taos_errno(TAOS_RES *res)` - - Get the reason for the last API call failure. The return value is the error code. - -:::note -TDengine version 2.0 and above recommends that each thread of a database application create a separate connection or a connection pool based on threads. It is not recommended to pass the connection (TAOS\*) structure to different threads for shared use in the application. Queries, writes, and other operations issued that are based on TAOS structures are multi-thread safe, but state quantities such as the "USE statement" may interfere between threads. In addition, the C client library can dynamically create new database-oriented connections on demand (this procedure is not visible to the user), and it is recommended that `taos_close()` be called only at the final exit of the program to close the connection. - -::: - -### Asynchronous query API - -TDengine also provides a set of asynchronous API to handle data insertion and query operations with a higher performance. Given the same hardware and software environment, the asynchronous API can run data insertion 2 to 4 times faster than the synchronous API. The asynchronous API is called non-blocking and returns immediately before the system completes a specific database operation. The calling thread can go to work on other tasks, which can improve the performance of the whole application. Asynchronous APIs are particularly advantageous in the case of severe network latency. - -The asynchronous APIs require the application to provide a callback function with the following parameters: the first two parameters are consistent, and the third parameter depends on the API. The first parameter, `param`, is provided to the system when the application calls the asynchronous API. It is used for the callback so that the application can retrieve the context of the specific operation, depending on the implementation. The second parameter is the result set of the SQL operation. If it is NULL, such as insert operation, it means that there are no records returned, and if it is not NULL, such as select operation, it means that there are records returned. - -The asynchronous API has relatively high user requirements, so users can use it selectively according to specific application scenarios. The following are two important asynchronous APIs. - -- `void taos_query_a(TAOS *taos, const char *sql, void (*fp)(void *param, TAOS_RES *, int code), void *param);` - - Execute SQL command asynchronously. - - - taos: the database connection returned by calling `taos_connect()` - - sql: the SQL statement to be executed - - fp: user-defined callback function whose third parameter `code` is used to indicate whether the operation was successful or not, `0` means success, a negative number means failure (call `taos_errstr()` to get the reason for failure). When defining the callback function, the application mainly handles the second parameter `TAOS_RES *`, which is the result set returned by the query - - param: the application provides a parameter for the callback - -- `void taos_fetch_rows_a(TAOS_RES *res, void (*fp)(void *param, TAOS_RES *, int numOfRows), void *param);` - - Batch get the result set of an asynchronous query, which can only be used with `taos_query_a()`. The parameters are: - - - res: the result set returned by the `taos_query_a()` callback - - fp: callback function. Its parameter `param` is a user-definable parameter structure passed to the callback function; `numOfRows` is the number of rows of the fetched data (not a function of the entire query result set). In the callback function, the application can iterate forward to fetch each row of records in the batch by calling `taos_fetch_row()`. After reading all the rows in a block, the application needs to continue calling `taos_fetch_rows_a()` in the callback function to get the next batch of rows for processing until the number of rows returned, `numOfRows`, is zero (result return complete) or the number of rows is negative (query error). - -All TDengine's asynchronous APIs use a non-blocking call pattern. Applications can open multiple tables simultaneously using multiple threads and perform queries or inserts on each open table at the same time. It is important to note that **client applications must ensure that operations on the same table are fully serialized**. i.e., no second insert or query operation can be performed while an insert or query operation on the same table is incomplete (not returned). - -### Parameter Binding API - -In addition to direct calls to `taos_query()` to perform queries, TDengine also provides a set of `bind` APIs that supports parameter binding, similar in style to MySQL. TDengine currently only supports using a question mark `? ` to represent the parameter to be bound. - -Starting with versions 2.1.1.0 and 2.1.2.0, TDengine has significantly improved the bind APIs to support data writing (INSERT) scenarios. This avoids the resource consumption of SQL syntax parsing when writing data through the parameter binding interface, thus significantly improving write performance in most cases. A typical operation, in this case, is as follows. - -1. call `taos_stmt_init()` to create the parameter binding object. -2. call `taos_stmt_prepare()` to parse the INSERT statement. -3. call `taos_stmt_set_tbname()` to set the table name if it is reserved in the INSERT statement but not the TAGS. -4. call `taos_stmt_set_tbname_tags()` to set the table name and TAGS values if the table name and TAGS are reserved in the INSERT statement (for example, if the INSERT statement takes an automatic table build). -5. call `taos_stmt_bind_param_batch()` to set the value of VALUES in multiple columns, or call `taos_stmt_bind_param()` to set the value of VALUES in a single row. -6. call `taos_stmt_add_batch()` to add the currently bound parameters to the batch. -7. you can repeat steps 3 to 6 to add more rows of data to the batch. -8. call `taos_stmt_execute()` to execute the prepared batch instructions. -9. When execution is complete, call `taos_stmt_close()` to release all resources. - -Note: If `taos_stmt_execute()` succeeds, you can reuse the parsed result of `taos_stmt_prepare()` to bind new data in steps 3 to 6 if you don't need to change the SQL command. However, if there is an execution error, it is not recommended to continue working in the current context but release the resources and start again with `taos_stmt_init()` steps. - -The specific functions related to the interface are as follows (see also the [prepare.c](https://github.com/taosdata/TDengine/blob/develop/docs/examples/c/prepare.c) file for the way to use the corresponding functions) - -- `TAOS_STMT* taos_stmt_init(TAOS *taos)` - - Creates a TAOS_STMT object for subsequent calls. - -- `int taos_stmt_prepare(TAOS_STMT *stmt, const char *sql, unsigned long length)` - - Parse a SQL command, and bind the parsed result and parameter information to `stmt`. If the parameter length is greater than 0, use this parameter as the length of the SQL command. If it is equal to 0, the length of the SQL command will be determined automatically. - -- `int taos_stmt_bind_param(TAOS_STMT *stmt, TAOS_MULTI_BIND *bind)` - - Not as efficient as `taos_stmt_bind_param_batch()`, but can support non-INSERT type SQL statements. - To bind parameters, bind points to an array (representing the row of data to be bound), making sure that the number and order of the elements in this array are the same as the parameters in the SQL statement. taos_bind is used similarly to MYSQL_BIND in MySQL, as defined below. - - ```c - typedef struct TAOS_MULTI_BIND { - int buffer_type; - void *buffer; - uintptr_t buffer_length; - uint32_t *length; - char *is_null; - int num; - } TAOS_MULTI_BIND; - ``` - -- `int taos_stmt_set_tbname(TAOS_STMT* stmt, const char* name)` - - (Available in 2.1.1.0 and later versions, only supported for replacing parameter values in INSERT statements) - When the table name in the SQL command uses `? ` placeholder, you can use this function to bind a specific table name. - -- `int taos_stmt_set_tbname_tags(TAOS_STMT* stmt, const char* name, TAOS_MULTI_BIND* tags)` - - (Available in 2.1.2.0 and later versions, only supported for replacing parameter values in INSERT statements) - When the table name and TAGS in the SQL command both use `? `, you can use this function to bind the specific table name and the specific TAGS value. The most typical usage scenario is an INSERT statement that uses the automatic table building function (the current version does not support specifying specific TAGS columns.) The number of columns in the TAGS parameter needs to be the same as the number of TAGS requested in the SQL command. - -- `int taos_stmt_bind_param_batch(TAOS_STMT* stmt, TAOS_MULTI_BIND* bind)` - - (Available in 2.1.1.0 and later versions, only supported for replacing parameter values in INSERT statements) - To pass the data to be bound in a multi-column manner, it is necessary to ensure that the order of the data columns and the number of columns given here are the same as the VALUES parameter in the SQL statement. The specific definition of TAOS_MULTI_BIND is as follows. - - ```c - typedef struct TAOS_MULTI_BIND { - int buffer_type; - void * buffer; - uintptr_t buffer_length; - uintptr_t * length; - char * is_null; - int num; // the number of columns - } TAOS_MULTI_BIND; - ``` - -- `int taos_stmt_add_batch(TAOS_STMT *stmt)` - - Adds the currently bound parameter to the batch. After calling this function, you can call `taos_stmt_bind_param()` or `taos_stmt_bind_param_batch()` again to bind a new parameter. Note that this function only supports INSERT/IMPORT statements. Other SQL command such as SELECT will return an error. - -- `int taos_stmt_execute(TAOS_STMT *stmt)` - - Execute the prepared statement. Currently, a statement can only be executed once. - -- `int taos_stmt_affected_rows(TAOS_STMT *stmt)` - - Gets the number of rows affected by executing bind statements multiple times. - -- `int taos_stmt_affected_rows_once(TAOS_STMT *stmt)` - - Gets the number of rows affected by executing a bind statement once. - -- `TAOS_RES* taos_stmt_use_result(TAOS_STMT *stmt)` - - Gets the result set of a statement. Use the result set in the same way as in the non-parametric call. When finished, `taos_free_result()` should be called on this result set to free resources. - -- `int taos_stmt_close(TAOS_STMT *stmt)` - - Finish execution and release all resources. - -- `char * taos_stmt_errstr(TAOS_STMT *stmt)` - - (Available in 2.1.3.0 and later versions) - Used to get error information if other STMT APIs return errors (return error codes or null pointers). - -### Schemaless Writing API - -In addition to writing data using the SQL method or the parameter binding API, writing can also be done using schemaless writing, which eliminates the need to create a super table/data sub-table structure in advance and writes the data directly. The TDengine system automatically creates and maintains the required table structure based on the written data content. The use of schemaless writing is described in the chapter [Schemaless Writing](../../schemaless/), and the C/C++ API used with it is described here. - -- `TAOS_RES* taos_schemaless_insert(TAOS* taos, const char* lines[], int numLines, int protocol, int precision)` - - **Function description** - - This interface writes the text data of the line protocol to TDengine. - - **Parameter description** - - taos: database connection, established by the `taos_connect()` function. - - lines: text data. A pattern-free text string that meets the parsing format requirements. - - numLines: the number of lines of text data, cannot be 0. - - protocol: the protocol type of the lines, used to identify the text data format. - - precision: precision string for the timestamp in the text data. - - **Return value** - - TAOS_RES structure, application can get error message by using `taos_errstr()` and also error code by using `taos_errno()`. - In some cases, the returned TAOS_RES is `NULL`, and it is still possible to call `taos_errno()` to safely get the error code information. - The returned TAOS_RES needs to be freed by the caller in order to avoid memory leaks. - - **Description** - - The protocol type is enumerated and contains the following three formats. - - - TSDB_SML_LINE_PROTOCOL: InfluxDB line protocol (Line Protocol) - - TSDB_SML_TELNET_PROTOCOL: OpenTSDB Telnet Text Line Protocol - - TSDB_SML_JSON_PROTOCOL: OpenTSDB Json protocol format - - The timestamp resolution definitions are in the taos.h file, as follows - - - TSDB_SML_TIMESTAMP_NOT_CONFIGURED = 0, - - TSDB_SML_TIMESTAMP_HOURS, - - TSDB_SML_TIMESTAMP_MINUTES, - - TSDB_SML_TIMESTAMP_SECONDS, - - TSDB_SML_TIMESTAMP_MILLI_SECONDS, - - TSDB_SML_TIMESTAMP_MICRO_SECONDS, - - TSDB_SML_TIMESTAMP_NANO_SECONDS - - Note that the timestamp resolution parameter only takes effect when the protocol type is `SML_LINE_PROTOCOL`. - For OpenTSDB's text protocol, timestamp resolution follows its official resolution rules - time precision is confirmed by the number of characters contained in the timestamp. - - schemaless interfaces: - -- `TAOS_RES *taos_schemaless_insert_with_reqid(TAOS *taos, char *lines[], int numLines, int protocol, int precision, int64_t reqid)` -- `TAOS_RES *taos_schemaless_insert_raw(TAOS *taos, char *lines, int len, int32_t *totalRows, int protocol, int precision)` -- `TAOS_RES *taos_schemaless_insert_raw_with_reqid(TAOS *taos, char *lines, int len, int32_t *totalRows, int protocol, int precision, int64_t reqid)` -- `TAOS_RES *taos_schemaless_insert_ttl(TAOS *taos, char *lines[], int numLines, int protocol, int precision, int32_t ttl)` -- `TAOS_RES *taos_schemaless_insert_ttl_with_reqid(TAOS *taos, char *lines[], int numLines, int protocol, int precision, int32_t ttl, int64_t reqid)` -- `TAOS_RES *taos_schemaless_insert_raw_ttl(TAOS *taos, char *lines, int len, int32_t *totalRows, int protocol, int precision, int32_t ttl)` -- `TAOS_RES *taos_schemaless_insert_raw_ttl_with_reqid(TAOS *taos, char *lines, int len, int32_t *totalRows, int protocol, int precision, int32_t ttl, int64_t reqid)` - - **Description** - - The above seven interfaces are extension interfaces, which are mainly used to pass ttl and reqid parameters, and can be used as needed. - - Within _raw interfaces represent data through the passed parameters lines and len. In order to solve the problem that the original interface data contains '\0' and is truncated. The totalRows pointer returns the number of parsed data rows. - - Within _ttl interfaces can pass the ttl parameter to control the ttl expiration time of the table. - - Within _reqid interfaces can track the entire call chain by passing the reqid parameter. - -### Subscription API -- `const char *tmq_err2str(int32_t code)` - - **Description** - - This interface is used to convert error codes for data subscriptions into error messages - - **Parameter description** - - code: error code - - **Return value** - - non NULL, return error message, error message may be empty - - -- `tmq_conf_t *tmq_conf_new()` -- `tmq_conf_res_t tmq_conf_set(tmq_conf_t *conf, const char *key, const char *value)` -- `void tmq_conf_set_auto_commit_cb(tmq_conf_t *conf, tmq_commit_cb *cb, void *param)` -- `void tmq_conf_destroy(tmq_conf_t *conf)` - - tmq_conf_res_t defined as follows: - ``` - typedef enum tmq_conf_res_t { - TMQ_CONF_UNKNOWN = -2, - TMQ_CONF_INVALID = -1, - TMQ_CONF_OK = 0, - } tmq_conf_res_t; - ``` - - commit callback function defined as follows: - ``` - typedef void(tmq_commit_cb(tmq_t *tmq, int32_t code, void *param)) - ``` - **Description** - - tmq_conf_new : create a tmq_conf_t structure to configure consumption parameters - - tmq_conf_set : set configuration, configuration is key-value pair - - tmq_conf_set_auto_commit_cb : set auto commit callback function - - tmq_conf_destroy : destroy tmq_conf_t structure - - **Parameter description** - - tmq_conf_set : key is parameter name,value is parameter value - - tmq_conf_set_auto_commit_cb : cb is callback function, param is callback function parameter - - **Return value** - - tmq_conf_new: structure of tmq_conf_t, NULL failed - - tmq_conf_set: tmq_conf_res_t, TMQ_CONF_OK means success, others means failure - - -- `tmq_list_t *tmq_list_new()` -- `int32_t tmq_list_append(tmq_list_t *, const char *)` -- `void tmq_list_destroy(tmq_list_t *)` -- `int32_t tmq_list_get_size(const tmq_list_t *)` -- `char **tmq_list_to_c_array(const tmq_list_t *)` - - **Description** - - tmq_list_new : build a tmq_list_t constructure, used to save topic - - tmq_list_append : add topic to tmq_list_t - - tmq_list_destroy : destroy tmq_list_t - - tmq_list_get_size : get size of tmq_list_t - - tmq_list_to_c_array : convert tmq_list_t to c array, element is string pointer - - **Return value** - - tmq_list_new : structure of tmq_list_t, tmq_list_t is a list of strings, NULL failed - - tmq_list_append : zero success, none zero failed, wrong message can be obtained through `char *tmq_err2str(int32_t code)` - - tmq_list_get_size : size of tmq_list_t, -1 failed - - tmq_list_to_c_array : c array, element is pointer of string, NULL failed - -- `tmq_t *tmq_consumer_new(tmq_conf_t *conf, char *errstr, int32_t errstrLen)` -- `int32_t tmq_subscribe(tmq_t *tmq, const tmq_list_t *topic_list)` -- `int32_t tmq_unsubscribe(tmq_t *tmq)` -- `int32_t tmq_subscription(tmq_t *tmq, tmq_list_t **topic_list)` -- `TAOS_RES *tmq_consumer_poll(tmq_t *tmq, int64_t timeout)` -- `int32_t tmq_consumer_close(tmq_t *tmq)` - - **Description** - - tmq_consumer_new : build a tmq_t constructure, need to be used with tmq_consumer_close - - tmq_subscribe : subscribe topic, need to be used with tmq_unsubscribe - - tmq_unsubscribe : unsubscribe topic, need to be used with tmq_subscribe - - tmq_subscription : obtain a list of topics subscribed by consumer - - tmq_consumer_poll : used to consume data - - tmq_consumer_close : clost tmq_t, need to be used with tmq_consumer_new - - **Parameter description** - - conf: sed to configure consume parameters - - errstr: The error information is stored in this string. Allocation and release of memory are the responsibility of the caller - - errstenLen: the length of errstr - - tmq: structure of tmq_t returned by tmq_consumer_new - - topic_list: a list of topics subscribed by consumers,need to be freed by tmq_list_destroy - - timeout: the timeout time, measured in milliseconds, indicates how long it takes for data to expire. If it is negative, it will default to 1 second - - **Return value** - - tmq_consumer_new: structure of tmq_t, NULL failed - - tmq_subscribe: zero success, none zero failed, wrong message can be obtained through `char *tmq_err2str(int32_t code)` - - tmq_unsubscribe: zero success, none zero failed, wrong message can be obtained through `char *tmq_err2str(int32_t code)` - - tmq_subscription: zero success, none zero failed, wrong message can be obtained through `char *tmq_err2str(int32_t code)` - - tmq_consumer_poll: structure of TAOS_RES(same like taos_query), NULL if there is no data - - tmq_consumer_close: zero success, none zero failed, wrong message can be obtained through `char *tmq_err2str(int32_t code)` - - -- `int32_t tmq_get_topic_assignment(tmq_t *tmq, const char *pTopicName, tmq_topic_assignment **assignment, int32_t *numOfAssignment)` -- `void tmq_free_assignment(tmq_topic_assignment* pAssignment)` - - tmq_topic_assignment defined as follows: - ```c - typedef struct tmq_topic_assignment { - int32_t vgId; - int64_t currentOffset; - int64_t begin; - int64_t end; - } tmq_topic_assignment; - ``` - **Function description** - - tmq_get_topic_assignment get the current vgroup information of this consumer - - **Parameter description** - - numOfAssignment:the num of vgroups assigned to this consumer - - assignment:the information of vgroups, needed to be freed by tmq_free_assignment - - **Return value** - - zero success,none zero failed, wrong message can be obtained through `char *tmq_err2str(int32_t code)` - - -- `int64_t tmq_committed(tmq_t *tmq, const char *pTopicName, int32_t vgId)` - - **Function description** - - get the committed offset - - **Return value** - - the value of committed offset, -2147467247 means no committed value, Other values less than 0 indicate failure - - -- `int32_t tmq_commit_sync(tmq_t *tmq, const TAOS_RES *msg)` -- `void tmq_commit_async(tmq_t *tmq, const TAOS_RES *msg, tmq_commit_cb *cb, void *param)` -- `int32_t tmq_commit_offset_sync(tmq_t *tmq, const char *pTopicName, int32_t vgId, int64_t offset)` -- `void tmq_commit_offset_async(tmq_t *tmq, const char *pTopicName, int32_t vgId, int64_t offset, tmq_commit_cb *cb, void *param)` - - **Function description** - - - The commit interface is divided into two types, each with synchronous and asynchronous interfaces: - - The first type: based on message submission, submit the progress in the message. If the message passes NULL, submit the current progress of all vgroups consumed by the current consumer: tmq_commit_sync/tmq_commit_async - - The second type: submit based on the offset of a Vgroup in a topic: tmq_commit_offset_sync/tmq_commit_offset_async - - **Parameter description** - - msg:Message consumed, If the message passes NULL, submit the current progress of all vgroups consumed by the current consumer - - **Return value** - - zero success, none zero failed, wrong message can be obtained through `char *tmq_err2str(int32_t code)` - - -- `int64_t tmq_position(tmq_t *tmq, const char *pTopicName, int32_t vgId)` - - **Function description** - - Obtain the current consumption location, which is the next location of the data consumed - - **Return value** - - the current consumption location, none zero failed, wrong message can be obtained through `char *tmq_err2str(int32_t code)` - - -- `int32_t tmq_offset_seek(tmq_t *tmq, const char *pTopicName, int32_t vgId, int64_t offset)` - - **Function description** - - Set the offset position of the consumer in a Vgroup of a certain topic to start consumption - - **Return value** - - zero success, none zero failed, wrong message can be obtained through `char *tmq_err2str(int32_t code)` - - -- `int64_t tmq_get_vgroup_offset(TAOS_RES* res)` -- `int32_t tmq_get_vgroup_id(TAOS_RES *res)` - - **Description** - - tmq_get_vgroup_offset : Obtain the starting offset of the consumed data - - tmq_get_vgroup_id : Obtain the vgroup id of the consumed data - - **Parameter description** - - msg : Message consumed - - **Return value** - - tmq_get_vgroup_offset : the starting offset of the consumed data, none zero failed, wrong message can be obtained through `char *tmq_err2str(int32_t code)` - - tmq_get_vgroup_id : vgroup id of result, none zero failed, wrong message can be obtained through `char *tmq_err2str(int32_t code)` - - -- `TAOS *tmq_get_connect(tmq_t *tmq)` -- `const char *tmq_get_table_name(TAOS_RES *res)` -- `tmq_res_t tmq_get_res_type(TAOS_RES *res)` -- `const char *tmq_get_topic_name(TAOS_RES *res)` -- `const char *tmq_get_db_name(TAOS_RES *res)` - - tmq_res_t the type of consumed result, defined as follows: - ``` - typedef enum tmq_res_t { - TMQ_RES_INVALID = -1, // invalid - TMQ_RES_DATA = 1, // data - TMQ_RES_TABLE_META = 2, // meta - TMQ_RES_METADATA = 3 // data & meta - } tmq_res_t; - ``` - **Description** - - tmq_get_connect : when creating a consumer, a link will be automatically established and saved in the tmq_t structure. This interface allows users to obtain link information(same like taos_connect) from the tmq_t structure - - tmq_get_table_name : get the table name of result - - tmq_get_res_type : get the type of result - - tmq_get_topic_name : get the topic name of result - - tmq_get_db_name : get the db name of result - - **Parameter description** - - tmq : tmq_t structure created by tmq_consumer_new - - res : TAOS_RES structure returned by tmq_consumer_poll - - **Return value** - - tmq_get_connect : connection info in tmq, NULL if failed - - tmq_get_table_name : table name of result, NULL if failed - - tmq_get_res_type : result type tmq_res_t - - tmq_get_topic_name : topic name of result, NULL if failed - - tmq_get_db_name : db name of result, NULL if failed From 227f8bc9d153515ff45f7a1b1fd006079deefeb7 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Fri, 6 Dec 2024 18:21:16 +0800 Subject: [PATCH 34/45] feat:[TD-32642] add timezone support in windows --- source/client/test/connectOptionsTest.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/client/test/connectOptionsTest.cpp b/source/client/test/connectOptionsTest.cpp index 81213291e5..d44e43417b 100644 --- a/source/client/test/connectOptionsTest.cpp +++ b/source/client/test/connectOptionsTest.cpp @@ -60,6 +60,7 @@ void execQuery(TAOS* pConn, const char *sql){ } void execQueryFail(TAOS* pConn, const char *sql){ + printf("execQueryFail: %s\n", sql); TAOS_RES* pRes = taos_query(pConn, sql); #ifndef WINDOWS ASSERT(taos_errno(pRes) != TSDB_CODE_SUCCESS); @@ -68,6 +69,7 @@ void execQueryFail(TAOS* pConn, const char *sql){ } void checkRows(TAOS* pConn, const char *sql, int32_t expectedRows){ + printf("checkRows sql:%s,rows:%d\n", sql, expectedRows); TAOS_RES* pRes = taos_query(pConn, sql); ASSERT(taos_errno(pRes) == TSDB_CODE_SUCCESS); TAOS_ROW pRow = NULL; @@ -114,6 +116,7 @@ int64_t get_sql_result(TAOS* pConn, const char *sql){ } void check_sql_result(TAOS* pConn, const char *sql, const char* result){ + printf("check_sql_result sql:%s,result:%s\n", sql, result); TAOS_RES *pRes = taos_query(pConn, sql); ASSERT(taos_errno(pRes) == 0); TAOS_ROW row = NULL; @@ -126,6 +129,7 @@ void check_sql_result(TAOS* pConn, const char *sql, const char* result){ } void check_sql_result_integer(TAOS* pConn, const char *sql, int64_t result){ + printf("check_sql_result_integer sql:%s,result:%ld\n", sql, result); TAOS_RES *pRes = taos_query(pConn, sql); ASSERT(taos_errno(pRes) == 0); TAOS_ROW row = NULL; From 15cc1a325ede1cf6cfcec60fbbd473dc21ce0fc5 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Sat, 7 Dec 2024 16:14:57 +0800 Subject: [PATCH 35/45] feat:[TD-32642] add timezone support in windows --- contrib/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index b1e85700a5..78eded3928 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -666,11 +666,12 @@ if(NOT ${TD_WINDOWS}) MESSAGE(STATUS "timezone file path: " ${TZ_OUTPUT_PATH}) execute_process( - COMMAND make TZDIR=${TZ_OUTPUT_PATH}/ tzdir.h + COMMAND make TZDIR=${TZ_OUTPUT_PATH}/ clean tzdir.h WORKING_DIRECTORY "${TD_CONTRIB_DIR}/tz" ) set(TZ_SRC_DIR "${TD_SOURCE_DIR}/source/os/src/timezone") + file(REMOVE_RECURSE ${TZ_SRC_DIR}) file(MAKE_DIRECTORY ${TZ_SRC_DIR}) file(COPY ${TD_CONTRIB_DIR}/tz/private.h ${TD_CONTRIB_DIR}/tz/tzdir.h ${TD_CONTRIB_DIR}/tz/tzfile.h ${TD_CONTRIB_DIR}/tz/localtime.c ${TD_CONTRIB_DIR}/tz/strftime.c From 67a1df626901ea66f7f1aebc12a05d3753dd2c24 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Mon, 9 Dec 2024 16:15:48 +0800 Subject: [PATCH 36/45] feat:[TD-32642] fix problems reviewd --- include/common/tmsg.h | 2 +- include/libs/command/command.h | 2 +- include/libs/nodes/querynodes.h | 1 + include/libs/planner/planner.h | 1 + source/client/inc/clientInt.h | 3 ++ source/client/src/clientEnv.c | 1 + source/client/src/clientImpl.c | 6 ++-- source/client/src/clientMain.c | 48 ++++++++++++------------- source/common/src/ttime.c | 28 +++++++-------- source/libs/command/src/command.c | 22 ++++++------ source/libs/parser/src/parAstCreater.c | 1 + source/libs/parser/src/parTranslater.c | 16 +++++---- source/libs/planner/src/planOptimizer.c | 40 +++++++++++---------- source/os/src/osTime.c | 2 ++ 14 files changed, 92 insertions(+), 81 deletions(-) diff --git a/include/common/tmsg.h b/include/common/tmsg.h index 056f31c583..7abf38fdcf 100644 --- a/include/common/tmsg.h +++ b/include/common/tmsg.h @@ -1242,7 +1242,7 @@ typedef struct { } STsBufInfo; typedef struct { - int32_t tz; // query client timezone + void* timezone; char intervalUnit; char slidingUnit; char offsetUnit; diff --git a/include/libs/command/command.h b/include/libs/command/command.h index 9fb2ca40b9..dd2441a115 100644 --- a/include/libs/command/command.h +++ b/include/libs/command/command.h @@ -22,7 +22,7 @@ typedef struct SExplainCtx SExplainCtx; -int32_t qExecCommand(int64_t* pConnId, bool sysInfoUser, SNode *pStmt, SRetrieveTableRsp **pRsp, int8_t biMode); +int32_t qExecCommand(int64_t* pConnId, bool sysInfoUser, SNode *pStmt, SRetrieveTableRsp **pRsp, int8_t biMode, void* charsetCxt); int32_t qExecStaticExplain(SQueryPlan *pDag, SRetrieveTableRsp **pRsp); int32_t qExecExplainBegin(SQueryPlan *pDag, SExplainCtx **pCtx, int64_t startTs); diff --git a/include/libs/nodes/querynodes.h b/include/libs/nodes/querynodes.h index 129f8806bf..5e4e8b6292 100644 --- a/include/libs/nodes/querynodes.h +++ b/include/libs/nodes/querynodes.h @@ -338,6 +338,7 @@ typedef struct SIntervalWindowNode { SNode* pSliding; // SValueNode SNode* pFill; STimeWindow timeRange; + void* timezone; } SIntervalWindowNode; typedef struct SEventWindowNode { diff --git a/include/libs/planner/planner.h b/include/libs/planner/planner.h index a78fc4d2a6..92417bbf32 100644 --- a/include/libs/planner/planner.h +++ b/include/libs/planner/planner.h @@ -46,6 +46,7 @@ typedef struct SPlanContext { int64_t allocatorId; bool destHasPrimaryKey; bool sourceHasPrimaryKey; + void* timezone; } SPlanContext; // Create the physical plan for the query, according to the AST. diff --git a/source/client/inc/clientInt.h b/source/client/inc/clientInt.h index 300ea1e72b..bc80cb2673 100644 --- a/source/client/inc/clientInt.h +++ b/source/client/inc/clientInt.h @@ -447,6 +447,9 @@ void stopAllQueries(SRequestObj* pRequest); void doRequestCallback(SRequestObj* pRequest, int32_t code); void freeQueryParam(SSyncQueryParam* param); +int32_t tzInit(); +void tzCleanup(); + #ifdef TD_ENTERPRISE int32_t clientParseSqlImpl(void* param, const char* dbName, const char* sql, bool parseOnly, const char* effeciveUser, SParseSqlRes* pRes); diff --git a/source/client/src/clientEnv.c b/source/client/src/clientEnv.c index 7334d1702e..5313a4f46e 100644 --- a/source/client/src/clientEnv.c +++ b/source/client/src/clientEnv.c @@ -976,6 +976,7 @@ void taos_init_imp(void) { tscError("failed to init conv"); return; } + ENV_ERR_RET(tzInit(), "failed to init timezone"); ENV_ERR_RET(monitorInit(), "failed to init monitor"); ENV_ERR_RET(rpcInit(), "failed to init rpc"); diff --git a/source/client/src/clientImpl.c b/source/client/src/clientImpl.c index 18298c5a20..e5e304bc79 100644 --- a/source/client/src/clientImpl.c +++ b/source/client/src/clientImpl.c @@ -333,7 +333,7 @@ int32_t parseSql(SRequestObj* pRequest, bool topicQuery, SQuery** pQuery, SStmtC int32_t execLocalCmd(SRequestObj* pRequest, SQuery* pQuery) { SRetrieveTableRsp* pRsp = NULL; int8_t biMode = atomic_load_8(&pRequest->pTscObj->biMode); - int32_t code = qExecCommand(&pRequest->pTscObj->id, pRequest->pTscObj->sysInfo, pQuery->pRoot, &pRsp, biMode); + int32_t code = qExecCommand(&pRequest->pTscObj->id, pRequest->pTscObj->sysInfo, pQuery->pRoot, &pRsp, biMode, pRequest->pTscObj->optionInfo.charsetCxt); if (TSDB_CODE_SUCCESS == code && NULL != pRsp) { code = setQueryResultFromRsp(&pRequest->body.resInfo, pRsp, pRequest->body.resInfo.convertUcs4); } @@ -371,7 +371,7 @@ void asyncExecLocalCmd(SRequestObj* pRequest, SQuery* pQuery) { } int32_t code = qExecCommand(&pRequest->pTscObj->id, pRequest->pTscObj->sysInfo, pQuery->pRoot, &pRsp, - atomic_load_8(&pRequest->pTscObj->biMode)); + atomic_load_8(&pRequest->pTscObj->biMode), pRequest->pTscObj->optionInfo.charsetCxt); if (TSDB_CODE_SUCCESS == code && NULL != pRsp) { code = setQueryResultFromRsp(&pRequest->body.resInfo, pRsp, pRequest->body.resInfo.convertUcs4); } @@ -509,6 +509,7 @@ int32_t getPlan(SRequestObj* pRequest, SQuery* pQuery, SQueryPlan** pPlan, SArra .pMsg = pRequest->msgBuf, .msgLen = ERROR_MSG_BUF_DEFAULT_SIZE, .pUser = pRequest->pTscObj->user, + .timezone = pRequest->pTscObj->optionInfo.timezone, .sysInfo = pRequest->pTscObj->sysInfo}; return qCreateQueryPlan(&cxt, pPlan, pNodeList); @@ -1363,6 +1364,7 @@ static int32_t asyncExecSchQuery(SRequestObj* pRequest, SQuery* pQuery, SMetaDat .msgLen = ERROR_MSG_BUF_DEFAULT_SIZE, .pUser = pRequest->pTscObj->user, .sysInfo = pRequest->pTscObj->sysInfo, + .timezone = pRequest->pTscObj->optionInfo.timezone, .allocatorId = pRequest->allocatorRefId}; if (TSDB_CODE_SUCCESS == code) { code = qCreateQueryPlan(&cxt, &pDag, pMnodeList); diff --git a/source/client/src/clientMain.c b/source/client/src/clientMain.c index a6dba45357..5f106327f2 100644 --- a/source/client/src/clientMain.c +++ b/source/client/src/clientMain.c @@ -46,7 +46,6 @@ int taos_options(TSDB_OPTION option, const void *arg, ...) { for (int i = 1; atomic_val_compare_exchange_32(&lock, 0, 1) != 0; ++i) { if (i % 1000 == 0) { - tscInfo("haven't acquire lock after spin %d times.", i); (void)sched_yield(); } } @@ -62,32 +61,27 @@ static void freeTz(void *p){ tzfree(tz); } +int32_t tzInit(){ + pTimezoneMap = taosHashInit(0, MurmurHash3_32, false, HASH_ENTRY_LOCK); + if (pTimezoneMap == NULL) { + return terrno; + } + taosHashSetFreeFp(pTimezoneMap, freeTz); + + pTimezoneNameMap = taosHashInit(0, taosIntHash_64, false, HASH_ENTRY_LOCK); + if (pTimezoneNameMap == NULL) { + return terrno; + } + return 0; +} + +void tzCleanup(){ + taosHashCleanup(pTimezoneMap); + taosHashCleanup(pTimezoneNameMap); +} + static timezone_t setConnnectionTz(const char* val){ timezone_t tz = NULL; - static int32_t lock_c = 0; - - for (int i = 1; atomic_val_compare_exchange_32(&lock_c, 0, 1) != 0; ++i) { - if (i % 1000 == 0) { - tscInfo("haven't acquire lock after spin %d times.", i); - (void)sched_yield(); - } - } - - if (pTimezoneMap == NULL){ - pTimezoneMap = taosHashInit(0, MurmurHash3_32, false, HASH_ENTRY_LOCK); - if (pTimezoneMap == NULL) { - goto END; - } - taosHashSetFreeFp(pTimezoneMap, freeTz); - } - - if (pTimezoneNameMap == NULL){ - pTimezoneNameMap = taosHashInit(0, taosIntHash_64, false, HASH_ENTRY_LOCK); - if (pTimezoneNameMap == NULL) { - goto END; - } - } - timezone_t *tmp = taosHashGet(pTimezoneMap, val, strlen(val)); if (tmp != NULL && *tmp != NULL){ tz = *tmp; @@ -107,8 +101,10 @@ static timezone_t setConnnectionTz(const char* val){ } int32_t code = taosHashPut(pTimezoneMap, val, strlen(val), &tz, sizeof(timezone_t)); if (code != 0){ + tscError("%s put timezone to tz map error:%d", __func__, code); tzfree(tz); tz = NULL; + goto END; } time_t tx1 = taosGetTimestampSec(); @@ -120,7 +116,6 @@ static timezone_t setConnnectionTz(const char* val){ } END: - atomic_store_32(&lock_c, 0); return tz; } #endif @@ -242,6 +237,7 @@ void taos_cleanup(void) { tscWarn("failed to cleanup task queue"); } + tzCleanup(); tmqMgmtClose(); int32_t id = clientReqRefPool; diff --git a/source/common/src/ttime.c b/source/common/src/ttime.c index 0ce5e80b29..9746fea034 100644 --- a/source/common/src/ttime.c +++ b/source/common/src/ttime.c @@ -751,7 +751,7 @@ int64_t taosTimeTruncate(int64_t ts, const SInterval* pInterval) { start /= (int64_t)(TSDB_TICK_PER_SECOND(precision)); struct tm tm; time_t tt = (time_t)start; - if (taosLocalTime(&tt, &tm, NULL, 0, NULL) == NULL){ + if (taosLocalTime(&tt, &tm, NULL, 0, pInterval->timezone) == NULL){ uError("%s failed to convert time to local time, code:%d", __FUNCTION__, errno); return ts; } @@ -770,7 +770,7 @@ int64_t taosTimeTruncate(int64_t ts, const SInterval* pInterval) { tm.tm_mon = mon % 12; } - tt = taosMktime(&tm, NULL); + tt = taosMktime(&tm, pInterval->timezone); if (tt == -1){ uError("%s failed to convert local time to time, code:%d", __FUNCTION__, errno); return ts; @@ -789,12 +789,12 @@ int64_t taosTimeTruncate(int64_t ts, const SInterval* pInterval) { start = news; if (news <= ts) { int64_t prev = news; - int64_t newe = taosTimeAdd(news, pInterval->interval, pInterval->intervalUnit, precision, NULL) - 1; + int64_t newe = taosTimeAdd(news, pInterval->interval, pInterval->intervalUnit, precision, pInterval->timezone) - 1; if (newe < ts) { // move towards the greater endpoint while (newe < ts && news < ts) { news += pInterval->sliding; - newe = taosTimeAdd(news, pInterval->interval, pInterval->intervalUnit, precision, NULL) - 1; + newe = taosTimeAdd(news, pInterval->interval, pInterval->intervalUnit, precision, pInterval->timezone) - 1; } prev = news; @@ -802,7 +802,7 @@ int64_t taosTimeTruncate(int64_t ts, const SInterval* pInterval) { while (newe >= ts) { prev = news; news -= pInterval->sliding; - newe = taosTimeAdd(news, pInterval->interval, pInterval->intervalUnit, precision, NULL) - 1; + newe = taosTimeAdd(news, pInterval->interval, pInterval->intervalUnit, precision, pInterval->timezone) - 1; } } @@ -824,8 +824,6 @@ int64_t taosTimeTruncate(int64_t ts, const SInterval* pInterval) { // see // https://docs.microsoft.com/en-us/cpp/c-runtime-library/daylight-dstbias-timezone-and-tzname?view=vs-2019 int64_t timezone = _timezone; - int32_t daylight = _daylight; - char** tzname = _tzname; #endif start += (int64_t)(timezone * TSDB_TICK_PER_SECOND(precision)); @@ -835,7 +833,7 @@ int64_t taosTimeTruncate(int64_t ts, const SInterval* pInterval) { // not enough time range if (start < 0 || INT64_MAX - start > pInterval->interval - 1) { - end = taosTimeAdd(start, pInterval->interval, pInterval->intervalUnit, precision, NULL) - 1; + end = taosTimeAdd(start, pInterval->interval, pInterval->intervalUnit, precision, pInterval->timezone) - 1; while (end < ts) { // move forward to the correct time window start += pInterval->sliding; @@ -854,15 +852,15 @@ int64_t taosTimeTruncate(int64_t ts, const SInterval* pInterval) { if (pInterval->offset > 0) { // try to move current window to the left-hande-side, due to the offset effect. - int64_t newe = taosTimeAdd(start, pInterval->interval, pInterval->intervalUnit, precision, NULL) - 1; + int64_t newe = taosTimeAdd(start, pInterval->interval, pInterval->intervalUnit, precision, pInterval->timezone) - 1; int64_t slidingStart = start; while (newe >= ts) { start = slidingStart; - slidingStart = taosTimeAdd(slidingStart, -pInterval->sliding, pInterval->slidingUnit, precision, NULL); - int64_t news = taosTimeAdd(slidingStart, pInterval->offset, pInterval->offsetUnit, precision, NULL); - newe = taosTimeAdd(news, pInterval->interval, pInterval->intervalUnit, precision, NULL) - 1; + slidingStart = taosTimeAdd(slidingStart, -pInterval->sliding, pInterval->slidingUnit, precision, pInterval->timezone); + int64_t news = taosTimeAdd(slidingStart, pInterval->offset, pInterval->offsetUnit, precision, pInterval->timezone); + newe = taosTimeAdd(news, pInterval->interval, pInterval->intervalUnit, precision, pInterval->timezone) - 1; } - start = taosTimeAdd(start, pInterval->offset, pInterval->offsetUnit, precision, NULL); + start = taosTimeAdd(start, pInterval->offset, pInterval->offsetUnit, precision, pInterval->timezone); } return start; @@ -870,7 +868,7 @@ int64_t taosTimeTruncate(int64_t ts, const SInterval* pInterval) { // used together with taosTimeTruncate. when offset is great than zero, slide-start/slide-end is the anchor point int64_t taosTimeGetIntervalEnd(int64_t intervalStart, const SInterval* pInterval) { - return taosTimeAdd(intervalStart, pInterval->interval, pInterval->intervalUnit, pInterval->precision, NULL) - 1; + return taosTimeAdd(intervalStart, pInterval->interval, pInterval->intervalUnit, pInterval->precision, pInterval->timezone) - 1; } void calcIntervalAutoOffset(SInterval* interval) { @@ -889,7 +887,7 @@ void calcIntervalAutoOffset(SInterval* interval) { TSKEY news = start; while (news <= skey) { start = news; - news = taosTimeAdd(start, interval->sliding, interval->slidingUnit, interval->precision, NULL); + news = taosTimeAdd(start, interval->sliding, interval->slidingUnit, interval->precision, interval->timezone); if (news < start) { // overflow happens uError("%s failed and skip, skey [%" PRId64 "], inter[%" PRId64 "(%c)], slid[%" PRId64 "(%c)], precision[%d]", diff --git a/source/libs/command/src/command.c b/source/libs/command/src/command.c index b86621738d..88c3d7307b 100644 --- a/source/libs/command/src/command.c +++ b/source/libs/command/src/command.c @@ -573,7 +573,7 @@ static void appendTagNameFields(char* buf, int32_t* len, STableCfg* pCfg) { } } -static int32_t appendTagValues(char* buf, int32_t* len, STableCfg* pCfg) { +static int32_t appendTagValues(char* buf, int32_t* len, STableCfg* pCfg, void* charsetCxt) { int32_t code = TSDB_CODE_SUCCESS; SArray* pTagVals = NULL; STag* pTag = (STag*)pCfg->pTags; @@ -585,7 +585,7 @@ static int32_t appendTagValues(char* buf, int32_t* len, STableCfg* pCfg) { if (tTagIsJson(pTag)) { char* pJson = NULL; - parseTagDatatoJson(pTag, &pJson, NULL); + parseTagDatatoJson(pTag, &pJson, charsetCxt); if (NULL == pJson) { qError("failed to parse tag to json, pJson is NULL"); return terrno; @@ -726,7 +726,7 @@ static void appendTableOptions(char* buf, int32_t* len, SDbCfgInfo* pDbCfg, STab } } -static int32_t setCreateTBResultIntoDataBlock(SSDataBlock* pBlock, SDbCfgInfo* pDbCfg, char* tbName, STableCfg* pCfg) { +static int32_t setCreateTBResultIntoDataBlock(SSDataBlock* pBlock, SDbCfgInfo* pDbCfg, char* tbName, STableCfg* pCfg, void* charsetCxt) { int32_t code = TSDB_CODE_SUCCESS; QRY_ERR_RET(blockDataEnsureCapacity(pBlock, 1)); pBlock->info.rows = 1; @@ -760,7 +760,7 @@ static int32_t setCreateTBResultIntoDataBlock(SSDataBlock* pBlock, SDbCfgInfo* p appendTagNameFields(buf2, &len, pCfg); len += tsnprintf(buf2 + VARSTR_HEADER_SIZE + len, SHOW_CREATE_TB_RESULT_FIELD2_LEN - (VARSTR_HEADER_SIZE + len), ") TAGS ("); - code = appendTagValues(buf2, &len, pCfg); + code = appendTagValues(buf2, &len, pCfg, charsetCxt); TAOS_CHECK_ERRNO(code); len += snprintf(buf2 + VARSTR_HEADER_SIZE + len, SHOW_CREATE_TB_RESULT_FIELD2_LEN - (VARSTR_HEADER_SIZE + len), ")"); @@ -817,11 +817,11 @@ static int32_t setCreateViewResultIntoDataBlock(SSDataBlock* pBlock, SShowCreate return code; } -static int32_t execShowCreateTable(SShowCreateTableStmt* pStmt, SRetrieveTableRsp** pRsp) { +static int32_t execShowCreateTable(SShowCreateTableStmt* pStmt, SRetrieveTableRsp** pRsp, void* charsetCxt) { SSDataBlock* pBlock = NULL; int32_t code = buildCreateTbResultDataBlock(&pBlock); if (TSDB_CODE_SUCCESS == code) { - code = setCreateTBResultIntoDataBlock(pBlock, pStmt->pDbCfg, pStmt->tableName, pStmt->pTableCfg); + code = setCreateTBResultIntoDataBlock(pBlock, pStmt->pDbCfg, pStmt->tableName, pStmt->pTableCfg, charsetCxt); } if (TSDB_CODE_SUCCESS == code) { code = buildRetrieveTableRsp(pBlock, SHOW_CREATE_TB_RESULT_COLS, pRsp); @@ -830,14 +830,14 @@ static int32_t execShowCreateTable(SShowCreateTableStmt* pStmt, SRetrieveTableRs return code; } -static int32_t execShowCreateSTable(SShowCreateTableStmt* pStmt, SRetrieveTableRsp** pRsp) { +static int32_t execShowCreateSTable(SShowCreateTableStmt* pStmt, SRetrieveTableRsp** pRsp, void* charsetCxt) { STableCfg* pCfg = (STableCfg*)pStmt->pTableCfg; if (TSDB_SUPER_TABLE != pCfg->tableType) { terrno = TSDB_CODE_TSC_NOT_STABLE_ERROR; return terrno; } - return execShowCreateTable(pStmt, pRsp); + return execShowCreateTable(pStmt, pRsp, charsetCxt); } static int32_t execAlterCmd(char* cmd, char* value, bool* processed) { @@ -1058,7 +1058,7 @@ static int32_t execShowCreateView(SShowCreateViewStmt* pStmt, SRetrieveTableRsp* return code; } -int32_t qExecCommand(int64_t* pConnId, bool sysInfoUser, SNode* pStmt, SRetrieveTableRsp** pRsp, int8_t biMode) { +int32_t qExecCommand(int64_t* pConnId, bool sysInfoUser, SNode* pStmt, SRetrieveTableRsp** pRsp, int8_t biMode, void* charsetCxt) { switch (nodeType(pStmt)) { case QUERY_NODE_DESCRIBE_STMT: return execDescribe(sysInfoUser, pStmt, pRsp, biMode); @@ -1067,9 +1067,9 @@ int32_t qExecCommand(int64_t* pConnId, bool sysInfoUser, SNode* pStmt, SRetrieve case QUERY_NODE_SHOW_CREATE_DATABASE_STMT: return execShowCreateDatabase((SShowCreateDatabaseStmt*)pStmt, pRsp); case QUERY_NODE_SHOW_CREATE_TABLE_STMT: - return execShowCreateTable((SShowCreateTableStmt*)pStmt, pRsp); + return execShowCreateTable((SShowCreateTableStmt*)pStmt, pRsp, charsetCxt); case QUERY_NODE_SHOW_CREATE_STABLE_STMT: - return execShowCreateSTable((SShowCreateTableStmt*)pStmt, pRsp); + return execShowCreateSTable((SShowCreateTableStmt*)pStmt, pRsp, charsetCxt); case QUERY_NODE_SHOW_CREATE_VIEW_STMT: return execShowCreateView((SShowCreateViewStmt*)pStmt, pRsp); case QUERY_NODE_ALTER_LOCAL_STMT: diff --git a/source/libs/parser/src/parAstCreater.c b/source/libs/parser/src/parAstCreater.c index f8d77be75a..022dcea462 100644 --- a/source/libs/parser/src/parAstCreater.c +++ b/source/libs/parser/src/parAstCreater.c @@ -1411,6 +1411,7 @@ SNode* createIntervalWindowNode(SAstCreateContext* pCxt, SNode* pInterval, SNode interval->pSliding = pSliding; interval->pFill = pFill; interval->timeRange = TSWINDOW_INITIALIZER; + interval->timezone = pCxt->pQueryCxt->timezone; return (SNode*)interval; _err: nodesDestroyNode((SNode*)interval); diff --git a/source/libs/parser/src/parTranslater.c b/source/libs/parser/src/parTranslater.c index 3896a76877..035d114252 100755 --- a/source/libs/parser/src/parTranslater.c +++ b/source/libs/parser/src/parTranslater.c @@ -5935,6 +5935,7 @@ void tryCalcIntervalAutoOffset(SIntervalWindowNode *pInterval) { .slidingUnit = (pSliding != NULL) ? pSliding->unit : pInter->unit, .offset = pOffset->datum.i, .precision = precision, + .timezone = pInterval->timezone, .timeRange = pInterval->timeRange}; /** @@ -10108,7 +10109,7 @@ static int32_t translateCreateSmaIndex(STranslateContext* pCxt, SCreateIndexStmt return code; } -int32_t createIntervalFromCreateSmaIndexStmt(SCreateIndexStmt* pStmt, SInterval* pInterval) { +int32_t createIntervalFromCreateSmaIndexStmt(SCreateIndexStmt* pStmt, SInterval* pInterval, void* timezone) { pInterval->interval = ((SValueNode*)pStmt->pOptions->pInterval)->datum.i; pInterval->intervalUnit = ((SValueNode*)pStmt->pOptions->pInterval)->unit; pInterval->offset = NULL != pStmt->pOptions->pOffset ? ((SValueNode*)pStmt->pOptions->pOffset)->datum.i : 0; @@ -10121,6 +10122,7 @@ int32_t createIntervalFromCreateSmaIndexStmt(SCreateIndexStmt* pStmt, SInterval* parserError("%s failed for invalid interval offset %" PRId64, __func__, pInterval->offset); return TSDB_CODE_INVALID_PARA; } + pInterval->timezone = timezone; return TSDB_CODE_SUCCESS; } @@ -10132,7 +10134,7 @@ int32_t translatePostCreateSmaIndex(SParseContext* pParseCxt, SQuery* pQuery, SS STranslateContext pCxt = {0}; code = initTranslateContext(pParseCxt, NULL, &pCxt); if (TSDB_CODE_SUCCESS == code) { - code = createIntervalFromCreateSmaIndexStmt(pStmt, &interval); + code = createIntervalFromCreateSmaIndexStmt(pStmt, &interval, pParseCxt->timezone); } if (TSDB_CODE_SUCCESS == code) { @@ -11973,6 +11975,7 @@ static int32_t buildIntervalForCreateStream(SCreateStreamStmt* pStmt, SInterval* (NULL != pWindow->pSliding ? ((SValueNode*)pWindow->pSliding)->unit : pInterval->intervalUnit); pInterval->precision = ((SColumnNode*)pWindow->pCol)->node.resType.precision; pInterval->timeRange = pWindow->timeRange; + pInterval->timezone = pWindow->timezone; return code; } @@ -12022,7 +12025,7 @@ int32_t translatePostCreateStream(SParseContext* pParseCxt, SQuery* pQuery, SSDa if (TSDB_CODE_SUCCESS == code) { if (interval.interval > 0) { pStmt->pReq->lastTs = taosTimeAdd(taosTimeTruncate(lastTs, &interval), interval.interval, interval.intervalUnit, - interval.precision, NULL); + interval.precision, pParseCxt->timezone); } else { pStmt->pReq->lastTs = lastTs + 1; // start key of the next time window } @@ -12893,7 +12896,7 @@ static int32_t translateCreateTSMA(STranslateContext* pCxt, SCreateTSMAStmt* pSt return code; } -static int32_t buildIntervalForCreateTSMA(SCreateTSMAStmt* pStmt, SInterval* pInterval) { +static int32_t buildIntervalForCreateTSMA(SCreateTSMAStmt* pStmt, SInterval* pInterval, void* timezone) { int32_t code = TSDB_CODE_SUCCESS; pInterval->interval = ((SValueNode*)pStmt->pOptions->pInterval)->datum.i; pInterval->intervalUnit = ((SValueNode*)pStmt->pOptions->pInterval)->unit; @@ -12901,6 +12904,7 @@ static int32_t buildIntervalForCreateTSMA(SCreateTSMAStmt* pStmt, SInterval* pIn pInterval->sliding = pInterval->interval; pInterval->slidingUnit = pInterval->intervalUnit; pInterval->precision = pStmt->pOptions->tsPrecision; + pInterval->timezone = timezone; return code; } @@ -12912,7 +12916,7 @@ int32_t translatePostCreateTSMA(SParseContext* pParseCxt, SQuery* pQuery, SSData int32_t code = initTranslateContext(pParseCxt, NULL, &cxt); if (TSDB_CODE_SUCCESS == code) { - code = buildIntervalForCreateTSMA(pStmt, &interval); + code = buildIntervalForCreateTSMA(pStmt, &interval, pParseCxt->timezone); } if (TSDB_CODE_SUCCESS == code) { @@ -12922,7 +12926,7 @@ int32_t translatePostCreateTSMA(SParseContext* pParseCxt, SQuery* pQuery, SSData if (TSDB_CODE_SUCCESS == code) { if (interval.interval > 0) { pStmt->pReq->lastTs = taosTimeAdd(taosTimeTruncate(lastTs, &interval), interval.interval, interval.intervalUnit, - interval.precision, NULL); + interval.precision, pParseCxt->timezone); } else { pStmt->pReq->lastTs = lastTs + 1; // start key of the next time window } diff --git a/source/libs/planner/src/planOptimizer.c b/source/libs/planner/src/planOptimizer.c index 4a8df98b8b..8bcd2a9619 100644 --- a/source/libs/planner/src/planOptimizer.c +++ b/source/libs/planner/src/planOptimizer.c @@ -2928,7 +2928,7 @@ static int32_t smaIndexOptCreateSmaScan(SScanLogicNode* pScan, STableIndexInfo* return TSDB_CODE_SUCCESS; } -static bool smaIndexOptEqualInterval(SScanLogicNode* pScan, SWindowLogicNode* pWindow, STableIndexInfo* pIndex) { +static bool smaIndexOptEqualInterval(SScanLogicNode* pScan, SWindowLogicNode* pWindow, STableIndexInfo* pIndex, void* tz) { if (pWindow->interval != pIndex->interval || pWindow->intervalUnit != pIndex->intervalUnit || pWindow->offset != pIndex->offset || pWindow->sliding != pIndex->sliding || pWindow->slidingUnit != pIndex->slidingUnit) { @@ -2941,6 +2941,7 @@ static bool smaIndexOptEqualInterval(SScanLogicNode* pScan, SWindowLogicNode* pW .offsetUnit = TIME_UNIT_MILLISECOND, .sliding = pIndex->sliding, .slidingUnit = pIndex->slidingUnit, + .timezone = tz, .precision = pScan->node.precision}; return (pScan->scanRange.skey == taosTimeTruncate(pScan->scanRange.skey, &interval)) && (pScan->scanRange.ekey + 1 == taosTimeTruncate(pScan->scanRange.ekey + 1, &interval)); @@ -3051,9 +3052,9 @@ static int32_t smaIndexOptCreateSmaCols(SNodeList* pFuncs, uint64_t tableId, SNo return code; } -static int32_t smaIndexOptCouldApplyIndex(SScanLogicNode* pScan, STableIndexInfo* pIndex, SNodeList** pCols) { +static int32_t smaIndexOptCouldApplyIndex(SScanLogicNode* pScan, STableIndexInfo* pIndex, SNodeList** pCols, void* tz) { SWindowLogicNode* pWindow = (SWindowLogicNode*)pScan->node.pParent; - if (!smaIndexOptEqualInterval(pScan, pWindow, pIndex)) { + if (!smaIndexOptEqualInterval(pScan, pWindow, pIndex, tz)) { return TSDB_CODE_SUCCESS; } SNodeList* pSmaFuncs = NULL; @@ -3084,7 +3085,7 @@ static int32_t smaIndexOptimizeImpl(SOptimizeContext* pCxt, SLogicSubplan* pLogi for (int32_t i = 0; i < nindexes; ++i) { STableIndexInfo* pIndex = taosArrayGet(pScan->pSmaIndexes, i); SNodeList* pSmaCols = NULL; - code = smaIndexOptCouldApplyIndex(pScan, pIndex, &pSmaCols); + code = smaIndexOptCouldApplyIndex(pScan, pIndex, &pSmaCols, pCxt->pPlanCxt->timezone); if (TSDB_CODE_SUCCESS == code && NULL != pSmaCols) { code = smaIndexOptApplyIndex(pLogicSubplan, pScan, pIndex, pSmaCols); pCxt->optimized = true; @@ -6773,7 +6774,7 @@ typedef struct STSMAOptCtx { SNodeList** ppParentTsmaSubplans; } STSMAOptCtx; -static int32_t fillTSMAOptCtx(STSMAOptCtx* pTsmaOptCtx, SScanLogicNode* pScan) { +static int32_t fillTSMAOptCtx(STSMAOptCtx* pTsmaOptCtx, SScanLogicNode* pScan, void* tz) { int32_t code = 0; pTsmaOptCtx->pScan = pScan; pTsmaOptCtx->pParent = pScan->node.pParent; @@ -6793,7 +6794,7 @@ static int32_t fillTSMAOptCtx(STSMAOptCtx* pTsmaOptCtx, SScanLogicNode* pScan) { pTsmaOptCtx->queryInterval->sliding = pWindow->sliding; pTsmaOptCtx->queryInterval->slidingUnit = pWindow->slidingUnit; pTsmaOptCtx->queryInterval->precision = pWindow->node.precision; - pTsmaOptCtx->queryInterval->tz = taosGetLocalTimezoneOffset(); + pTsmaOptCtx->queryInterval->timezone = tz; pTsmaOptCtx->pAggFuncs = pWindow->pFuncs; pTsmaOptCtx->ppParentTsmaSubplans = &pWindow->pTsmaSubplans; } else { @@ -6984,7 +6985,7 @@ static int32_t tsmaInfoCompWithIntervalDesc(const void* pLeft, const void* pRigh return 0; } -static void tsmaOptInitIntervalFromTsma(SInterval* pInterval, const STableTSMAInfo* pTsma, int8_t precision) { +static void tsmaOptInitIntervalFromTsma(SInterval* pInterval, const STableTSMAInfo* pTsma, int8_t precision, void* tz) { pInterval->interval = pTsma->interval; pInterval->intervalUnit = pTsma->unit; pInterval->sliding = pTsma->interval; @@ -6992,15 +6993,16 @@ static void tsmaOptInitIntervalFromTsma(SInterval* pInterval, const STableTSMAIn pInterval->offset = 0; pInterval->offsetUnit = pTsma->unit; pInterval->precision = precision; + pInterval->timezone = tz; } static const STSMAOptUsefulTsma* tsmaOptFindUsefulTsma(const SArray* pUsefulTsmas, int32_t startIdx, int64_t startAlignInterval, int64_t endAlignInterval, - int8_t precision) { + int8_t precision, void* tz) { SInterval tsmaInterval; for (int32_t i = startIdx; i < pUsefulTsmas->size; ++i) { const STSMAOptUsefulTsma* pUsefulTsma = taosArrayGet(pUsefulTsmas, i); - tsmaOptInitIntervalFromTsma(&tsmaInterval, pUsefulTsma->pTsma, precision); + tsmaOptInitIntervalFromTsma(&tsmaInterval, pUsefulTsma->pTsma, precision, tz); if (taosTimeTruncate(startAlignInterval, &tsmaInterval) == startAlignInterval && taosTimeTruncate(endAlignInterval, &tsmaInterval) == endAlignInterval) { return pUsefulTsma; @@ -7009,7 +7011,7 @@ static const STSMAOptUsefulTsma* tsmaOptFindUsefulTsma(const SArray* pUsefulTsma return NULL; } -static int32_t tsmaOptSplitWindows(STSMAOptCtx* pTsmaOptCtx, const STimeWindow* pScanRange) { +static int32_t tsmaOptSplitWindows(STSMAOptCtx* pTsmaOptCtx, const STimeWindow* pScanRange, void* tz) { bool needTailWindow = false; bool isSkeyAlignedWithTsma = true, isEkeyAlignedWithTsma = true; int32_t code = 0; @@ -7025,17 +7027,17 @@ static int32_t tsmaOptSplitWindows(STSMAOptCtx* pTsmaOptCtx, const STimeWindow* if (pScanRange->ekey <= pScanRange->skey) return code; if (!pInterval) { - tsmaOptInitIntervalFromTsma(&interval, pTsma, pTsmaOptCtx->precision); + tsmaOptInitIntervalFromTsma(&interval, pTsma, pTsmaOptCtx->precision, tz); pInterval = &interval; } - tsmaOptInitIntervalFromTsma(&tsmaInterval, pTsma, pTsmaOptCtx->precision); + tsmaOptInitIntervalFromTsma(&tsmaInterval, pTsma, pTsmaOptCtx->precision, tz); // check for head windows if (pScanRange->skey != TSKEY_MIN) { startOfSkeyFirstWin = taosTimeTruncate(pScanRange->skey, pInterval); endOfSkeyFirstWin = - taosTimeAdd(startOfSkeyFirstWin, pInterval->interval, pInterval->intervalUnit, pTsmaOptCtx->precision, NULL); + taosTimeAdd(startOfSkeyFirstWin, pInterval->interval, pInterval->intervalUnit, pTsmaOptCtx->precision, tz); isSkeyAlignedWithTsma = taosTimeTruncate(pScanRange->skey, &tsmaInterval) == pScanRange->skey; } else { endOfSkeyFirstWin = TSKEY_MIN; @@ -7045,7 +7047,7 @@ static int32_t tsmaOptSplitWindows(STSMAOptCtx* pTsmaOptCtx, const STimeWindow* if (pScanRange->ekey != TSKEY_MAX) { startOfEkeyFirstWin = taosTimeTruncate(pScanRange->ekey, pInterval); endOfEkeyFirstWin = - taosTimeAdd(startOfEkeyFirstWin, pInterval->interval, pInterval->intervalUnit, pTsmaOptCtx->precision, NULL); + taosTimeAdd(startOfEkeyFirstWin, pInterval->interval, pInterval->intervalUnit, pTsmaOptCtx->precision, tz); isEkeyAlignedWithTsma = taosTimeTruncate(pScanRange->ekey + 1, &tsmaInterval) == (pScanRange->ekey + 1); if (startOfEkeyFirstWin > startOfSkeyFirstWin) { needTailWindow = true; @@ -7056,9 +7058,9 @@ static int32_t tsmaOptSplitWindows(STSMAOptCtx* pTsmaOptCtx, const STimeWindow* if (!isSkeyAlignedWithTsma) { scanRange.ekey = TMIN( scanRange.ekey, - taosTimeAdd(startOfSkeyFirstWin, pInterval->interval * 1, pInterval->intervalUnit, pTsmaOptCtx->precision, NULL) - 1); + taosTimeAdd(startOfSkeyFirstWin, pInterval->interval * 1, pInterval->intervalUnit, pTsmaOptCtx->precision, tz) - 1); const STSMAOptUsefulTsma* pTsmaFound = - tsmaOptFindUsefulTsma(pTsmaOptCtx->pUsefulTsmas, 1, scanRange.skey, scanRange.ekey + 1, pTsmaOptCtx->precision); + tsmaOptFindUsefulTsma(pTsmaOptCtx->pUsefulTsmas, 1, scanRange.skey, scanRange.ekey + 1, pTsmaOptCtx->precision, tz); STSMAOptUsefulTsma usefulTsma = {.pTsma = pTsmaFound ? pTsmaFound->pTsma : NULL, .scanRange = scanRange, .pTsmaScanCols = pTsmaFound ? pTsmaFound->pTsmaScanCols : NULL}; @@ -7083,7 +7085,7 @@ static int32_t tsmaOptSplitWindows(STSMAOptCtx* pTsmaOptCtx, const STimeWindow* scanRange.ekey = pScanRange->ekey; const STSMAOptUsefulTsma* pTsmaFound = tsmaOptFindUsefulTsma(pTsmaOptCtx->pUsefulTsmas, 1, scanRange.skey - startOfEkeyFirstWin, - scanRange.ekey + 1 - startOfEkeyFirstWin, pTsmaOptCtx->precision); + scanRange.ekey + 1 - startOfEkeyFirstWin, pTsmaOptCtx->precision, tz); STSMAOptUsefulTsma usefulTsma = {.pTsma = pTsmaFound ? pTsmaFound->pTsma : NULL, .scanRange = scanRange, .pTsmaScanCols = pTsmaFound ? pTsmaFound->pTsmaScanCols : NULL}; @@ -7522,7 +7524,7 @@ static int32_t tsmaOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan SLogicNode* pRootNode = getLogicNodeRootNode((SLogicNode*)pScan); if (getOptHint(pRootNode->pHint, HINT_SKIP_TSMA)) return code; - code = fillTSMAOptCtx(&tsmaOptCtx, pScan); + code = fillTSMAOptCtx(&tsmaOptCtx, pScan, pCxt->pPlanCxt->timezone); if (code == TSDB_CODE_SUCCESS) { // 1. extract useful tsmas code = tsmaOptFilterTsmas(&tsmaOptCtx); @@ -7531,7 +7533,7 @@ static int32_t tsmaOptimize(SOptimizeContext* pCxt, SLogicSubplan* pLogicSubplan // 2. sort useful tsmas with interval taosArraySort(tsmaOptCtx.pUsefulTsmas, tsmaInfoCompWithIntervalDesc); // 3. split windows - code = tsmaOptSplitWindows(&tsmaOptCtx, tsmaOptCtx.pTimeRange); + code = tsmaOptSplitWindows(&tsmaOptCtx, tsmaOptCtx.pTimeRange, pCxt->pPlanCxt->timezone); if (TSDB_CODE_SUCCESS == code && tsmaOptIsUsingTsmas(&tsmaOptCtx)) { // 4. create logic plan code = tsmaOptGeneratePlan(&tsmaOptCtx); diff --git a/source/os/src/osTime.c b/source/os/src/osTime.c index ff05bcef19..29cbcaeb2c 100644 --- a/source/os/src/osTime.c +++ b/source/os/src/osTime.c @@ -445,6 +445,7 @@ time_t taosMktime(struct tm *timep, timezone_t tz) { if (r == (time_t)-1) { terrno = TAOS_SYSTEM_ERROR(errno); } + timezone = -timep->tm_gmtoff; return r; #endif } @@ -533,6 +534,7 @@ struct tm *taosLocalTime(const time_t *timep, struct tm *result, char *buf, int3 if (res == NULL && buf != NULL) { (void)snprintf(buf, bufSize, "NaN"); } + timezone = -result->tm_gmtoff; return res; #endif } From befaee12a5889b930ca622144f131d5ad5473c90 Mon Sep 17 00:00:00 2001 From: Alex Duan <417921451@qq.com> Date: Mon, 9 Dec 2024 16:57:04 +0800 Subject: [PATCH 37/45] case: test passed ok --- tests/parallel_test/cases.task | 1 + .../eco-system/manager/schema_change.py | 46 ++++++++++++++++--- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/tests/parallel_test/cases.task b/tests/parallel_test/cases.task index a635ae680a..47a6b418f6 100644 --- a/tests/parallel_test/cases.task +++ b/tests/parallel_test/cases.task @@ -1082,6 +1082,7 @@ ,,n,system-test,python3 ./test.py -f 5-taos-tools/taosbenchmark/insertMix.py -N 3 ,,n,system-test,python3 ./test.py -f 5-taos-tools/taosbenchmark/stt.py -N 3 ,,n,system-test,python3 ./test.py -f eco-system/meta/database/keep_time_offset.py +,,y,system-test,./pytest.sh python3 ./test.py -f eco-system/manager/schema_change.py -N 3 -M 3 #tsim test ,,y,script,./test.sh -f tsim/query/timeline.sim diff --git a/tests/system-test/eco-system/manager/schema_change.py b/tests/system-test/eco-system/manager/schema_change.py index 400d2b100b..8eef773d48 100644 --- a/tests/system-test/eco-system/manager/schema_change.py +++ b/tests/system-test/eco-system/manager/schema_change.py @@ -133,8 +133,17 @@ class TDTestCase: if len(tags) < 10: return - sel_cols = random.sample(columns, random.randint(2,int(len(columns)/10))) - sel_tags = random.sample(tags, random.randint(1,int(len(tags)/10))) + # cmax + cmax = int(len(columns)/10) + if cmax <=2 : + cmax = 3 + sel_cols = random.sample(columns, random.randint(2, cmax)) + + # tmax + tmax = int(len(tags)/10) + if tmax <=1 : + tmax = 2 + sel_tags = random.sample(tags, random.randint(1, tmax)) field_cols = ",".join(sel_cols) field_tags = ",".join(sel_tags) @@ -217,18 +226,43 @@ class TDTestCase: #time.sleep(0.3) + # create db and stb + def create(self, db, stb, cols, tags): + # create db + sql = f"create database {db} ;" + tdSql.execute(sql) + + # switch db + tdSql.execute(f"use {db};") + + # cols + sql1 = "" + for k, v in cols.items(): + sql1 += f",{k} {v}" + # tags + sql2 = "" + for k, v in tags.items(): + if sql2 == "": + sql2 = f"{k} {v}" + else: + sql2 += f",{k} {v}" + + # create stb + sql = f"create table {db}.{stb}(ts timestamp {sql1}) tags({sql2})" + tdSql.execute(sql) + # run def run(self): # seed random.seed(int(time.time())) self.dbname = "schema_change" - # switch db - tdSql.execute(f"use {self.dbname};") + # create db + self.create(self.dbname, "meters", self.column_dict, self.tag_dict) + # change meters - self.change_schema(1000000) - + self.change_schema(1000) def stop(self): From acf6e5389473776191ec58da7232df7c6b7437cc Mon Sep 17 00:00:00 2001 From: yihaoDeng Date: Mon, 9 Dec 2024 18:00:30 +0800 Subject: [PATCH 38/45] fix:compile error in windows --- source/client/src/clientEnv.c | 3 ++- source/client/src/clientMain.c | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/source/client/src/clientEnv.c b/source/client/src/clientEnv.c index 5313a4f46e..a3ffa7f3c2 100644 --- a/source/client/src/clientEnv.c +++ b/source/client/src/clientEnv.c @@ -976,8 +976,9 @@ void taos_init_imp(void) { tscError("failed to init conv"); return; } +#ifndef WINDOWS ENV_ERR_RET(tzInit(), "failed to init timezone"); - +#endif ENV_ERR_RET(monitorInit(), "failed to init monitor"); ENV_ERR_RET(rpcInit(), "failed to init rpc"); diff --git a/source/client/src/clientMain.c b/source/client/src/clientMain.c index 5f106327f2..d80c7df2e4 100644 --- a/source/client/src/clientMain.c +++ b/source/client/src/clientMain.c @@ -237,7 +237,9 @@ void taos_cleanup(void) { tscWarn("failed to cleanup task queue"); } +#ifndef WINDOWS tzCleanup(); +#endif tmqMgmtClose(); int32_t id = clientReqRefPool; From fe40ea67a6932e0990034d257db44e9022252d72 Mon Sep 17 00:00:00 2001 From: Shengliang Guan Date: Tue, 10 Dec 2024 01:13:17 +0000 Subject: [PATCH 39/45] doc: minor changes --- docs/en/14-reference/03-taos-sql/22-meta.md | 26 +++++----- .../14-reference/03-taos-sql/02-database.md | 3 -- .../03-taos-sql/12-distinguished.md | 2 +- docs/zh/14-reference/03-taos-sql/22-meta.md | 50 +++++++++---------- 4 files changed, 39 insertions(+), 42 deletions(-) diff --git a/docs/en/14-reference/03-taos-sql/22-meta.md b/docs/en/14-reference/03-taos-sql/22-meta.md index 271f52d2c0..0d4dd8fe2e 100644 --- a/docs/en/14-reference/03-taos-sql/22-meta.md +++ b/docs/en/14-reference/03-taos-sql/22-meta.md @@ -318,7 +318,7 @@ Configuration parameters for each dnode in the system. Users with SYSINFO attrib Note: Users with SYSINFO property set to 0 cannot view this table. | # | **Column Name** | **Data Type** | **Description** | -| ---- | :-------------: | -------------- | ------------------------------------ | +|:-----|:----------------|:---------------|:-------------------------------------| | 1 | user_name | VARCHAR(24) | Username | | 2 | privilege | VARCHAR(10) | Permission description | | 3 | db_name | VARCHAR(65) | Database name | @@ -328,17 +328,17 @@ Note: Users with SYSINFO property set to 0 cannot view this table. ## INS_DISK_USAGE -| # | **Column Name** | **Data type** | **Description**| -| --- | :----------: | ------------ | ------------------------| -| 1 | db_name | VARCHAR(32) | Database name -| 2 | vgroup_id | INT | vgroup ID -| 3 | wal | BIGINT | WAL file size, in KB -| 4 | data1 | BIGINT | Data file size on primary storage, in KB -| 5 | data2 | BIGINT | Data file size on secondary storage, in KB -| 6 | data3 | BIGINT | Data file size on tertiary storage, in KB -| 7 | cache_rdb | BIGINT | Size of last/last_row files, in KB -| 8 | table_meta | BIGINT | Size of meta files, in KB -| 9 | s3 | BIGINT | Size occupied on S3, in KB -| 10 | raw_data | BIGINT | Estimated size of raw data, in KB +| # | **Column Name** | **Data type** | **Description**| +|:----|:-----------|:-----------|:--------------------| +| 1 | db_name | VARCHAR(32) | Database name | +| 2 | vgroup_id | INT | vgroup ID | +| 3 | wal | BIGINT | WAL file size, in KB | +| 4 | data1 | BIGINT | Data file size on primary storage, in KB | +| 5 | data2 | BIGINT | Data file size on secondary storage, in KB | +| 6 | data3 | BIGINT | Data file size on tertiary storage, in KB | +| 7 | cache_rdb | BIGINT | Size of last/last_row files, in KB | +| 8 | table_meta | BIGINT | Size of meta files, in KB | +| 9 | s3 | BIGINT | Size occupied on S3, in KB | +| 10 | raw_data | BIGINT | Estimated size of raw data, in KB | note: diff --git a/docs/zh/14-reference/03-taos-sql/02-database.md b/docs/zh/14-reference/03-taos-sql/02-database.md index 9856089713..62a4b7b274 100644 --- a/docs/zh/14-reference/03-taos-sql/02-database.md +++ b/docs/zh/14-reference/03-taos-sql/02-database.md @@ -232,6 +232,3 @@ SHOW db_name.disk_info; 该命令本质上等同于 `select sum(data1 + data2 + data3)/sum(raw_data), sum(data1 + data2 + data3) from information_schema.ins_disk_usage where db_name="dbname"` - - - diff --git a/docs/zh/14-reference/03-taos-sql/12-distinguished.md b/docs/zh/14-reference/03-taos-sql/12-distinguished.md index 4945e37779..ef8faff85a 100644 --- a/docs/zh/14-reference/03-taos-sql/12-distinguished.md +++ b/docs/zh/14-reference/03-taos-sql/12-distinguished.md @@ -124,7 +124,7 @@ INTERVAL 子句允许使用 AUTO 关键字来指定窗口偏移量,此时如 ```sql -- 有起始时间限制,从 '2018-10-03 14:38:05' 切分时间窗口 -SELECT COUNT(*) FROM meters WHERE _rowts >= '2018-10-03 14:38:05' INTERVAL (1m, AUTO); +SELECT COUNT(*) FROM meters WHERE _rowts >= '2018-10-03 14:38:05' INTERVAL (1m, AUTO); -- 无起始时间限制,不生效,仍以 0 为偏移量 SELECT COUNT(*) FROM meters WHERE _rowts < '2018-10-03 15:00:00' INTERVAL (1m, AUTO); diff --git a/docs/zh/14-reference/03-taos-sql/22-meta.md b/docs/zh/14-reference/03-taos-sql/22-meta.md index 74b327cbac..5708d814a7 100644 --- a/docs/zh/14-reference/03-taos-sql/22-meta.md +++ b/docs/zh/14-reference/03-taos-sql/22-meta.md @@ -303,24 +303,24 @@ TDengine 内置了一个名为 `INFORMATION_SCHEMA` 的数据库,提供对数 ## INS_STREAMS -| # | **列名** | **数据类型** | **说明** | -| --- | :----------: | ------------ | -------------------------------------------------------------------------------------------------------------------- | -| 1 | stream_name | VARCHAR(64) | 流计算名称 | -| 2 | create_time | TIMESTAMP | 创建时间 | -| 3 | sql | VARCHAR(1024) | 创建流计算时提供的 SQL 语句 | -| 4 | status | VARCHAR(20) | 流当前状态 | -| 5 | source_db | VARCHAR(64) | 源数据库 | -| 6 | target_db | VARCHAR(64) | 目的数据库 | -| 7 | target_table | VARCHAR(192) | 流计算写入的目标表 | -| 8 | watermark | BIGINT | watermark,详见 SQL 手册流式计算。需要注意,`watermark` 为 TDengine 关键字,作为列名使用时需要使用 ` 进行转义。 | -| 9 | trigger | INT | 计算结果推送模式,详见 SQL 手册流式计算。需要注意,`trigger` 为 TDengine 关键字,作为列名使用时需要使用 ` 进行转义。 | +| # | **列名** | **数据类型** | **说明** | +|:----|:-----------|:------------|:--------| +| 1 | stream_name | VARCHAR(64) | 流计算名称 | +| 2 | create_time | TIMESTAMP | 创建时间 | +| 3 | sql | VARCHAR(1024) | 创建流计算时提供的 SQL 语句 | +| 4 | status | VARCHAR(20) | 流当前状态 | +| 5 | source_db | VARCHAR(64) | 源数据库 | +| 6 | target_db | VARCHAR(64) | 目的数据库 | +| 7 | target_table | VARCHAR(192) | 流计算写入的目标表 | +| 8 | watermark | BIGINT | watermark,详见 SQL 手册流式计算。需要注意,`watermark` 为 TDengine 关键字,作为列名使用时需要使用 ` 进行转义。 | +| 9 | trigger | INT | 计算结果推送模式,详见 SQL 手册流式计算。需要注意,`trigger` 为 TDengine 关键字,作为列名使用时需要使用 ` 进行转义。| ## INS_USER_PRIVILEGES 注:SYSINFO 属性为 0 的用户不能查看此表。 -| # | **列名** | **数据类型** | **说明** | -| --- | :----------: | ------------ | -------------------------------------------------------------------------------------------------------------------- | +| # | **列名** | **数据类型** | **说明** | +|:----|:-----------|:------------|:--------| | 1 | user_name | VARCHAR(24) | 用户名 | 2 | privilege | VARCHAR(10) | 权限描述 | 3 | db_name | VARCHAR(65) | 数据库名称 @@ -329,17 +329,17 @@ TDengine 内置了一个名为 `INFORMATION_SCHEMA` 的数据库,提供对数 ## INS_DISK_USAGE -| # | **列名** | **数据类型** | **说明** | -| --- | :----------: | ------------ | -------------------------------------------------------------------------------------------------------------------- | -| 1 | db_name | VARCHAR(32) | 数据库名称 -| 2 | vgroup_id | INT | vgroup 的 ID -| 3 | wal | BIGINT | wal 文件大小, 单位为 K -| 4 | data1 | BIGINT | 一级存储上数据文件的大小,单位为KB -| 5 | data2 | BIGINT | 二级存储上数据文件的大小,单位为 KB -| 6 | data3 | BIGINT | 三级存储上数据文件的大小, 单位为KB -| 7 | cache_rdb | BIGINT | last/last_row 文件的大小,单位为KB -| 8 | table_meta | BIGINT | meta 文件的大小, 单位为KB -| 9 | s3 | BIGINT | s3 上占用的大小, 单位为KB -| 10 | raw_data | BIGINT | 预估的原始数据的大小, 单位为KB +| # | **列名** | **数据类型** | **说明** | +|:----|:-----------|:------------|:--------| +| 1 | db_name | VARCHAR(32) | 数据库名称 | +| 2 | vgroup_id | INT | vgroup 的 ID | +| 3 | wal | BIGINT | wal 文件大小, 单位为 K | +| 4 | data1 | BIGINT | 一级存储上数据文件的大小,单位为KB | +| 5 | data2 | BIGINT | 二级存储上数据文件的大小,单位为 KB | +| 6 | data3 | BIGINT | 三级存储上数据文件的大小, 单位为KB | +| 7 | cache_rdb | BIGINT | last/last_row 文件的大小,单位为KB | +| 8 | table_meta | BIGINT | meta 文件的大小, 单位为KB | +| 9 | s3 | BIGINT | s3 上占用的大小, 单位为KB | +| 10 | raw_data | BIGINT | 预估的原始数据的大小, 单位为KB | From 383e731449964652611827670229cc6330c34e51 Mon Sep 17 00:00:00 2001 From: wangmm0220 Date: Tue, 10 Dec 2024 09:44:14 +0800 Subject: [PATCH 40/45] feat:[TD-32642] add document for timezone --- docs/en/14-reference/05-connector/10-cpp.mdx | 3 ++- docs/zh/14-reference/05-connector/10-cpp.mdx | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/en/14-reference/05-connector/10-cpp.mdx b/docs/en/14-reference/05-connector/10-cpp.mdx index 7b98695bb0..7e643304c3 100644 --- a/docs/en/14-reference/05-connector/10-cpp.mdx +++ b/docs/en/14-reference/05-connector/10-cpp.mdx @@ -705,8 +705,9 @@ The basic API is used to establish database connections and provide a runtime en - The option of TSDB_OPTION_CONNECTION_CLEAR is used to reset all connection options. - After resetting the time zone and character set, using the operating system settings, the user IP and user app will be reset to empty. - The values of the connection options are all string type, and the maximum value of the user app parameter is 23, which will be truncated if exceeded; Error reported when other parameters are illegal. - - When the time zone parameter is set to empty or illegal, it defaults to UTC. + - If time zone value can not be used to find a time zone file or can not be interpreted as a direct specification, UTC is used, which is the same as the operating system time zone rules. Please refer to the tzset function description for details. You can view the current time zone of the connection by sql:select timezone(). - Time zones and character sets only work on the client side and do not affect related behaviors on the server side. + - The time zone file uses the operating system time zone file and can be updated by oneself. If there is an error when setting the time zone, please check if the time zone file or path (mac:/var/db/timezone/zoneinfo, Linux:/var/share/zoneinfo) is correct. - `char *taos_get_client_info()` - **Interface Description**: Gets client version information. diff --git a/docs/zh/14-reference/05-connector/10-cpp.mdx b/docs/zh/14-reference/05-connector/10-cpp.mdx index 64163dcdca..b434ff98a0 100644 --- a/docs/zh/14-reference/05-connector/10-cpp.mdx +++ b/docs/zh/14-reference/05-connector/10-cpp.mdx @@ -699,10 +699,11 @@ TDengine 客户端驱动的版本号与 TDengine 服务端的版本号是一一 - 该接口只对当前连接有效,不会影响其他连接。 - 同样参数多次调用该接口,以后面的为准,可以作为修改的方法。 - TSDB_OPTION_CONNECTION_CLEAR 选项用于重置所有连接选项。 - - 时区和字符集重置后,使用操作系统的设置,user ip 和 user app 重置后为空。 + - 时区和字符集重置后,使用系统的设置,user ip 和 user app 重置后为空。 - 连接选项的值都是 string 类型,user app 参数值最大为 23,超过该值会被截断;其他参数非法时报错。 - - 时区参数设置为空或非法时,默认为 UTC。 + - 时区配置找不到时区文件或者不能按照规范解释时,默认为 UTC,和操作系统时区规则相同,详见 tzset 函数说明。可通过 select timezone() 查看当前连接的时区。 - 时区和字符集只在 client 侧起作用,对于在服务端的相关行为不起作用。 + - 时区文件使用操作系统时区文件,可以自行更新操作系统时区文件。如果设置时区报错,请检查是否有时区文件或路径(mac:/var/db/timezone/zoneinfo, linux:/usr/share/zoneinfo)是否正确。 - `char *taos_get_client_info()` - **接口说明**:获取客户端版本信息。 From 82415c2a9d2b337a8f61012856e2941c9e8f5610 Mon Sep 17 00:00:00 2001 From: Shengliang Guan Date: Tue, 10 Dec 2024 14:30:20 +0800 Subject: [PATCH 41/45] doc: minor changes --- docs/zh/05-basic/03-query.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/zh/05-basic/03-query.md b/docs/zh/05-basic/03-query.md index b6f172829a..f3c9eb099e 100644 --- a/docs/zh/05-basic/03-query.md +++ b/docs/zh/05-basic/03-query.md @@ -303,13 +303,12 @@ Query OK, 5 row(s) in set (0.016812s) #### FILL 子句 -FILL 子句,用于指定某一窗口区间数据缺失的情况下的填充模式。填充模式包括以下几种: 1. 不进行填充:NONE(默认填充模式)。 -2. VALUE 填充:固定值填充,此时需要指定填充的数值。例如:FILL(VALUE, 1.23)。这里需要注意,最终填充的值受由相应列的类型决定,如 FILL(VALUE, 1.23),相应列为 INT 类型,则填充值为 1。 -3. PREV 填充:使用前一个非 NULL 值填充数据。例如:FILL(PREV)。 -4. NULL 填充:使用 NULL 填充数据。例如:FILL(NULL)。 -5. LINEAR 填充:根据前后距离最近的非 NULL 值做线性插值填充。例如:FILL(LINEAR)。 -6. NEXT 填充:使用下一个非 NULL 值填充数据。例如:FILL(NEXT)。 +2. VALUE 填充:固定值填充,此时需要指定填充的数值。例如:FILL(VALUE, 1.23)。这里需要注意,最终填充的值受由相应列的类型决定,如 FILL(VALUE, 1.23),相应列为 INT 类型,则填充值为 1, 若查询列表中有多列需要 FILL, 则需要给每一个 FILL 列指定 VALUE, 如 `SELECT _wstart, min(c1), max(c1) FROM ... FILL(VALUE, 0, 0)`, 注意, SELECT 表达式中只有包含普通列时才需要指定 FILL VALUE, 如 `_wstart`, `_wstart+1a`, `now`, `1+1` 以及使用 partition by 时的 partition key (如 tbname)都不需要指定 VALUE, 如 `timediff(last(ts), _wstart)` 则需要指定VALUE。 +3. PREV 填充:使用前一个非 NULL 值填充数据。例如:FILL(PREV)。 +4. NULL 填充:使用 NULL 填充数据。例如:FILL(NULL)。 +5. LINEAR 填充:根据前后距离最近的非 NULL 值做线性插值填充。例如:FILL(LINEAR)。 +6. NEXT 填充:使用下一个非 NULL 值填充数据。例如:FILL(NEXT)。 以上填充模式中,除了 NONE 模式默认不填充值之外,其他模式在查询的整个时间范围内如果没有数据 FILL 子句将被忽略,即不产生填充数据,查询结果为空。这种行为在部分模式(PREV、NEXT、LINEAR)下具有合理性,因为在这些模式下没有数据意味着无法产生填充数值。 From b0cf6205f3540c0812f9ea7b4083903170fdc0ff Mon Sep 17 00:00:00 2001 From: Shengliang Guan Date: Tue, 10 Dec 2024 14:34:49 +0800 Subject: [PATCH 42/45] doc: minor changes --- docs/zh/14-reference/05-connector/10-cpp.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/zh/14-reference/05-connector/10-cpp.mdx b/docs/zh/14-reference/05-connector/10-cpp.mdx index b434ff98a0..a836234af2 100644 --- a/docs/zh/14-reference/05-connector/10-cpp.mdx +++ b/docs/zh/14-reference/05-connector/10-cpp.mdx @@ -680,14 +680,14 @@ TDengine 客户端驱动的版本号与 TDengine 服务端的版本号是一一 - **接口说明**:清理运行环境,应用退出前应调用。 - `int taos_options(TSDB_OPTION option, const void * arg, ...)` - - **接口说明**:设置客户端选项,目前支持区域设置(`TSDB_OPTION_LOCALE`)、字符集设置(`TSDB_OPTION_CHARSET`)、时区设置(`TSDB_OPTION_TIMEZONE`)、配置文件路径设置(`TSDB_OPTION_CONFIGDIR`)。区域设置、字符集、时区默认为操作系统当前设置。 + - **接口说明**:设置客户端选项,支持区域设置(`TSDB_OPTION_LOCALE`)、字符集设置(`TSDB_OPTION_CHARSET`)、时区设置(`TSDB_OPTION_TIMEZONE`)、配置文件路径设置(`TSDB_OPTION_CONFIGDIR`)。区域设置、字符集、时区默认为操作系统当前设置。 - **参数说明**: - `option`:[入参] 设置项类型。 - `arg`:[入参] 设置项值。 - **返回值**:`0`:成功,`-1`:失败。 - `int taos_options_connection(TAOS *taos, TSDB_OPTION_CONNECTION option, const void *arg, ...)` - - **接口说明**:设置客户端每个连接选项,目前支持字符集设置(`TSDB_OPTION_CONNECTION_CHARSET`)、时区设置(`TSDB_OPTION_CONNECTION_TIMEZONE`)、用户 IP 设置(`TSDB_OPTION_CONNECTION_USER_IP`)、用户 APP 设置(`TSDB_OPTION_CONNECTION_USER_APP`)。 + - **接口说明**:设置客户端连接选项,目前支持字符集设置(`TSDB_OPTION_CONNECTION_CHARSET`)、时区设置(`TSDB_OPTION_CONNECTION_TIMEZONE`)、用户 IP 设置(`TSDB_OPTION_CONNECTION_USER_IP`)、用户 APP 设置(`TSDB_OPTION_CONNECTION_USER_APP`)。 - **参数说明**: - `taos`: [入参] taos_connect 返回的连接句柄。 - `option`:[入参] 设置项类型。 @@ -700,7 +700,7 @@ TDengine 客户端驱动的版本号与 TDengine 服务端的版本号是一一 - 同样参数多次调用该接口,以后面的为准,可以作为修改的方法。 - TSDB_OPTION_CONNECTION_CLEAR 选项用于重置所有连接选项。 - 时区和字符集重置后,使用系统的设置,user ip 和 user app 重置后为空。 - - 连接选项的值都是 string 类型,user app 参数值最大为 23,超过该值会被截断;其他参数非法时报错。 + - 连接选项的值都是 string 类型,user app 参数值最大长度为 23,超过该长度会被截断;其他参数非法时报错。 - 时区配置找不到时区文件或者不能按照规范解释时,默认为 UTC,和操作系统时区规则相同,详见 tzset 函数说明。可通过 select timezone() 查看当前连接的时区。 - 时区和字符集只在 client 侧起作用,对于在服务端的相关行为不起作用。 - 时区文件使用操作系统时区文件,可以自行更新操作系统时区文件。如果设置时区报错,请检查是否有时区文件或路径(mac:/var/db/timezone/zoneinfo, linux:/usr/share/zoneinfo)是否正确。 From acd87db992cdedbadfa92ccc9a468676f715a9e8 Mon Sep 17 00:00:00 2001 From: Shengliang Guan Date: Tue, 10 Dec 2024 07:15:54 +0000 Subject: [PATCH 43/45] fix: compile errors --- include/os/osLocale.h | 6 +++--- source/os/test/osEnvTests.cpp | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/include/os/osLocale.h b/include/os/osLocale.h index d7a3540887..78d5f948b0 100644 --- a/include/os/osLocale.h +++ b/include/os/osLocale.h @@ -28,9 +28,9 @@ extern "C" { #define setlocale SETLOCALE_FUNC_TAOS_FORBID #endif -char *taosCharsetReplace(char *charsetstr); -void taosGetSystemLocale(char *outLocale, char *outCharset); -int32_t taosSetSystemLocale(const char *inLocale); +char *taosCharsetReplace(char *charsetstr); +void taosGetSystemLocale(char *outLocale, char *outCharset); +int32_t taosSetSystemLocale(const char *inLocale); #ifdef __cplusplus } diff --git a/source/os/test/osEnvTests.cpp b/source/os/test/osEnvTests.cpp index d5b3cac8a6..6af6f84b68 100644 --- a/source/os/test/osEnvTests.cpp +++ b/source/os/test/osEnvTests.cpp @@ -84,9 +84,8 @@ TEST(osEnvTests, osSufficient) { ret = osTempSpaceSufficient(); EXPECT_EQ(ret, true); - osSetSystemLocale(NULL, NULL); - osSetSystemLocale(NULL, NULL); - osSetSystemLocale("1", "2"); + taosSetSystemLocale(NULL); + taosSetSystemLocale("1"); osSetProcPath(1, NULL); osSetProcPath(0, (char **)&ret); From 85af6a9224a4b9cd5b51efa7eae64a573e90eadd Mon Sep 17 00:00:00 2001 From: yihaoDeng Date: Tue, 10 Dec 2024 16:16:38 +0800 Subject: [PATCH 44/45] fix:locale error --- source/os/src/osLocale.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/os/src/osLocale.c b/source/os/src/osLocale.c index cb0a2646fd..484436fafe 100644 --- a/source/os/src/osLocale.c +++ b/source/os/src/osLocale.c @@ -87,8 +87,7 @@ int32_t taosSetSystemLocale(const char *inLocale) { return terrno; } - (void)memcpy(tsLocale, inLocale, strlen(inLocale) + 1); - + tstrncpy(tsLocale, locale, TD_LOCALE_LEN); return 0; } From 9f2d7d49d8c169d3d5c5cafc03368318cdab96b9 Mon Sep 17 00:00:00 2001 From: yihaoDeng Date: Tue, 10 Dec 2024 19:25:59 +0800 Subject: [PATCH 45/45] fix:add gitignore files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4e47039628..c506616321 100644 --- a/.gitignore +++ b/.gitignore @@ -99,6 +99,7 @@ tests/examples/JDBC/JDBCDemo/.classpath tests/examples/JDBC/JDBCDemo/.project tests/examples/JDBC/JDBCDemo/.settings/ source/libs/parser/inc/sql.* +source/os/src/timezone/ tests/script/tmqResult.txt tests/system-test/case_to_run.txt tests/develop-test/case_to_run.txt