Skip to content

File tz_support.c

File List > base > tz_support.c

Go to the documentation of this file

/*
 * Copyright 2008 Search Solution Corporation
 * Copyright 2016 CUBRID Corporation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

/*
 * tz_support.c : Timezone runtime support
 */

#include "config.h"

#include <assert.h>

#include "tz_support.h"

#include "db_date.h"
#include "environment_variable.h"
#include "chartype.h"
#include "error_manager.h"
#if !defined(WINDOWS)
#include <dlfcn.h>
#include <unistd.h>
#endif
#include "system_parameter.h"
#include "show_scan.h"
#if defined (SERVER_MODE) || defined (SA_MODE)
#include "session.h"
#endif /* defined (SERVER_MODE) || defined (SA_MODE) */
#if !defined (SERVER_MODE)
#include "authenticate.h"
#include "db.h"
#endif /* !defined (SERVER_MODE) */
#include "boot_sr.h"
#include "dbtype.h"
#if defined (SERVER_MODE)
#include "thread_manager.hpp"
#endif // SERVER_MODE
// XXX: SHOULD BE THE LAST INCLUDE HEADER
#include "memory_wrapper.hpp"

#if defined (SUPPRESS_STRLEN_WARNING)
#define strlen(s1)  ((int) strlen(s1))
#endif /* defined (SUPPRESS_STRLEN_WARNING) */

typedef struct tz_decode_info TZ_DECODE_INFO;
struct tz_decode_info
{
  TZ_REGION_TYPE type;      /* 0 : offset ; 1 : zone + DST */
  union
  {
    int offset;         /* in seconds */
    struct
    {
      unsigned int zone_id; /* geographical zone id */
      unsigned int offset_id;   /* GMT offset rule id; MAX_INT if not yet determined */
      unsigned int dst_id;  /* daylight rule id ; MAX_INT if not yet determined */
      char dst_str[TZ_DS_STRING_SIZE];

      TZ_TIMEZONE *p_timezone;
      TZ_OFFSET_RULE *p_zone_off_rule;
      TZ_DS_RULE *p_ds_rule;
    } zone;
  };
};


enum ds_search_direction
{
  FORWARD = 0,
  BACKWARD = 1
};
typedef enum ds_search_direction DS_SEARCH_DIRECTION;


#define FULL_DATE(jul_date, time_sec) ((full_date_t) jul_date * 86400ll \
                                       + (full_date_t) time_sec)
#define TIME_OFFSET(is_utc, offset) \
  ((is_utc) ? (-offset) : (offset))
#define ABS(i) ((i) >= 0 ? (i) : -(i))

#define TZ_INVALID_OFFSET ((((23 * 60) + 59) * 60) + 59)
#define TZ_MIN_OFFSET -12 * 3600
#define TZ_MAX_OFFSET 14 * 3600
#define MILLIS_IN_A_DAY (long)(86400000)    /* 24L * 60L * 60L * 1000 */
#define TZ_MASK_TZ_ID_FLAG 0xc0000000
#define TZ_BIT_SHIFT_TZ_ID_FLAG 30
#define TZ_OFFSET_MASK 0x3fffffff
#define SECONDS_IN_A_DAY 24 * 3600

static int tz_Initialized = 0;
static TZ_REGION tz_Region_system;
#if !defined(SERVER_MODE)
static TZ_REGION tz_Region_session;
#endif

/*
 * structures and functions for loading timezone data
 */
static void *tz_Lib_handle = NULL;
#if defined(WINDOWS)
static TZ_DATA
  tz_Timezone_data = { 0, NULL, 0, NULL, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, {'0'} };

/* Timezone data generated when doing an extend */
static TZ_DATA
  tz_New_timezone_data = { 0, NULL, 0, NULL, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, {'0'} };

#else
static TZ_DATA tz_Timezone_data = { 0, NULL, 0, NULL, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, {'0'} };

/* Timezone data generated when doing an extend */
static TZ_DATA tz_New_timezone_data = { 0, NULL, 0, NULL, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, {'0'} };
#endif

static const int tz_Days_of_month[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
static const int tz_Days_up_to_month[] = { 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };

#if defined (SA_MODE)
bool tz_Is_backward_compatible_timezone[ZONE_MAX];
bool tz_Compare_datetimetz_tz_id = false;
bool tz_Compare_timestamptz_tz_id = false;
#endif /* SA_MODE */

static int tz_load_library (const char *lib_file, void **handle);
static int tz_load_data_from_lib (TZ_DATA * tzd, void *lib_handle);
static bool tz_get_leapsec_support (void);
static const TZ_REGION *tz_get_invalid_tz_region (void);
static DB_DATE tz_get_current_date (void);
static int tz_str_timezone_decode (const char *tz_str, const int tz_str_size, TZ_DECODE_INFO * tz_info,
                   const char **tz_end);
static int tz_zone_info_to_str (const TZ_DECODE_INFO * tz_info, char *tz_str, const int tz_str_size);
static void tz_encode_tz_id (const TZ_DECODE_INFO * tz_info, TZ_ID * tz_id);
static void tz_encode_tz_region (const TZ_DECODE_INFO * tz_info, TZ_REGION * tz_region);
static void tz_decode_tz_id (const TZ_ID * tz_id, const bool is_full_decode, TZ_DECODE_INFO * tz_info);
static void tz_decode_tz_region (const TZ_REGION * tz_region, TZ_DECODE_INFO * tz_info);
static int tz_fast_find_ds_rule (const TZ_DATA * tzd, const TZ_DS_RULESET * ds_ruleset, const int src_julian_date,
                 const int src_year, const int src_month, int *ds_rule_id);
static bool tz_check_ds_match_string (const TZ_OFFSET_RULE * off_rule, const TZ_DS_RULE * ds_rule,
                      const char *ds_string, const char *default_abrev);
static int tz_datetime_utc_conv (const DB_DATETIME * src_dt, TZ_DECODE_INFO * tz_info, bool src_is_utc,
                 bool only_tz_adjust, DB_DATETIME * dest_dt);
static int tz_conv_tz_datetime_w_zone_info (const DB_DATETIME * src_dt, const TZ_DECODE_INFO * src_zone_info_in,
                        const TZ_DECODE_INFO * dest_zone_info_in, DB_DATETIME * dest_dt,
                        TZ_DECODE_INFO * src_zone_info_out, TZ_DECODE_INFO * dest_zone_info_out);
static int tz_print_tz_offset (char *result, int tz_offset);
static int starts_with (const char *prefix, const char *str);
static int tz_get_zone_id_by_name (const char *name, const int name_size);
static void tz_timestamp_decode_leap_sec_adj (int timestamp, int *yearp, int *monthsp, int *dayp, int *hoursp,
                          int *minutesp, int *secondsp);
static int tz_offset (const bool src_is_utc, const TZ_TIME_TYPE until_time_type, const int gmt_offset_sec,
              const int ds_save_time);
static int get_date_diff_from_ds_rule (const int src_julian_date, const int src_time_sec, const TZ_DS_RULE * ds_rule,
                       const DS_SEARCH_DIRECTION direction, full_date_t * date_diff);
static int get_closest_ds_rule (const int src_julian_date, const int src_time_sec, const TZ_DS_RULESET * ds_ruleset,
                const TZ_DATA * tzd, const DS_SEARCH_DIRECTION direction);
static int get_saving_time_from_offset_rule (const TZ_OFFSET_RULE * offset_rule, const TZ_DATA * tzd, int *save_time);
static bool is_in_overlap_interval (const TZ_TIME_TYPE time_type, const full_date_t offset_rule_diff,
                    const full_date_t gmt_diff, const int save_time_diff);
static int get_year_to_apply_rule (const int src_year, const TZ_DS_RULE * ds_rule);
static int set_new_zone_id (TZ_DECODE_INFO * tz_info);
#if defined (LINUX)
static int find_timezone_from_clock (char *timezone_name, int buf_len);
static int find_timezone_from_localtime (char *timezone_name, int buf_len);
#endif
#if defined(WINDOWS)
static int tz_get_iana_zone_id_by_windows_zone (const char *windows_zone_name);
#endif

#if defined(WINDOWS)
#define TZ_GET_SYM_ADDR(lib, sym) GetProcAddress((HMODULE)lib, sym)
#else
#define TZ_GET_SYM_ADDR(lib, sym) dlsym(lib, sym)
#endif

#define TZLIB_GET_ADDR(v, SYM_NAME, SYM_TYPE, lh)               \
  do {                                      \
    v = (SYM_TYPE) TZ_GET_SYM_ADDR (lh, SYM_NAME);              \
    if (v == NULL)                              \
      {                                     \
        strncpy (sym_name, (SYM_NAME), sizeof (sym_name) - 1);          \
        sym_name[sizeof (sym_name) - 1] = '\0';                 \
        goto error_loading_symbol;                      \
      }                                     \
  } while (0)

#define TZLIB_GET_VAL(v, SYM_NAME, SYM_TYPE, lh)        \
  do {                              \
    SYM_TYPE* aux;                      \
    TZLIB_GET_ADDR(aux, SYM_NAME, SYM_TYPE*, lh);       \
    v = *aux;                           \
  } while (0)

#define APPLY_NEXT_OFF_RULE()                   \
  do {                              \
    prev_off_rule = curr_off_rule;              \
    curr_off_rule = next_off_rule;              \
    next_off_rule = offset_rules[offset_rule_counter++];    \
    curr_offset_id++;                       \
  } while (0)

/*
 * tz_load_library - loads the timezone specific DLL/so
 * Returns : error code - ER_LOC_INIT if library load fails
 *          - NO_ERROR if success
 * lib_file(in)  : path to library
 * handle(out)   : handle to the loaded library
 */
static int
tz_load_library (const char *lib_file, void **handle)
{
  int err_status = NO_ERROR;
  char err_msg[512];
#if defined(WINDOWS)
  DWORD loading_err;
  char *lpMsgBuf;
  UINT error_mode = 0;
#else
  char *error;
#endif

  assert (lib_file != NULL);

#if defined(WINDOWS)
  error_mode = SetErrorMode (SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
  *handle = LoadLibrary (lib_file);
  SetErrorMode (error_mode);
  loading_err = GetLastError ();
#elif defined(_AIX)
  dlerror ();           /* Clear any existing error */
  *handle = dlopen (lib_file, RTLD_NOW | RTLD_MEMBER);
#else
  dlerror ();           /* Clear any existing error */
  *handle = dlopen (lib_file, RTLD_NOW);
#endif

  if (*handle == NULL)
    {
      err_status = ER_TZ_LOAD_ERROR;
    }

  if (err_status == ER_TZ_LOAD_ERROR)
    {
#if defined(WINDOWS)
      FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY, NULL,
             loading_err, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), (char *) &lpMsgBuf, 1,
             (va_list *) & lib_file);
      snprintf (err_msg, sizeof (err_msg) - 1,
        "Library file is invalid or not accessible.\n" " Unable to load %s !\n %s", lib_file, lpMsgBuf);
      LocalFree (lpMsgBuf);
#else
      error = dlerror ();
      snprintf_dots_truncate (err_msg, sizeof (err_msg) - 1,
                  "Library file is invalid or not accessible.\n" " Unable to load %s !\n %s", lib_file,
                  error);
#endif
      printf ("%s\n", err_msg);
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TZ_LOAD_ERROR, 1, err_msg);
    }

  return err_status;
}

/*
 * tz_load_data_from_lib() - uses the handle to load the data from the timezone shared library into tzd.
 * Returns: 0 (NO_ERROR) if success, -1 otherwise
 * tzd(out): TZ_DATA parameter where to load the data into
 * lib_handle(in): shared library/object handle
 */
static int
tz_load_data_from_lib (TZ_DATA * tzd, void *lib_handle)
{
  char sym_name[TZLIB_SYMBOL_NAME_SIZE + 1];
  char err_msg[512 + PATH_MAX];
  char *checksum;

  assert (lib_handle != NULL);
  assert (tzd != NULL);

  /* load data from the shared library */
  TZLIB_GET_VAL (tzd->country_count, "tz_country_count", int, lib_handle);
  TZLIB_GET_ADDR (tzd->countries, "tz_countries", TZ_COUNTRY *, lib_handle);

  TZLIB_GET_VAL (tzd->timezone_count, "timezone_count", int, lib_handle);
  TZLIB_GET_ADDR (tzd->timezone_names, "tz_timezone_names", char **, lib_handle);
  TZLIB_GET_ADDR (tzd->timezones, "timezones", TZ_TIMEZONE *, lib_handle);

  TZLIB_GET_VAL (tzd->offset_rule_count, "offset_rule_count", int, lib_handle);
  TZLIB_GET_ADDR (tzd->offset_rules, "offset_rules", TZ_OFFSET_RULE *, lib_handle);

  TZLIB_GET_VAL (tzd->name_count, "tz_name_count", int, lib_handle);
  TZLIB_GET_ADDR (tzd->names, "tz_names", TZ_NAME *, lib_handle);

  TZLIB_GET_VAL (tzd->ds_ruleset_count, "ds_ruleset_count", int, lib_handle);
  TZLIB_GET_ADDR (tzd->ds_rulesets, "ds_rulesets", TZ_DS_RULESET *, lib_handle);

  TZLIB_GET_VAL (tzd->ds_rule_count, "ds_rule_count", int, lib_handle);
  TZLIB_GET_ADDR (tzd->ds_rules, "ds_rules", TZ_DS_RULE *, lib_handle);

  TZLIB_GET_VAL (tzd->ds_leap_sec_count, "ds_leap_sec_count", int, lib_handle);
  TZLIB_GET_ADDR (tzd->ds_leap_sec, "ds_leap_sec", TZ_LEAP_SEC *, lib_handle);

#if defined(WINDOWS)
  TZLIB_GET_VAL (tzd->windows_iana_map_count, "windows_iana_map_count", int, lib_handle);
  TZLIB_GET_ADDR (tzd->windows_iana_map, "windows_iana_map", TZ_WINDOWS_IANA_MAP *, lib_handle);
#endif

  TZLIB_GET_ADDR (checksum, "tz_timezone_checksum", char *, lib_handle);
  strncpy (tzd->checksum, checksum, 32);
  tzd->checksum[32] = '\0';

  return NO_ERROR;

error_loading_symbol:
  snprintf (err_msg, sizeof (err_msg) - 1, "Cannot load symbol '%s' from the timezone library file!", sym_name);
  printf ("%s\n", err_msg);
  er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TZ_LOAD_ERROR, 1, err_msg);

  return ER_TZ_LOAD_ERROR;
}

/*
 * tz_load() - opens the timezone library and loads the data into the tzd parameter
 * Returns: 0 (NO_ERROR) if success, -1 otherwise
 */
int
tz_load (void)
{
  int err_status = NO_ERROR;
  char lib_file[PATH_MAX] = { 0 };

  if (tz_Initialized)
    {
      return err_status;
    }

  envvar_libdir_file (lib_file, PATH_MAX, LIB_TZ_NAME);
  err_status = tz_load_library (lib_file, &tz_Lib_handle);

  if (err_status != NO_ERROR)
    {
      goto error_exit;
    }

  err_status = tz_load_data_from_lib (&tz_Timezone_data, tz_Lib_handle);
  if (err_status != NO_ERROR)
    {
      goto error_exit;
    }

  tz_Initialized = 1;

  return NO_ERROR;

error_exit:

  if (er_errid () == NO_ERROR)
    {
      err_status = ER_TZ_LOAD_ERROR;
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TZ_LOAD_ERROR, 1, lib_file);
    }

  return err_status;
}

/*
 * tz_unload () - destroy all timezone related data
 * Returns:
 */
void
tz_unload (void)
{
  memset (&tz_Timezone_data, 0, sizeof (tz_Timezone_data));

  if (tz_Lib_handle != NULL)
    {
#if defined(WINDOWS)
      FreeLibrary ((HMODULE) tz_Lib_handle);
#else
      dlclose (tz_Lib_handle);
#endif
      tz_Lib_handle = NULL;
    }
  tz_Initialized = 0;
}

/*
 * tz_get_leapsec_support() - returns true if leap-seconds
 * are activated and false if not
 */
static bool
tz_get_leapsec_support (void)
{
  bool leapsec_support;

  leapsec_support = prm_get_bool_value (PRM_ID_TZ_LEAP_SECOND_SUPPORT);

  return leapsec_support;
}

/*
 * tz_timestamp_encode_leap_sec_adj() - returns offset in seconds for leap second adjustment
 */
DB_BIGINT
tz_timestamp_encode_leap_sec_adj (const int year_century, const int year, const int mon, const int day)
{
  DB_BIGINT t = 0;
  int len, index;
  const TZ_DATA *tzd;
  TZ_LEAP_SEC leap_second;

  tzd = tz_get_data ();

  if (tzd == NULL || tz_get_leapsec_support () == false)
    {
      return 0;
    }

  len = tzd->ds_leap_sec_count;
  index = 0;
  while (index < len)
    {
      leap_second = tzd->ds_leap_sec[index];

      if (leap_second.year > year_century + year)
    {
      break;
    }
      if (leap_second.year == year_century + year)
    {
      if ((mon < leap_second.month) || (mon == leap_second.month && day <= leap_second.day))
        {
          break;
        }
    }
      index++;
    }
  t = index;

  return t;
}

/*
 * tz_timestamp_decode_sec() - extracts from a UNIX timestamp the year, month, day, hour, minute and second
 *
 */
void
tz_timestamp_decode_sec (int timestamp, int *yearp, int *monthsp, int *dayp, int *hoursp, int *minutesp, int *secondsp)
{
  if (tz_get_leapsec_support () == false)
    {
      tz_timestamp_decode_no_leap_sec (timestamp, yearp, monthsp, dayp, hoursp, minutesp, secondsp);
    }
  else
    {
      tz_timestamp_decode_leap_sec_adj (timestamp, yearp, monthsp, dayp, hoursp, minutesp, secondsp);
    }
}

/*
 * tz_timestamp_decode_leap_sec_adj() - returns offset in seconds for leap second adjustment
 */
static void
tz_timestamp_decode_leap_sec_adj (int timestamp, int *yearp, int *monthsp, int *dayp, int *hoursp, int *minutesp,
                  int *secondsp)
{
  const int year_base = 1970;
  const int secs_per_day = 24 * 3600;
  int index, leap_cnt;
  int leap, diff, i;
  int year, months, day;
  int hours, minutes, seconds;
  const TZ_DATA *tzd;

  year = 0;
  months = 0;

  tzd = tz_get_data ();
  index = 0;

  if (tzd != NULL)
    {
      leap_cnt = tzd->ds_leap_sec_count;
    }
  else
    {
      leap_cnt = 0;
    }

  /* get number of years */
  for (;;)
    {
      int days_in_year;
      leap = 0;

      if (IS_LEAP_YEAR (year_base + year))
    {
      days_in_year = 366;
    }
      else
    {
      days_in_year = 365;
    }

      while (index < leap_cnt && tzd->ds_leap_sec[index].year == year_base + year)
    {
      index++;
      leap++;
    }

      if (timestamp - days_in_year * secs_per_day - leap < 0)
    {
      index -= leap;
      break;
    }

      timestamp -= days_in_year * secs_per_day + leap;
      year++;
    }

  /* Get the number of months */
  for (i = TZ_MON_JAN; i <= TZ_MON_DEC; i++)
    {
      int subtract = tz_Days_of_month[i] * secs_per_day;
      leap = 0;

      /* leap year and february */
      if (IS_LEAP_YEAR (year_base + year) && i == TZ_MON_FEB)
    {
      subtract += secs_per_day;
    }

      while (index < leap_cnt && tzd->ds_leap_sec[index].year == year_base + year
         && tzd->ds_leap_sec[index].month == months)
    {
      index++;
      leap++;
    }

      if (timestamp - subtract - leap < 0)
    {
      index -= leap;
      break;
    }
      timestamp -= subtract + leap;
      months++;
    }

  /* Get the number of days */
  day = timestamp / secs_per_day;
  timestamp = timestamp % secs_per_day;
  leap = 0;

  while (index < leap_cnt && tzd->ds_leap_sec[index].year == year_base + year && tzd->ds_leap_sec[index].month == months
     && tzd->ds_leap_sec[index].day <= day)
    {
      index++;
      leap++;
    }

  diff = timestamp - leap;
  if (diff < 0)
    {
      day--;
      timestamp = secs_per_day + diff;
    }
  else
    {
      timestamp = diff;
    }
  day++;

  /* Get the hours, minutes and seconds */
  hours = timestamp / 3600;
  minutes = (timestamp % 3600) / 60;
  seconds = (timestamp % 3600) % 60;

  *yearp = year + year_base;
  *monthsp = months;
  *dayp = day;
  *hoursp = hours;
  *minutesp = minutes;
  *secondsp = seconds;
}

/*
 * tz_timestamp_decode_no_leap_sec() - extracts from a UNIX timestamp the year, month, day, hour, minute
 *                     and second without taking into account leap seconds
 */
void
tz_timestamp_decode_no_leap_sec (int timestamp, int *yearp, int *monthsp, int *dayp, int *hoursp, int *minutesp,
                 int *secondsp)
{
  const int year_base = 1970;
  const int secs_per_day = 24 * 3600;
  const int days_in_year = 365;
  const int secs_in_a_year = secs_per_day * days_in_year;
  int year, month, day;
  int hours, minutes, seconds;
  int secs_until_last_year;
  int secs_until_last_month = 0;
  int days_last_month;

  /* Get approximate number of years */
  year = timestamp / (days_in_year * secs_per_day);
  secs_until_last_year =
    year * secs_in_a_year + ((year + 1) / 4) * secs_per_day - ((year + 69) / 100) * secs_per_day +
    ((year + 369) / 400) * secs_per_day;

  /* If we overestimated the number of years take back one year */
  if (timestamp - secs_until_last_year < 0)
    {
      year--;
      secs_until_last_year -= secs_in_a_year;
      if (IS_LEAP_YEAR (year_base + year))
    {
      secs_until_last_year -= secs_per_day;
    }
    }
  timestamp -= secs_until_last_year;

  /* Get approximate number of months */
  month = timestamp / (31 * secs_per_day);

  /* If we underestimated the number of months add another one */
  if (month > TZ_MON_JAN)
    {
      secs_until_last_month = tz_Days_up_to_month[month - 1] * secs_per_day;
      if (IS_LEAP_YEAR (year_base + year) && month > TZ_MON_FEB)
    {
      secs_until_last_month += secs_per_day;
    }
    }

  days_last_month = tz_Days_of_month[month] * secs_per_day;
  if (IS_LEAP_YEAR (year_base + year) && month == TZ_MON_FEB)
    {
      days_last_month += secs_per_day;
    }

  if (timestamp - secs_until_last_month - days_last_month >= 0)
    {
      secs_until_last_month += days_last_month;
      month++;
    }

  timestamp -= secs_until_last_month;

  /* Get the number of days */
  day = timestamp / secs_per_day;
  day++;
  timestamp = timestamp % secs_per_day;

  /* Get the hours, minutes and seconds */
  hours = timestamp / 3600;
  minutes = (timestamp % 3600) / 60;
  seconds = (timestamp % 3600) % 60;

  *yearp = year + year_base;
  *monthsp = month;
  *dayp = day;
  *hoursp = hours;
  *minutesp = minutes;
  *secondsp = seconds;
}

/*
 * Timezone CUBRID access functions
 */

/*
 * tz_get_data() - get a reference to the global timezone data
 *
 * Returns: NULL if timezone data is not loaded, reference to it otherwise
 */
const TZ_DATA *
tz_get_data (void)
{
  if (tz_Timezone_data.timezone_count == 0)
    {
      return NULL;
    }

  return &tz_Timezone_data;
}

/*
 * tz_set_data() - Set the new timezone data
 * Returns: void
 */
void
tz_set_data (const TZ_DATA * data)
{
  tz_Timezone_data = *data;
}

/*
 * tz_get_new_data() - get a reference to the global new timezone data
 *
 * Returns: NULL if timezone data is not loaded, reference to it otherwise
 */
const TZ_DATA *
tz_get_new_timezone_data (void)
{
  if (tz_New_timezone_data.timezone_count == 0)
    {
      return NULL;
    }

  return &tz_New_timezone_data;
}

/*
 * tz_set_new_timezone_data() - Set the new timezone data
 *
 * Returns: void
 */
void
tz_set_new_timezone_data (const TZ_DATA * data)
{
  tz_New_timezone_data = *data;
}

/*
 *  tz_get_session_local_timezone - returns session timezone
 *
 */
const char *
tz_get_session_local_timezone (void)
{
  return prm_get_string_value (PRM_ID_TIMEZONE);
}

/*
 * tz_get_system_timezone - returns server timezone
 *
 */
const char *
tz_get_system_timezone (void)
{
  return prm_get_string_value (PRM_ID_SERVER_TIMEZONE);
}

/*
 * tz_get_system_tz_region() - get the system timezone region
 */
void
tz_get_system_tz_region (TZ_REGION * tz_region)
{
  *tz_region = tz_Region_system;
}

/*
 * tz_get_session_tz_region() - get the session timezone region
 */
void
tz_get_session_tz_region (TZ_REGION * tz_region)
{
  TZ_REGION *session_tz_region;

#if defined(CS_MODE)
  session_tz_region = tz_get_client_tz_region_session ();
  *tz_region = *session_tz_region;
#else /* SERVER or SA_MODE */
  if (BO_IS_SERVER_RESTARTED ())
    {
#if defined(SERVER_MODE)
      session_tz_region = tz_get_server_tz_region_session ();
#else
      session_tz_region = tz_get_client_tz_region_session ();
#endif /* SERVER_MODE */
      if (session_tz_region != NULL)
    {
      *tz_region = *session_tz_region;
    }
      else
    {
      /* A session could not be found with this thread, use invalid region */
      *tz_region = *tz_get_invalid_tz_region ();
    }
    }
  else
    {
      *tz_region = tz_Region_system;
    }
#endif /* CS_MODE */
}

/*
 * tz_id_to_region() - converts a TZ_ID into a TZ_REGION
 */
void
tz_id_to_region (const TZ_ID * tz_id, TZ_REGION * tz_region)
{
  TZ_DECODE_INFO tz_info;

  tz_decode_tz_id (tz_id, false, &tz_info);
  tz_encode_tz_region (&tz_info, tz_region);
}

/*
 * tz_get_utc_tz_id() - returns the compressed timezone identifer for UTC
 *
 */
const TZ_ID *
tz_get_utc_tz_id (void)
{
  static const TZ_ID utc_tz_id = 0x1 << 30;

  return &utc_tz_id;
}

/*
 * tz_get_utc_tz_region() - return the timezone region for UTC
 *
 */
const TZ_REGION *
tz_get_utc_tz_region (void)
{
  static const TZ_REGION utc_region = { TZ_REGION_OFFSET, {0} };

  return &utc_region;
}

/*
 * tz_get_invalid_tz_region() - returns the generic invalid timezone region
 *
 */
static const TZ_REGION *
tz_get_invalid_tz_region (void)
{
  static const TZ_REGION invalid_region = { TZ_REGION_OFFSET, {TZ_INVALID_OFFSET} };

  return &invalid_region;
}

/*
 * tz_get_current_date() -
 *
 */
static DB_DATE
tz_get_current_date (void)
{
  DB_DATETIME datetime;
  time_t time_val;
  struct tm c_time_struct, *c_time_struct_p;

  time_val = time (NULL);
  c_time_struct_p = localtime_r (&time_val, &c_time_struct);
  if (c_time_struct_p == NULL)
    {
      db_datetime_encode (&datetime, 1, 1, 2012, 10, 30, 0, 0);
    }
  else
    {
      db_datetime_encode (&datetime, c_time_struct_p->tm_mon + 1, c_time_struct_p->tm_mday,
              c_time_struct_p->tm_year + 1900, c_time_struct_p->tm_hour, c_time_struct_p->tm_min,
              c_time_struct_p->tm_sec, 0);
    }

  return datetime.date;
}

/*
 * tz_print_tz_offset () - stores in result in the format hh:mm or hh:mm:ss the number of seconds in tz_offset,
 *             where hh is the number of hours, mm the number of minutes and ss is the number of seconds
 *                 hh:mm:ss format is used only if the number of seconds is positive
 *
 * tz_offset (in) : timezone offset represented in seconds
 * result (out) : output timezone offset
 */

static int
tz_print_tz_offset (char *result, int tz_offset)
{
  const int sign_hour_minutes = 6;
  const int seconds = 3;
  int off_hour, off_min, off_sec, out_len = 0;
  char sign = '+';

  if (tz_offset < 0)
    {
      sign = '-';
      tz_offset = -tz_offset;
    }

  off_hour = tz_offset / 3600;
  off_min = (tz_offset % 3600) / 60;
  off_sec = (tz_offset % 3600) % 60;

  out_len += sign_hour_minutes;
  if (off_sec != 0)
    {
      out_len += seconds;
    }

  if (!off_sec)
    {
      if (snprintf (result, out_len + 1, "%c%02d:%02d", sign, off_hour, off_min) < 0)
    {
      assert (false);
      return ER_FAILED;
    }
    }
  else
    {
      if (snprintf (result, out_len + 1, "%c%02d:%02d:%02d", sign, off_hour, off_min, off_sec) < 0)
    {
      assert (false);
      return ER_FAILED;
    }
    }
  (result)[out_len] = '\0';
  return NO_ERROR;
}

/*
 * tz_get_timezone_offset () - puts in result the timezone offset of tz_str timezone
 * tz_str (in) : name or offset of timezone
 * tz_size (in) : length of timezone string
 * utc_datetime (in) : the current UTC datetime
 * result (out) : the timezone offset
 */
int
tz_get_timezone_offset (const char *tz_str, int tz_size, char *result, DB_DATETIME * utc_datetime)
{
  const char *p = tz_str;
  int error = NO_ERROR;

  while (p < tz_str + tz_size && char_isspace (*p))
    {
      p++;
    }

  if (p >= tz_str + tz_size)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TZ_INVALID_TIMEZONE, 0);
      return ER_TZ_INVALID_TIMEZONE;
    }

  if (*p == '+' || *p == '-')
    {
      int seconds = 0;
      const char *zone_end;
      if (tz_str_to_seconds (p, tz_str + tz_size, &seconds, &zone_end, true) != NO_ERROR)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TZ_INVALID_TIMEZONE, 0);
      return ER_TZ_INVALID_TIMEZONE;
    }

      while (zone_end < tz_str + tz_size && char_isspace (*zone_end))
    {
      zone_end++;
    }

      if (zone_end != tz_str + tz_size)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TZ_INVALID_TIMEZONE, 0);
      return ER_TZ_INVALID_TIMEZONE;
    }

      error = tz_print_tz_offset (result, seconds);
    }
  /* Handle the main case when the timezone is a name */
  else
    {
      int zone_id;
      TZ_DECODE_INFO tzinfo;
      DB_DATETIME dest_datetime;
      int tdif;
      const char *save_poz;
      const char *end;

      save_poz = p;
      while (p < tz_str + tz_size && !char_isspace (*p))
    {
      p++;
    }
      end = p;

      while (end < tz_str + tz_size && char_isspace (*end))
    {
      end++;
    }
      if (end != tz_str + tz_size)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TZ_INVALID_TIMEZONE, 0);
      return ER_TZ_INVALID_TIMEZONE;
    }

      zone_id = tz_get_zone_id_by_name (save_poz, CAST_BUFLEN (p - save_poz));
      if (zone_id == -1)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TZ_INVALID_TIMEZONE, 0);
      return ER_TZ_INVALID_TIMEZONE;
    }

      tzinfo.type = TZ_REGION_ZONE;
      tzinfo.zone.zone_id = zone_id;
      tzinfo.zone.dst_str[0] = '\0';

      error = tz_datetime_utc_conv (utc_datetime, &tzinfo, true, false, &dest_datetime);
      if (error != NO_ERROR)
    {
      return error;
    }

      tdif =
    (int) (dest_datetime.date - utc_datetime->date) * 3600 * 24 + (int) (dest_datetime.time -
                                         utc_datetime->time) / 1000;

      error = tz_print_tz_offset (result, tdif);
    }

  return error;
}

/*
 * tz_create_session_tzid_for_datetime() - Creates a TZ_ID object for a DATETIME
 *
 * Returns: error code
 * src_dt(in): DATETIME value
 * src_is_utc(in): if true, than source DATETIME is considered in UTC, otherwise in in session timezone
 * tz_id(out): result TZ_ID
 */
int
tz_create_session_tzid_for_datetime (const DB_DATETIME * src_dt, bool src_is_utc, TZ_ID * tz_id)
{
  TZ_DECODE_INFO tz_info;
  int er_status = NO_ERROR;
  DB_DATETIME dummy_dt;
  TZ_REGION session_tz_region;

  tz_get_session_tz_region (&session_tz_region);
  tz_decode_tz_region (&session_tz_region, &tz_info);

  /* we use tz_info which only has zone_id valid to establish correct offset and dst_id according to dt value */
  er_status = tz_datetime_utc_conv (src_dt, &tz_info, src_is_utc, true, &dummy_dt);
  if (er_status != NO_ERROR)
    {
      return er_status;
    }

  tz_encode_tz_id (&tz_info, tz_id);

  return er_status;
}

/*
 * tz_create_session_tzid_for_timestamp() - Creates a TZ_ID object for a TIMESTAMP
 *
 * Returns: error code
 * src_ts(in): TIMESTAMP value
 * tz_id(out): result TZ_ID
 *
 */
int
tz_create_session_tzid_for_timestamp (const DB_UTIME * src_ts, TZ_ID * tz_id)
{
  DB_DATE date;
  DB_TIME time;
  DB_DATETIME dt;

  db_timestamp_decode_utc (src_ts, &date, &time);
  dt.date = date;
  dt.time = time * 1000;

  return tz_create_session_tzid_for_datetime (&dt, true, tz_id);
}

/*
 * tz_create_session_tzid_for_time() - Creates a TZ_ID object for a TIME
 *
 * Returns: error code
 * src_dt(in): TIME value
 * src_is_utc(in): if true, than source TIME is considered in UTC, otherwise in in session timezone
 * tz_id(out): result TZ_ID
 *
 */
int
tz_create_session_tzid_for_time (const DB_TIME * src_time, bool src_is_utc, TZ_ID * tz_id)
{
  DB_DATETIME src_dt;

  src_dt.date = tz_get_current_date ();
  src_dt.time = *src_time;

  return tz_create_session_tzid_for_datetime (&src_dt, src_is_utc, tz_id);
}

/*
 * tz_get_zone_id_by_name() - returns the ID of a timezone having the specified name or alias.
 *
 * Returns: ID of zone, or -1 if not found
 * name(in): timezone name or alias to search for (not null-terminated)
 * name_size(in): size in bytes of name
 */
static int
tz_get_zone_id_by_name (const char *name, const int name_size)
{
  const TZ_DATA *tzd;
  int name_index = -1;
  int index_bot, index_top;
  int cmp_res;

  assert (name != NULL);

  tzd = tz_get_data ();
  if (tzd == NULL)
    {
      return -1;
    }

  index_bot = 0;
  index_top = tzd->name_count - 1;

  while (index_bot <= index_top)
    {
      name_index = (index_bot + index_top) >> 1;
      cmp_res = strncmp (name, tzd->names[name_index].name, name_size);

      if (cmp_res < 0)
    {
      index_top = name_index - 1;
    }
      else if (cmp_res > 0)
    {
      index_bot = name_index + 1;
    }
      else
    {
      if (strlen (tzd->names[name_index].name) != name_size)
        {
          index_top = name_index - 1;
          continue;
        }
      return name_index;
    }
    }

  return -1;
}

/*
 * tz_str_timezone_decode () -
 *
 * Return: error code
 * tz_str(in): string containing timezone information (zone, daylight saving); not null-terminated
 * tz_str(in): string containing timezone information (zone, daylight saving); null-terminated
 * tz_info(out): object containing decoded timezone info
 * tz_end(out): pointer to end of zone information
 *
 *  Valid formats for timezone string (leading/trailing whitespaces and
 *  leading zeros are optional) :
 *    - " +08:00 "
 *    - " Europe/Berlin "
 *    - " Europe/Berlin +08:00 "
 */
static int
tz_str_timezone_decode (const char *tz_str, const int tz_str_size, TZ_DECODE_INFO * tz_info, const char **tz_end)
{
  const char *zone_str, *tz_str_end;
  const char *zone_str_end;
  int zone_id;

  tz_str_end = tz_str + tz_str_size;

  zone_str = tz_str;
  while (zone_str < tz_str_end && char_isspace (*zone_str))
    {
      zone_str++;
    }

  if (zone_str >= tz_str_end)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TZ_INVALID_TIMEZONE, 0);
      return ER_TZ_INVALID_TIMEZONE;
    }

  if (*zone_str == '+' || *zone_str == '-')
    {
      if (tz_str_to_seconds (zone_str, tz_str_end, &(tz_info->offset), &zone_str_end, true) != NO_ERROR)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TZ_INVALID_TIMEZONE, 0);
      return ER_TZ_INVALID_TIMEZONE;
    }
      tz_info->type = TZ_REGION_OFFSET;
    }
  else
    {
      const char *dst_str = NULL, *dst_str_end = NULL, *reg_str_end = NULL;
      /* zone plus optional DST */
      reg_str_end = zone_str;
      while (reg_str_end < tz_str_end && !char_isspace (*reg_str_end))
    {
      reg_str_end++;
    }

      dst_str = reg_str_end;
      while (dst_str < tz_str_end && char_isspace (*dst_str))
    {
      dst_str++;
    }

      if (dst_str < tz_str_end)
    {
      dst_str_end = dst_str;
      while (dst_str_end < tz_str_end && !char_isspace (*dst_str_end))
        {
          dst_str_end++;
        }
      zone_str_end = dst_str_end;
    }
      else
    {
      zone_str_end = dst_str;
      dst_str = NULL;
    }

      zone_id = tz_get_zone_id_by_name (zone_str, CAST_BUFLEN (reg_str_end - zone_str));
      if (zone_id == -1)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TZ_INVALID_TIMEZONE, 0);
      return ER_TZ_INVALID_TIMEZONE;
    }

      tz_info->type = TZ_REGION_ZONE;
      tz_info->zone.zone_id = zone_id;
      tz_info->zone.dst_id = TZ_DS_ID_MAX;
      tz_info->zone.offset_id = TZ_OFFSET_ID_MAX;

      if (dst_str != NULL)
    {
      if (dst_str_end - dst_str > (int) sizeof (tz_info->zone.dst_str))
        {
          er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TZ_INVALID_DST, 0);
          return ER_TZ_INVALID_TIMEZONE;
        }

      strncpy (tz_info->zone.dst_str, dst_str, dst_str_end - dst_str);
      tz_info->zone.dst_str[dst_str_end - dst_str] = '\0';
      zone_str = dst_str_end;
    }
      else
    {
      tz_info->zone.dst_str[0] = '\0';
    }
    }

  if (tz_end != NULL)
    {
      while (zone_str_end < tz_str_end && char_isspace (*zone_str_end))
    {
      zone_str_end++;
    }
      *tz_end = zone_str_end;
    }

  return NO_ERROR;
}

/*
 * tz_str_to_region ()
 *
 * Return: error code
 * tz_str(in): string containing timezone information (zone, daylight saving); not null-terminated
 * tz_str_size(in): size in characters of zone information of tz_str
 * tz_region(out): object containing timezone info
 *
 *  Valid formats for timezone string (leading/trailing white-spaces and leading zeros are optional) :
 *    - " +08:00 "
 *    - " Europe/Berlin "
 *    - " Europe/Berlin +08:00 "
 */
int
tz_str_to_region (const char *tz_str, const int tz_str_size, TZ_REGION * tz_region)
{
  const char *zone_str, *tz_str_end;
  const char *zone_str_end;
  int reg_zone_id, reg_offset;
  TZ_REGION_TYPE reg_type;

  tz_str_end = tz_str + tz_str_size;
  zone_str = tz_str;

  while (zone_str < tz_str_end && char_isspace (*zone_str))
    {
      zone_str++;
    }

  if (zone_str >= tz_str_end)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TZ_INVALID_TIMEZONE, 0);
      return ER_TZ_INVALID_TIMEZONE;
    }

  if (*zone_str == '+' || *zone_str == '-')
    {
      if (tz_str_to_seconds (zone_str, tz_str_end, &reg_offset, &zone_str_end, true) != NO_ERROR)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TZ_INVALID_TIMEZONE, 0);
      return ER_TZ_INVALID_TIMEZONE;
    }

      while (zone_str_end < tz_str_end && char_isspace (*zone_str_end))
    {
      zone_str_end++;
    }

      if (zone_str_end != tz_str_end)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TZ_INVALID_TIMEZONE, 0);
      return ER_TZ_INVALID_TIMEZONE;
    }

      reg_type = TZ_REGION_OFFSET;
    }
  else
    {
      const char *reg_str_end;

      reg_str_end = zone_str;
      while (reg_str_end < tz_str_end && !char_isspace (*reg_str_end))
    {
      reg_str_end++;
    }

      reg_zone_id = tz_get_zone_id_by_name (zone_str, CAST_BUFLEN (reg_str_end - zone_str));
      if (reg_zone_id == -1)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TZ_INVALID_TIMEZONE, 0);
      return ER_TZ_INVALID_TIMEZONE;
    }

      reg_type = TZ_REGION_ZONE;
    }

  if (tz_region != NULL)
    {
      tz_region->type = reg_type;
      if (reg_type == TZ_REGION_OFFSET)
    {
      tz_region->offset = reg_offset;
    }
      else
    {
      tz_region->zone_id = reg_zone_id;
    }
    }

  return NO_ERROR;
}

/*
 * tz_create_datetimetz () - transforms a DATETIME and timezone string (or default timezone identifier) into a DATETIME
 *               (in UTC) with timezone info, considering the source DATETIME in specified timezone
 *
 * Return: error code
 * dt(in): decoded local datetime value (as appears in the user string)
 * tz_str(in): string containing timezone information (zone, daylight saving);
 *         null-terminated, can be NULL, in which case default_tz_region is used
 * tz_size(in): size of tz_str
 * default_tz_region(in): default timezone region to apply if input string does not contain a valid zone information
 * dt_tz(out): object containing datetime value (adjusted to UTC) and timezone info
 * end_tz_str(out): pointer to end of parsed string
 *
 *  Valid formats for timezone string (leading/trailing white-spaces and leading zero are optional) :
 *    - " +08:00 "
 *    - " Europe/Berlin "
 *    - " Europe/Berlin +08:00 "
 */
int
tz_create_datetimetz (const DB_DATETIME * dt, const char *tz_str, const int tz_size,
              const TZ_REGION * default_tz_region, DB_DATETIMETZ * dt_tz, const char **end_tz_str)
{
  int err_status = NO_ERROR;
  DB_DATETIME utc_dt;
  TZ_DECODE_INFO tz_info;

  if (end_tz_str != NULL)
    {
      *end_tz_str = NULL;
    }

  if (tz_str != NULL)
    {
      err_status = tz_str_timezone_decode (tz_str, tz_size, &tz_info, end_tz_str);
      if (err_status != NO_ERROR)
    {
      goto exit;
    }
    }
  else
    {
      tz_decode_tz_region (default_tz_region, &tz_info);
    }

  err_status = tz_datetime_utc_conv (dt, &tz_info, false, false, &utc_dt);
  if (err_status != NO_ERROR)
    {
      goto exit;
    }
  tz_encode_tz_id (&tz_info, &(dt_tz->tz_id));
  dt_tz->datetime = utc_dt;

exit:
  return err_status;
}

/*
 * tz_create_timestamptz () - creates a timestamp with timezone from date and time values considering the user timezone
 *                from tz_str; if this is NULL, then default_tz_region is used
 *
 * Return: error code
 * date(in): local date
 * time(in): local time
 * tz_str(in): string containing timezone information (zone, daylight saving);
 *         null-terminated, can be NULL, in which case default_tz_region is used
 * tz_size(in): size of tz_str
 * default_tz_region(in): default timezone region to apply if input string does not contain a valid zone information
 * ts_tz(out): object containing timestamp value and timezone info
 *
 */
int
tz_create_timestamptz (const DB_DATE * date, const DB_TIME * time, const char *tz_str, const int tz_size,
               const TZ_REGION * default_tz_region, DB_TIMESTAMPTZ * ts_tz, const char **end_tz_str)
{
  int err_status = NO_ERROR;
  DB_DATETIME utc_dt, dt;
  TZ_DECODE_INFO tz_info;
  DB_DATE date_utc;
  DB_TIME time_utc;

  if (end_tz_str != NULL)
    {
      *end_tz_str = NULL;
    }

  if (tz_str != NULL)
    {
      err_status = tz_str_timezone_decode (tz_str, tz_size, &tz_info, end_tz_str);
      if (err_status != NO_ERROR)
    {
      goto exit;
    }
    }
  else
    {
      tz_decode_tz_region (default_tz_region, &tz_info);
    }

  dt.date = *date;
  dt.time = (*time) * 1000;

  err_status = tz_datetime_utc_conv (&dt, &tz_info, false, false, &utc_dt);
  if (err_status != NO_ERROR)
    {
      goto exit;
    }
  date_utc = utc_dt.date;
  time_utc = utc_dt.time / 1000;

  err_status = db_timestamp_encode_utc (&date_utc, &time_utc, &ts_tz->timestamp);
  if (err_status != NO_ERROR)
    {
      goto exit;
    }
  tz_encode_tz_id (&tz_info, &(ts_tz->tz_id));

exit:
  return err_status;
}

/*
 * tz_create_datetimetz_from_ses () - creates a datetime with timezone from a datetime using session timezone
 *
 * Return: error code
 * dt(in): decoded local datetime value (as appears in the user string)
 * dt_tz(out): object containing datetime value (adjusted to UTC) and timezone info
 *
 */
int
tz_create_datetimetz_from_ses (const DB_DATETIME * dt, DB_DATETIMETZ * dt_tz)
{
  int err_status = NO_ERROR;
  DB_DATETIME utc_dt;
  TZ_DECODE_INFO tz_info;
  TZ_REGION session_tz_region;

  tz_get_session_tz_region (&session_tz_region);
  tz_decode_tz_region (&session_tz_region, &tz_info);

  err_status = tz_datetime_utc_conv (dt, &tz_info, false, false, &utc_dt);
  if (err_status != NO_ERROR)
    {
      goto exit;
    }
  tz_encode_tz_id (&tz_info, &(dt_tz->tz_id));
  dt_tz->datetime = utc_dt;

exit:
  return err_status;
}

/*
 * tz_conv_tz_time_w_zone_name() - Converts the time_source from timezone source zone into timezone dest_zone
 *
 *
 * Return: error code
 * time_source(in): object containing source time value
 * source_zone(in): source timezone string
 * len_source(in): length of source timezone string
 * dest_zone(in): destination timezone string
 * len_dest(in): length of destination timezone string
 * time_dest(out): object containing output time value
 */
int
tz_conv_tz_time_w_zone_name (const DB_TIME * time_source, const char *source_zone, int len_source,
                 const char *dest_zone, int len_dest, DB_TIME * time_dest)
{
  int err_status = NO_ERROR;
  DB_DATETIME src_dt, dest_dt;
  TZ_REGION source, dest;

  err_status = tz_str_to_region (source_zone, len_source, &source);
  if (err_status != NO_ERROR)
    {
      return err_status;
    }

  err_status = tz_str_to_region (dest_zone, len_dest, &dest);
  if (err_status != NO_ERROR)
    {
      return err_status;
    }

  src_dt.date = tz_get_current_date ();
  src_dt.time = (*time_source) * 1000;

  err_status = tz_conv_tz_datetime_w_region (&src_dt, &source, &dest, &dest_dt, NULL, NULL);
  if (err_status != NO_ERROR)
    {
      return err_status;
    }

  *time_dest = dest_dt.time / 1000;

  return err_status;
}

/*
 * tz_utc_datetimetz_to_local () -
 *
 * Return: error code
 * dt_utc(in): object containing datetime value (in UTC reference)
 * tz_id(in): TZ_ID encoded value
 * dt_local(out): object containing datetime value (adjusted to timezone contained in tz_id)
 *
 */
int
tz_utc_datetimetz_to_local (const DB_DATETIME * dt_utc, const TZ_ID * tz_id, DB_DATETIME * dt_local)
{
  int total_offset;
  int err_status = NO_ERROR;
  TZ_DECODE_INFO tz_info;

  tz_decode_tz_id (tz_id, true, &tz_info);

  if (tz_info.type == TZ_REGION_OFFSET)
    {
      total_offset = tz_info.offset;
      if (total_offset == TZ_INVALID_OFFSET)
    {
#if !defined (CS_MODE)
      assert (BO_IS_SERVER_RESTARTED ());
#endif
      err_status = ER_TZ_INTERNAL_ERROR;
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, err_status, 0);
    }
    }
  else
    {
      assert (tz_info.zone.p_zone_off_rule != NULL);
      total_offset = tz_info.zone.p_zone_off_rule->gmt_off;

      if (tz_info.zone.p_zone_off_rule->ds_type == DS_TYPE_RULESET_ID && tz_info.zone.p_ds_rule != NULL)
    {
      total_offset += tz_info.zone.p_ds_rule->save_time;
    }
      if (tz_info.zone.p_zone_off_rule->ds_type == DS_TYPE_FIXED)
    {
      total_offset += tz_info.zone.p_zone_off_rule->ds_ruleset;
    }
    }

  if (dt_utc->date == 0)
    {
      assert (dt_utc->time == 0);
      *dt_local = *dt_utc;
    }
  else
    {
      err_status = db_add_int_to_datetime ((DB_DATETIME *) dt_utc, total_offset * 1000, dt_local);
    }

  return err_status;
}

/*
 * tz_datetimeltz_to_local () -
 *
 * Return: error code
 * dt_ltz(in): object containing datetime value in UTC representing a datetime in local timezone
 * dt_local(out): object containing datetime value (adjusted to session timezone)
 *
 */
int
tz_datetimeltz_to_local (const DB_DATETIME * dt_ltz, DB_DATETIME * dt_local)
{
  int error = NO_ERROR;
  TZ_ID ses_tz_id;

  error = tz_create_session_tzid_for_datetime (dt_ltz, true, &ses_tz_id);
  if (error != NO_ERROR)
    {
      return error;
    }

  return tz_utc_datetimetz_to_local (dt_ltz, &ses_tz_id, dt_local);
}

/*
 * tz_zone_info_to_str () - Print a timezone decoded information into a string
 *
 * Return: print characters
 * tz_info(in): object containing timezone info
 * tz_str(in/out): buffer string to print in
 * tz_str_size(in): size of buffer
 *
 */
static int
tz_zone_info_to_str (const TZ_DECODE_INFO * tz_info, char *tz_str, const int tz_str_size)
{
  const TZ_DATA *tzd = tz_get_data ();
  int zone_name_size;
  int zone_id;
  int zone_offset_id;
  int dst_format_size;
  int total_len;
  TZ_TIMEZONE *timezone;
  TZ_OFFSET_RULE *zone_off_rule;
  char dst_format[TZ_MAX_FORMAT_SIZE];
  const char *p_dst_format = NULL;

  if (tz_info->type == TZ_REGION_OFFSET)
    {
      int offset = tz_info->offset;
      int hour, min, sec, n;
      char sign;

      if (offset == TZ_INVALID_OFFSET)
    {
      return -1;
    }

      sign = (offset < 0) ? '-' : '+';
      assert (offset >= TZ_MIN_OFFSET && offset <= TZ_MAX_OFFSET);

      offset = (offset < 0) ? (-offset) : offset;
      db_time_decode ((DB_TIME *) (&offset), &hour, &min, &sec);

      if (sec > 0)
    {
      n = sprintf (tz_str, "%c%02d:%02d:%02d", sign, hour, min, sec);
    }
      else
    {
      n = sprintf (tz_str, "%c%02d:%02d", sign, hour, min);
    }

      return n;
    }

  if (tzd == NULL)
    {
      return -1;
    }

  zone_id = tz_info->zone.zone_id;
  zone_offset_id = tz_info->zone.offset_id;
  zone_name_size = strlen (tzd->names[zone_id].name);

  timezone = &(tzd->timezones[tzd->names[zone_id].zone_id]);
  zone_off_rule = &(tzd->offset_rules[timezone->gmt_off_rule_start + zone_offset_id]);

  p_dst_format = zone_off_rule->std_format;
  if (zone_off_rule->ds_type == DS_TYPE_RULESET_ID)
    {
      TZ_DS_RULESET *ds_ruleset;
      TZ_DS_RULE *ds_rule = NULL;
      int dst_id = tz_info->zone.dst_id;
      const char *ds_abbr = NULL;

      ds_ruleset = &(tzd->ds_rulesets[zone_off_rule->ds_ruleset]);
      if (dst_id < ds_ruleset->count)
    {
      ds_rule = &(tzd->ds_rules[dst_id + ds_ruleset->index_start]);
      ds_abbr = ds_rule->letter_abbrev;
    }

      if (zone_off_rule->var_format != NULL)
    {
      if (ds_abbr == NULL)
        {
          p_dst_format = NULL;
        }
      else
        {
          snprintf (dst_format, sizeof (dst_format) - 1, zone_off_rule->var_format,
            (ds_abbr != NULL && *ds_abbr != '-') ? ds_abbr : "");
          p_dst_format = dst_format;
        }
    }
      else
    {
      if (ds_rule != NULL && ds_rule->save_time != 0 && zone_off_rule->save_format != NULL)
        {
          p_dst_format = zone_off_rule->save_format;
        }
    }
    }

  if (p_dst_format != NULL)
    {
      dst_format_size = strlen (p_dst_format);
    }
  else
    {
      dst_format_size = 0;
    }

  if (dst_format_size > 0)
    {
      total_len = dst_format_size + zone_name_size + 2;
    }
  else
    {
      total_len = zone_name_size + 1;
    }

  if (total_len > tz_str_size)
    {
      /* silent return */
      return -1;
    }

  if (p_dst_format != NULL)
    {
      snprintf (tz_str, tz_str_size, "%s %s", tzd->names[zone_id].name, p_dst_format);
    }
  else
    {
      snprintf (tz_str, tz_str_size, "%s", tzd->names[zone_id].name);
    }

  return total_len - 1;
}

/*
 * tz_id_to_str () - Print a timezone compressed identifier into a string
 *
 * Return: printed characters
 * tz_id(in): complete valid zone identifier
 * tz_str(in): buffer string to print in
 * tz_str_size(in): size of buffer
 *
 */
int
tz_id_to_str (const TZ_ID * tz_id, char *tz_str, const int tz_str_size)
{
  TZ_DECODE_INFO tz_info;

  tz_decode_tz_id (tz_id, true, &tz_info);
  return tz_zone_info_to_str (&tz_info, tz_str, tz_str_size);
}

/*
 * tz_datetimetz_fix_zone () - Adjusts timezone identifier part of a DATETIMETZ object so that offset and DST parts
 *                 are adjusted to new DATETIME
 *
 * Return: error code
 * src_dt_tz(in): datetime value (adjusted to UTC) and timezone identifier
 * dest_dt_tz(out): fixed DATETIMETZ value
 *
 *  Note : After an arithmetic operation (DATETIMETZ + number), the DATETIME
 *     part is changed, but the TZ_ID part remains unchanged and is not
 *     compatible. This functions adjusts the TZ_ID. If the TZ_ID is of
 *     offset type, no adjustements are necessary. If TZ_ID is of
 *     geographic type, only the zone idenfier is kept, while the offset
 *     and daylight saving rule identifiers are changed according to new
 *     date. This ensures that the output value is ready to be decoded.
 *
 */
int
tz_datetimetz_fix_zone (const DB_DATETIMETZ * src_dt_tz, DB_DATETIMETZ * dest_dt_tz)
{
  int er_status = NO_ERROR;
  TZ_DECODE_INFO tz_info;

  tz_decode_tz_id (&(src_dt_tz->tz_id), false, &tz_info);

  er_status = tz_datetime_utc_conv (&(src_dt_tz->datetime), &tz_info, true, true, &(dest_dt_tz->datetime));
  if (er_status != NO_ERROR)
    {
      return er_status;
    }

  tz_encode_tz_id (&tz_info, &(dest_dt_tz->tz_id));

  return er_status;
}

/*
 * tz_timestamptz_fix_zone () - Adjusts timezone identifier part of a DATETIMETZ object so that offset and DST parts
 *                  are adjusted to new DATETIME
 *
 * Return: error code
 * src_ts_tz(in): timestamp value and timezone identifier
 * dest_ts_tz(out): fixed TIMESTAMPTZ value
 *
 */
int
tz_timestamptz_fix_zone (const DB_TIMESTAMPTZ * src_ts_tz, DB_TIMESTAMPTZ * dest_ts_tz)
{
  int er_status = NO_ERROR;
  DB_DATETIMETZ src_dt_tz, dest_dt_tz;
  DB_DATE date;
  DB_TIME time;

  db_timestamp_decode_utc (&src_ts_tz->timestamp, &date, &time);
  src_dt_tz.datetime.date = date;
  src_dt_tz.datetime.time = time * 1000;
  src_dt_tz.tz_id = src_ts_tz->tz_id;

  er_status = tz_datetimetz_fix_zone (&src_dt_tz, &dest_dt_tz);
  if (er_status != NO_ERROR)
    {
      return er_status;
    }

  date = dest_dt_tz.datetime.date;
  time = dest_dt_tz.datetime.time / 1000;

  dest_ts_tz->tz_id = dest_dt_tz.tz_id;
  er_status = db_timestamp_encode_utc (&date, &time, &dest_ts_tz->timestamp);

  return er_status;
}

/*
 * Utility functions
 */

/*
 * tz_encode_tz_id () - Encodes a timezone decoded information into a compressed timezone identifier (ready for storage)
 *
 * Return: none
 * tz_info(in): object containing decoded timezone info
 * tz_id(out): compressed timezone identifier
 *
 *  FFZZZZZZ ZZZZZZZZ OOOOOOOO DDDDDDDD
 *  F = 01 : TZ_ID is number representing positive offset in seconds
 *  F = 10 : TZ_ID is number representing negative offset in seconds
 *  F = 00 : TZ_ID is encoded as ZOD
 *  F = 11 : not used
 *  Z = geographical zone id
 *  O = GMT offset sub-rule id
 *  D = DST sub-rule id
 */
static void
tz_encode_tz_id (const TZ_DECODE_INFO * tz_info, TZ_ID * tz_id)
{
  if (tz_info->type == TZ_REGION_OFFSET)
    {
      // *INDENT-OFF*
      unsigned int offset = static_cast<unsigned int> ((tz_info->offset < 0) ? (-tz_info->offset) : tz_info->offset);
      // *INDENT-ON*

      offset = offset & TZ_OFFSET_MASK;

      if (tz_info->offset < 0)
    {
      *tz_id = offset | (0x2 << 30);
    }
      else
    {
      *tz_id = offset | (0x1 << 30);
    }
    }
  else
    {
      int zone_id = tz_info->zone.zone_id;
      int offset_id = tz_info->zone.offset_id;
      int dst_id = tz_info->zone.dst_id;

      zone_id = zone_id & TZ_ZONE_ID_MAX;
      offset_id = offset_id & TZ_OFFSET_ID_MAX;
      dst_id = dst_id & TZ_DS_ID_MAX;

      *tz_id = dst_id | (offset_id << 8) | (zone_id) << 16;
    }
}

/*
 * tz_encode_tz_region () - encodes a partial timezone decoding struct into a timezone region
 *
 * Return: none
 * tz_info(in): object containing decoded timezone info
 * tz_region(out): time zone region
 */
static void
tz_encode_tz_region (const TZ_DECODE_INFO * tz_info, TZ_REGION * tz_region)
{
  tz_region->type = tz_info->type;
  if (tz_info->type == TZ_REGION_OFFSET)
    {
      tz_region->offset = tz_info->offset;
    }
  else
    {
      assert (tz_info->zone.zone_id < TZ_ZONE_ID_MAX);
      tz_region->zone_id = tz_info->zone.zone_id;
    }
}

/*
 * tz_decode_tz_id () - Decodes a timezone compressed identifier into a structure
 *
 * Return: none
 * tz_id(in): full
 * is_full_decode(in): true if full decoding should be performed; otherwise
 *             only zone identifier is decoded for a geographical TZ_ID
 * tz_info(out): object containing decoded timezone info
 *
 */
static void
tz_decode_tz_id (const TZ_ID * tz_id, const bool is_full_decode, TZ_DECODE_INFO * tz_info)
{
  unsigned int val = (unsigned int) *tz_id;
  unsigned int flag = (val & TZ_MASK_TZ_ID_FLAG) >> TZ_BIT_SHIFT_TZ_ID_FLAG;

  memset (tz_info, 0, sizeof (tz_info[0]));

  if (flag == 0)
    {
      tz_info->zone.zone_id = (val & (TZ_ZONE_ID_MAX << 16)) >> 16;
      tz_info->zone.offset_id = (val & (TZ_OFFSET_ID_MAX << 8)) >> 8;
      tz_info->zone.dst_id = val & TZ_DS_ID_MAX;
      tz_info->type = TZ_REGION_ZONE;

      assert (tz_info->zone.zone_id < TZ_ZONE_ID_MAX);

      if (is_full_decode)
    {
      int zone_id;
      int zone_offset_id;
      const TZ_DATA *tzd = tz_get_data ();
      TZ_TIMEZONE *timezone;
      TZ_OFFSET_RULE *zone_off_rule;

      if (tzd == NULL)
        {
          tz_info->type = TZ_REGION_OFFSET;
          tz_info->offset = 0;
          return;
        }

      zone_id = tzd->names[tz_info->zone.zone_id].zone_id;
      zone_offset_id = tz_info->zone.offset_id;

      assert (zone_offset_id >= 0 && zone_offset_id < TZ_OFFSET_ID_MAX);

      timezone = &(tzd->timezones[zone_id]);
      tz_info->zone.p_timezone = timezone;

      zone_off_rule = &(tzd->offset_rules[timezone->gmt_off_rule_start + zone_offset_id]);
      tz_info->zone.p_zone_off_rule = zone_off_rule;

      if (zone_off_rule->ds_type == DS_TYPE_RULESET_ID)
        {
          TZ_DS_RULESET *ds_ruleset;
          TZ_DS_RULE *ds_rule;
          int dst_id;
          const char *ds_abbr = NULL;

          ds_ruleset = &(tzd->ds_rulesets[zone_off_rule->ds_ruleset]);
          dst_id = tz_info->zone.dst_id;

          /* we may not have a DST rule, if Daylight Saving does not apply */
          if (dst_id != TZ_DS_ID_MAX)
        {
          assert (dst_id >= 0 && dst_id < ds_ruleset->count);
          ds_rule = &(tzd->ds_rules[dst_id + ds_ruleset->index_start]);
          ds_abbr = ds_rule->letter_abbrev;

          tz_info->zone.p_ds_rule = ds_rule;

          if (zone_off_rule->var_format != NULL)
            {
              snprintf (tz_info->zone.dst_str, sizeof (tz_info->zone.dst_str) - 1, zone_off_rule->var_format,
                (ds_abbr != NULL && *ds_abbr != '-') ? ds_abbr : "");
            }
        }
        }
    }
    }
  else
    {
      tz_info->type = TZ_REGION_OFFSET;
      if (flag == 0x2)
    {
      /* negative offset */
      tz_info->offset = -(int) (val & TZ_OFFSET_MASK);
    }
      else
    {
      /* positive offset */
      assert (flag == 0x1);
      tz_info->offset = val & TZ_OFFSET_MASK;
    }
    }
}

/*
 * tz_decode_tz_region () - Decodes a timezone region structure into a helper timezone decoder structure
 *
 * Return: none
 * tz_region(in): time zone region
 * tz_info(out): object containing partial decoded timezone info
 *
 */
static void
tz_decode_tz_region (const TZ_REGION * tz_region, TZ_DECODE_INFO * tz_info)
{
  tz_info->type = tz_region->type;

  if (tz_region->type == TZ_REGION_OFFSET)
    {
      tz_info->offset = tz_region->offset;
    }
  else
    {
      tz_info->zone.zone_id = tz_region->zone_id;
    }

  tz_info->zone.offset_id = TZ_OFFSET_ID_MAX;
  tz_info->zone.dst_id = TZ_DS_ID_MAX;
  tz_info->zone.p_timezone = NULL;
  tz_info->zone.p_zone_off_rule = NULL;
  tz_info->zone.p_ds_rule = NULL;
  tz_info->zone.dst_str[0] = '\0';
}

/*
 * tz_get_first_weekday_around_date () - find the day of month when a specific weekday occurs
 * Returns: -1 if error; otherwise, day of month (0 to 27/28/29/30, depending on the input month.
 *
 * year(in):
 * month(in): month (0-11)
 * weekday(in): weekday ( 0 = Sunday through 6 = Saturday)
 * ref_day(in): reference day of month (0 based)
 *
 */
int
tz_get_first_weekday_around_date (const int year, const int month, const int weekday, const int ref_day,
                  const bool before)
{
  int first_weekday = -1;
  int wday = -1;

  assert (year >= 1900);

  wday = db_get_day_of_week (year, month + 1, ref_day + 1);
  first_weekday = ref_day;

  while (wday != weekday)
    {
      if (before == true)
    {
      wday = (wday == TZ_WEEK_DAY_SUN) ? TZ_WEEK_DAY_SAT : (wday - 1);
      first_weekday--;
      assert (first_weekday >= 0);
    }
      else
    {
      wday = (wday + 1) % TZ_WEEK_DAY_COUNT;
      first_weekday++;

      assert (first_weekday <
          ((month == TZ_MON_FEB) ? (((IS_LEAP_YEAR (year)) ? 29 : 28)) : DAYS_IN_MONTH (month)));
    }
    }

  return first_weekday;
}

/*
 * tz_str_read_number() - attempts to read an integer value from a string
 *
 * Returns: error code
 * str(in): string to parse
 * str_end(in): pointer to end of string (after last character)
 * strict(in): true, if no trailing characters allowed
 * read_sign(in): true, if shoud read leading sign
 * val(out): the integer found in the input string
 * str_next(out): reference to the first character after the read integer.
 *        If no value was read, str_next will reference str.
 */
int
tz_str_read_number (const char *str, const char *str_end, const bool strict, const bool read_sign, int *val,
            const char **str_next)
{
  int cur_val = 0;
  const char *str_cursor;
  bool is_negative = false;

  assert (str < str_end && !IS_EMPTY_STR (str));

  str_cursor = str;

  if (read_sign == true && str < str_end)
    {
      if (*str_cursor == '-')
    {
      is_negative = true;
      str_cursor++;
    }
      else if (*str_cursor == '+')
    {
      str_cursor++;
    }
    }

  while (str_cursor < str_end && char_isdigit (*str_cursor))
    {
      cur_val = cur_val * 10 + (*str_cursor - '0');
      str_cursor++;
    }

  *val = is_negative ? -cur_val : cur_val;

  *str_next = (char *) str_cursor;

  if (strict && str_cursor == str)
    {
      return ER_FAILED;
    }

  return NO_ERROR;
}

/*
 * tz_str_read_time() - read a time value from an input string. Input time can be hh, hh:mm or hh:mm:ss (s or u)
 *
 * Returns: ER_FAILED (message error is not set) or NO_ERROR
 * str(in): string to parse
 * str_end(in): end of string (pointer to first character after string)
 * need_minutes(in): true if it is mandatory to read minutes part, otherwise hour specifier is enough
 * allow_sec60(in): true if 60 is allowed for the value of seconds, false otherwise
 * hour(out): parsed value for hour
 * min(out) : parsed value for minutes
 * sec(out) : parsed value for seconds
 * str_next(out): pointer to the char after the parsed time value
 */
int
tz_str_read_time (const char *str, const char *str_end, bool need_minutes, bool allow_sec60, int *hour, int *min,
          int *sec, const char **str_next)
{
  const char *str_cursor;
  int val_read = 0;

  assert (str < str_end && !IS_EMPTY_STR (str));

  *hour = *min = *sec = 0;
  str_cursor = str;

  /* read hour part */
  if (tz_str_read_number (str_cursor, str_end, true, false, &val_read, str_next) != NO_ERROR)
    {
      return ER_FAILED;
    }
  if (val_read < 0 || val_read > 24)
    {
      return ER_FAILED;
    }
  *hour = val_read;

  str_cursor = *str_next;
  if (*str_cursor != ':')
    {
      if (!need_minutes && *str_cursor == '\0')
    {
      return NO_ERROR;
    }
      /* invalid text representation for time */
      return ER_FAILED;
    }

  str_cursor++;         /* skip colon between hour and minute part */

  /* read minute part */
  if (str_cursor >= str_end || IS_EMPTY_STR (str_cursor))
    {
      /* missing minute token */
      return ER_FAILED;
    }
  if (tz_str_read_number (str_cursor, str_end, true, false, &val_read, str_next) != NO_ERROR)
    {
      return ER_FAILED;
    }
  if (val_read < 0 || val_read > 60 || (val_read > 0 && *hour == 24))
    {
      return ER_FAILED;
    }
  *min = val_read;

  /* read second part if exists */
  str_cursor = *str_next;

  assert (str_cursor <= str_end);
  if (str_cursor == str_end)
    {
      /* reaches end of the given string. This means it does not have second part */
      assert (*sec == 0);
      return NO_ERROR;
    }

  assert (str_cursor < str_end);
  if (*str_cursor == ':')
    {
      /* if there is a token for seconds, read it */
      str_cursor++;
      if (tz_str_read_number (str_cursor, str_end, true, false, &val_read, str_next) != NO_ERROR)
    {
      return ER_FAILED;
    }
      if (val_read < 0 || val_read > (allow_sec60 ? 61 : 60))
    {
      return ER_FAILED;
    }
      *sec = val_read;
    }

  return NO_ERROR;
}

/*
 * tz_str_to_seconds() - parses a string representing a signed time offset
 *
 * Returns: ER_FAILED (message error is not set) or NO_ERROR
 * str(in): string to parse
 * seconds(out): the signed number of seconds that the string represents
 * str_next(out): pointer to the first char after the parsed time
 * is_offset(in): true if str is a timezone offset, false if it is
 *                an absolute value
 */
int
tz_str_to_seconds (const char *str, const char *str_end, int *seconds, const char **str_next, const bool is_offset)
{
  int err_status = NO_ERROR;
  int result = 0;
  const char *str_cursor = NULL;
  int hour = 0, min = 0, sec = 0;
  bool is_negative = false;

  assert (str != NULL);

  str_cursor = str;
  if (str_cursor < str_end && *str_cursor == '-')
    {
      is_negative = true;
      str_cursor++;
    }
  if (str_cursor < str_end && *str_cursor == '+')
    {
      str_cursor++;
    }

  err_status = tz_str_read_time (str_cursor, str_end, false, false, &hour, &min, &sec, str_next);
  if (err_status != NO_ERROR)
    {
      return err_status;
    }
  if (is_offset == true)
    {
      if (is_negative == true && -(hour * 3600 + min * 60 + sec) < TZ_MIN_OFFSET)
    {
      return ER_FAILED;
    }
      if (is_negative == false && (hour * 3600 + min * 60 + sec) > TZ_MAX_OFFSET)
    {
      return ER_FAILED;
    }
    }

  result = sec + min * 60 + hour * 3600;

  *seconds = is_negative ? -result : result;
  return err_status;
}

/*
 * tz_get_ds_change_julian_date () - Computes the exact date when a daylight saving rule applies in year and
 *                                   the difference between the src_julian_date and the computed date
 * Returns: error code
 * src_julian_date(in): source julian date
 * ds_rule(in): daylight saving rule
 * year(in): current year to apply rule
 * ds_rule_julian_date(out): julian date
 * date_diff(out): date difference between the two dates
 */
int
tz_get_ds_change_julian_date_diff (const int src_julian_date, const TZ_DS_RULE * ds_rule, const int year,
                   int *ds_rule_julian_date, full_date_t * date_diff)
{
  int ds_rule_day;
  int ds_rule_month = ds_rule->in_month;

  /* get exact julian date/time for this rule in year 'local_year' */
  if (ds_rule->change_on.type == TZ_DS_TYPE_FIXED)
    {
      ds_rule_day = ds_rule->change_on.day_of_month;
    }
  else
    {
      int ds_rule_weekday, day_month_bound;
      bool before = (ds_rule->change_on.type == TZ_DS_TYPE_VAR_SMALLER) ? true : false;
      ds_rule_weekday = ds_rule->change_on.day_of_week;
      day_month_bound = ds_rule->change_on.day_of_month;

      if (ds_rule->change_on.type == TZ_DS_TYPE_VAR_SMALLER && ds_rule_month == TZ_MON_FEB && IS_LEAP_YEAR (year)
      && day_month_bound == 27)
    {
      day_month_bound++;
    }

      ds_rule_day = tz_get_first_weekday_around_date (year, ds_rule_month, ds_rule_weekday, day_month_bound, before);

      if (ds_rule_day == -1)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TZ_INTERNAL_ERROR, 0);
      return ER_TZ_INTERNAL_ERROR;
    }
    }

  *ds_rule_julian_date = julian_encode (1 + ds_rule_month, 1 + ds_rule_day, year);

  if (date_diff != NULL)
    {
      *date_diff = FULL_DATE (src_julian_date, 0) - FULL_DATE (*ds_rule_julian_date, 0);
    }

  return NO_ERROR;
}

/*
 * tz_fast_find_ds_rule () - Performs a search to find the daylight saving rule for which a certain date applies to
 *
 * Returns: error code
 * tzd(in): Daylight saving data context
 * ds_ruleset(in): set of daylight saving rules
 * src_julian_date(in): julian date for which we search a rule
 * src_year(in): year of date
 * src_month(in): month of date
 * ds_rule_id(out): found rule
 */
static int
tz_fast_find_ds_rule (const TZ_DATA * tzd, const TZ_DS_RULESET * ds_ruleset, const int src_julian_date,
              const int src_year, const int src_month, int *ds_rule_id)
{
  int curr_ds_id;
  int er_status = NO_ERROR;
  TZ_DS_RULE *curr_ds_rule;
  full_date_t smallest_date_diff = -1;
  int year_to_apply_rule = 0;
  full_date_t second_best_date_diff = -1;
  int second_best_ds_id = -1;

  *ds_rule_id = -1;

  if ((src_year > ds_ruleset->to_year_max && src_month > TZ_MON_JAN) || (src_year > ds_ruleset->to_year_max + 1))
    {
      goto exit;
    }

  for (curr_ds_id = 0; curr_ds_id < ds_ruleset->count; curr_ds_id++)
    {
      int ds_rule_julian_date;
      full_date_t date_diff;

      assert (curr_ds_id + ds_ruleset->index_start < tzd->ds_rule_count);
      curr_ds_rule = &(tzd->ds_rules[curr_ds_id + ds_ruleset->index_start]);

      if (src_year + 1 < curr_ds_rule->from_year)
    {
      /* no more rules will match */
      break;
    }

      if (src_year - 1 > curr_ds_rule->to_year
      || (src_year > ds_ruleset->to_year_max && curr_ds_rule->in_month < TZ_MON_DEC))
    {
      full_date_t diff;
      full_date_t ds_rule_date = 0;

      /* We don't need here the date difference so we use only rule date */
      er_status =
        tz_get_ds_change_julian_date_diff (0, curr_ds_rule, curr_ds_rule->to_year, &ds_rule_julian_date, NULL);
      if (er_status != NO_ERROR)
        {
          goto exit;
        }

      ds_rule_date = FULL_DATE (ds_rule_julian_date, curr_ds_rule->at_time);
      diff = FULL_DATE (src_julian_date, 0) - ds_rule_date;

      if (second_best_date_diff == -1 || diff < second_best_date_diff)
        {
          second_best_date_diff = diff;
          second_best_ds_id = curr_ds_id;
        }

      /* this rule cannot apply */
      continue;
    }
      else if (src_year < curr_ds_rule->from_year && curr_ds_rule->in_month > TZ_MON_JAN)
    {
      continue;
    }

      year_to_apply_rule = get_year_to_apply_rule (src_year, curr_ds_rule);
      er_status =
    tz_get_ds_change_julian_date_diff (src_julian_date, curr_ds_rule, year_to_apply_rule, &ds_rule_julian_date,
                       &date_diff);
      if (er_status != NO_ERROR)
    {
      goto exit;
    }

      if (date_diff < 0 && curr_ds_rule->from_year < year_to_apply_rule)
    {
      /* if DS rule does not apply to current year try previous year */
      er_status =
        tz_get_ds_change_julian_date_diff (src_julian_date, curr_ds_rule, year_to_apply_rule - 1,
                           &ds_rule_julian_date, &date_diff);
      if (er_status != NO_ERROR)
        {
          goto exit;
        }
    }

      if (date_diff >= DATE_DIFF_MATCH_SAFE_THRESHOLD_SEC
      && (smallest_date_diff == -1 || date_diff < smallest_date_diff))
    {
      /* a date difference of at least two days */
      *ds_rule_id = curr_ds_id;
      smallest_date_diff = date_diff;
    }
    }
  if (*ds_rule_id == -1)
    {
      *ds_rule_id = second_best_ds_id;
    }

exit:
  return er_status;
}

/*
 * tz_check_ds_match_string () - Checks if user supplied daylight saving string specifier matches the DS rule
 *
 * Returns: true if the DS string matches with the selected offset and DS rule
 * off_rule(in): Offset rule
 * ds_rule(in): daylight saving rule
 * ds_string(in): daylight saving specifier (user source)
 * default_abrev(in): default abbreviation in case ds_rule is NULL
 */
static bool
tz_check_ds_match_string (const TZ_OFFSET_RULE * off_rule, const TZ_DS_RULE * ds_rule, const char *ds_string,
              const char *default_abrev)
{
  bool rule_matched = true;
  const char *letter_abrev = NULL;

  if (ds_rule != NULL && ds_rule->letter_abbrev != NULL && *ds_rule->letter_abbrev != '-')
    {
      letter_abrev = ds_rule->letter_abbrev;
    }
  else if (ds_rule == NULL && *default_abrev != '-')
    {
      letter_abrev = default_abrev;
    }

  if (off_rule->var_format != NULL)
    {
      char rule_dst_format[TZ_MAX_FORMAT_SIZE];

      if (letter_abrev != NULL)
    {
      snprintf (rule_dst_format, sizeof (rule_dst_format) - 1, off_rule->var_format, letter_abrev);
    }
      else
    {
      snprintf (rule_dst_format, sizeof (rule_dst_format) - 1, off_rule->var_format, "");
    }

      if (strcasecmp (rule_dst_format, ds_string) != 0)
    {
      /* not matching with variable format */
      rule_matched = false;
    }
    }
  else if (off_rule->save_format != NULL && ds_rule->save_time != 0
       && strcasecmp (off_rule->save_format, ds_string) != 0)
    {
      /* not matching with DST format */
      rule_matched = false;
    }
  else if (off_rule->std_format != NULL && ds_rule->save_time == 0 && strcasecmp (off_rule->std_format, ds_string) != 0)
    {
      /* not matching with standard format */
      rule_matched = false;
    }

  return rule_matched;
}

/*
 * tz_offset_with_fixed_ds  () - Returns an offset that will be used to transform a date into either UTC time reference
 *               or local time reference
 *
 * Returns: an offset
 * src_is_utc(in): true if UTC time reference, false otherwise
 * until_time_type(in): time type of the offset rule
 * gmt_offset_sec(in): gmt offset of the offset rule
 * ds_save_time(in): daylight saving time
 */
static int
tz_offset (const bool src_is_utc, const TZ_TIME_TYPE until_time_type, const int gmt_offset_sec, const int ds_save_time)
{
  int offset = 0;

  if (src_is_utc == true)
    {
      if (until_time_type == TZ_TIME_TYPE_LOCAL_STD)
    {
      offset += gmt_offset_sec;
    }
      else if (until_time_type == TZ_TIME_TYPE_LOCAL_WALL)
    {
      offset += gmt_offset_sec + ds_save_time;
    }
    }
  else if (src_is_utc == false)
    {
      if (until_time_type == TZ_TIME_TYPE_UTC)
    {
      offset -= gmt_offset_sec + ds_save_time;
    }
      else if (until_time_type == TZ_TIME_TYPE_LOCAL_STD)
    {
      offset -= ds_save_time;
    }
    }

  return offset;
}

/*
 * get_date_diff_from_ds_rule  () - Returns the date difference between a source date and the date when applying
 *                  a daylight saving rule using from_year or to_year
 *
 * Returns: error or no error
 * src_julian_date(in): input source date
 * src_time_sec(in): input source time
 * ds_rule(in): input daylight saving rule
 * direction(in): flag that tells in which direction to search
 * date_diff(out): date difference
 */
static int
get_date_diff_from_ds_rule (const int src_julian_date, const int src_time_sec, const TZ_DS_RULE * ds_rule,
                const DS_SEARCH_DIRECTION direction, full_date_t * date_diff)
{
  int ds_rule_julian_date;
  int year, err_status = NO_ERROR;
  full_date_t ds_rule_date;

  if (direction == FORWARD)
    {
      year = ds_rule->from_year;
    }
  else
    {
      year = ds_rule->to_year;
    }

  /* We don't need here the date difference so we use only rule date */
  err_status = tz_get_ds_change_julian_date_diff (0, ds_rule, year, &ds_rule_julian_date, NULL);
  if (err_status != NO_ERROR)
    {
      goto exit;
    }

  ds_rule_date = FULL_DATE (ds_rule_julian_date, ds_rule->at_time);
  *date_diff = FULL_DATE (src_julian_date, src_time_sec) - ds_rule_date;

  if (direction == FORWARD)
    {
      *date_diff = -(*date_diff);
    }

exit:
  return err_status;
}

/*
 * get_closest_ds_rule() - Returns the id of the closest daylight saving rule in the ds_ruleset relative to to_year
 *             or from_year
 *
 * Returns: the id of the rule or -1 in case of error
 * src_julian_date(in): input source date
 * src_time_sec(in): input source time
 * ds_ruleset(in): input ds_ruleset
 * tzd(in): pointer to the tzdata
 * direction(in): input flag that tells us in which direction to search
 */
static int
get_closest_ds_rule (const int src_julian_date, const int src_time_sec, const TZ_DS_RULESET * ds_ruleset,
             const TZ_DATA * tzd, const DS_SEARCH_DIRECTION direction)
{
  int curr_ds_id = 0;
  int closest_ds_rule_id = 0;
  TZ_DS_RULE *ds_rule;
  full_date_t best_diff = -1;
  full_date_t date_diff;
  int err_status = NO_ERROR;

  for (; curr_ds_id < ds_ruleset->count; curr_ds_id++)
    {
      ds_rule = &(tzd->ds_rules[curr_ds_id + ds_ruleset->index_start]);

      err_status = get_date_diff_from_ds_rule (src_julian_date, src_time_sec, ds_rule, direction, &date_diff);
      if (err_status != NO_ERROR)
    {
      return -1;
    }
      if (direction == FORWARD && ds_rule->save_time != 0)
    {
      continue;
    }
      if (best_diff == -1 || date_diff < best_diff)
    {
      best_diff = date_diff;
      closest_ds_rule_id = curr_ds_id;
    }
    }

  return closest_ds_rule_id;
}

/*
 * get_saving_time_from_offset_rule() - Computes the daylight saving time for the last day when the input offset
 *                  rule applies
 *
 * Returns: error or no error
 * offset_rule(in): input offset rule
 * tzd(in): timezone data
 * save_time(out): output daylight saving time
 *
 */
static int
get_saving_time_from_offset_rule (const TZ_OFFSET_RULE * offset_rule, const TZ_DATA * tzd, int *save_time)
{
  int err_status = NO_ERROR;

  *save_time = 0;
  if (offset_rule->ds_type == DS_TYPE_RULESET_ID)
    {
      int ds_rule_id = -1;
      TZ_DS_RULESET *ds_ruleset_off_rule;
      int offset_rule_src_year;
      int offset_rule_src_month;

      julian_decode (offset_rule->julian_date, &offset_rule_src_month, NULL, &offset_rule_src_year, NULL);
      ds_ruleset_off_rule = &(tzd->ds_rulesets[offset_rule->ds_ruleset]);

      err_status =
    tz_fast_find_ds_rule (tzd, ds_ruleset_off_rule, offset_rule->julian_date, offset_rule_src_year,
                  offset_rule_src_month, &ds_rule_id);
      if (err_status != NO_ERROR)
    {
      goto exit;
    }

      if (ds_rule_id != -1)
    {
      TZ_DS_RULE *ds_rule;

      assert (ds_rule_id + ds_ruleset_off_rule->index_start < tzd->ds_rule_count);

      ds_rule = &(tzd->ds_rules[ds_rule_id + ds_ruleset_off_rule->index_start]);
      *save_time = ds_rule->save_time;
    }
    }
  else
    {
      *save_time = offset_rule->ds_ruleset;
    }
exit:
  return err_status;
}

/*
 * is_in_overlap_interval() - Verifies if a specific date is in the overlap interval between two offset rules
 *
 * Returns: true or false
 * time_type(in): time reference
 * offset_rule_diff(in): time difference between the date and the time when the first offset rule ends
 * gmt_diff(in): offset time difference between the two offset rules
 * save_time_diff(in): daylight saving time difference between the two offset rules
 *
 */
static bool
is_in_overlap_interval (const TZ_TIME_TYPE time_type, const full_date_t offset_rule_diff, const full_date_t gmt_diff,
            const int save_time_diff)
{
  full_date_t add_time = 0;
  bool overlap = true;

  if (time_type == TZ_TIME_TYPE_LOCAL_STD)
    {
      add_time = save_time_diff;
    }
  else if (time_type == TZ_TIME_TYPE_UTC)
    {
      add_time = gmt_diff + save_time_diff;
    }

  if (gmt_diff + save_time_diff > 0 || (ABS (offset_rule_diff + add_time) > ABS (gmt_diff + save_time_diff)))
    {
      overlap = false;
    }

  return overlap;
}

/*
 * get_year_to_apply_rule() - Computes the year in which to apply a daylight saving rule given the source year
 *
 *
 * Returns: the year in which to apply the daylight saving rule
 * src_year(in): source year
 * ds_rule(in): daylight saving rule
 *
 */
static int
get_year_to_apply_rule (const int src_year, const TZ_DS_RULE * ds_rule)
{
  int year_to_apply_rule;

  if (src_year <= ds_rule->to_year)
    {
      if (src_year >= ds_rule->from_year)
    {
      year_to_apply_rule = src_year;
    }
      else
    {
      year_to_apply_rule = ds_rule->from_year;
    }
    }
  else
    {
      year_to_apply_rule = src_year - 1;
    }

  return year_to_apply_rule;
}

/*
 * tz_datetime_utc_conv () -
 *
 * Return: error code
 * src_dt(in): object containing source datetime value;
 *         if 'src_is_utc' is true, than is UTC datetime, otherwise is local datetime
 * tz_info(in/out): (partial) decoded timezone info associated with source datetime, additional information is
 *          changed/added after conversion
 * src_is_utc(in): true if 'src_dt' is in UTC time reference, false if src_dt is in local time reference
 * only_tz_adjust(in): true if only timezone adjustment is desired, datetime itself is not changed (used in context of
 *             datetime arithmetic)
 * dt_dest(out): object containing destination datetime
 *
 */
static int
tz_datetime_utc_conv (const DB_DATETIME * src_dt, TZ_DECODE_INFO * tz_info, bool src_is_utc, bool only_tz_adjust,
              DB_DATETIME * dest_dt)
{
  int src_julian_date, rule_julian_date;
  int src_time_sec, rule_time_sec;
  int src_year;
  int src_month;
  int gmt_std_offset_sec;
  int total_offset_sec;
  int err_status = NO_ERROR;
  int curr_offset_id = -1;
  int curr_ds_id = -1, applying_ds_id = -1;
  full_date_t applying_date_diff = -1;
  TZ_TIMEZONE *timezone;
  const TZ_DATA *tzd;
  TZ_OFFSET_RULE *curr_off_rule = NULL, *next_off_rule = NULL;
  TZ_OFFSET_RULE *prev_off_rule = NULL;
  TZ_DS_RULESET *ds_ruleset;
  TZ_DS_RULE *curr_ds_rule, *applying_ds_rule;
  bool check_user_dst = false;
  bool applying_with_prev_year = false;
  bool applying_is_in_leap_interval = false;
  int save_time = 0, src_offset_curr_off_rule = 0;
  int src_offset_prev_off_rule = 0;
  int leap_offset_rule_interval = 0;
  int prev_rule_julian_date = 0, prev_rule_time_sec = 0;
  bool try_offset_rule_overlap = false;
  int second_best_applying_ds_id = -1;
  full_date_t second_best_applying_date_diff = -1;
  TZ_DS_RULE *second_best_applying_ds_rule = NULL;
  int offset_rule_counter = 0;
  TZ_OFFSET_RULE *offset_rules[2] = { NULL };

  if (tz_info->type == TZ_REGION_OFFSET)
    {
      /* keep offset and zone decode info */
      total_offset_sec = tz_info->offset;
      if (total_offset_sec == TZ_INVALID_OFFSET)
    {
#if !defined (CS_MODE)
      assert (BO_IS_SERVER_RESTARTED ());
#endif
      err_status = ER_TZ_INTERNAL_ERROR;
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, err_status, 0);
    }
      goto exit;
    }

  assert (tz_info->type == TZ_REGION_ZONE);

  tzd = tz_get_data ();

  if (tzd == NULL)
    {
      err_status = ER_TZ_INTERNAL_ERROR;
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, err_status, 0);
      goto exit;
    }

  if ((src_dt->time > MILLIS_IN_A_DAY))
    {
      err_status = ER_TIME_CONVERSION;
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TIME_CONVERSION, 0);
      goto exit;
    }
  /* start decoding zone , GMT offset id, DST id */
  assert ((int) tz_info->zone.zone_id < tzd->name_count);
  timezone = &(tzd->timezones[tzd->names[tz_info->zone.zone_id].zone_id]);

  assert (timezone->gmt_off_rule_count > 0);

  src_julian_date = src_dt->date;
  src_time_sec = src_dt->time / 1000;

  for (curr_offset_id = 0; curr_offset_id < timezone->gmt_off_rule_count; curr_offset_id++)
    {
      assert (timezone->gmt_off_rule_start + curr_offset_id < tzd->offset_rule_count);
      prev_off_rule = curr_off_rule;
      curr_off_rule = &(tzd->offset_rules[timezone->gmt_off_rule_start + curr_offset_id]);

      if (curr_off_rule->until_flag == UNTIL_EXPLICIT)
    {
      rule_julian_date = curr_off_rule->julian_date;
      rule_time_sec = (curr_off_rule->until_hour * 60 + curr_off_rule->until_min) * 60 + curr_off_rule->until_sec;
    }
      else
    {
      rule_julian_date = TZ_MAX_JULIAN_DATE;
      rule_time_sec = 0;
    }

      /* add a safety buffer of 1 julian day */
      if (src_julian_date <= rule_julian_date + 1)
    {
      /* this is a candidate, we still have to check the exact time when rule ends */
      if (curr_off_rule->until_flag == UNTIL_EXPLICIT)
        {
          /* set also next */
          assert (curr_offset_id < timezone->gmt_off_rule_count - 1);
          assert (timezone->gmt_off_rule_start + curr_offset_id + 1 < tzd->offset_rule_count);
          next_off_rule = &(tzd->offset_rules[timezone->gmt_off_rule_start + curr_offset_id + 1]);
          if (timezone->gmt_off_rule_start + curr_offset_id + 2 < tzd->offset_rule_count)
        {
          offset_rules[0] = &(tzd->offset_rules[timezone->gmt_off_rule_start + curr_offset_id + 2]);
        }
        }
      /* rule found */
      break;
    }
    }

  assert (curr_off_rule != NULL);
  assert (next_off_rule != NULL || curr_off_rule->until_flag == UNTIL_INFINITE);

  julian_decode (src_julian_date, &src_month, NULL, &src_year, NULL);

detect_dst:
  if (curr_off_rule->until_flag == UNTIL_EXPLICIT)
    {
      rule_julian_date = curr_off_rule->julian_date;
      rule_time_sec = (curr_off_rule->until_hour * 60 + curr_off_rule->until_min) * 60 + curr_off_rule->until_sec;
    }
  else
    {
      rule_julian_date = TZ_MAX_JULIAN_DATE;
      rule_time_sec = 0;
    }

  applying_ds_id = -1;
  applying_date_diff = -1;
  gmt_std_offset_sec = curr_off_rule->gmt_off;
  total_offset_sec = gmt_std_offset_sec;
  second_best_applying_ds_id = -1;
  second_best_applying_date_diff = -1;

  if (prev_off_rule != NULL)
    {
      prev_rule_julian_date = prev_off_rule->julian_date;
      prev_rule_time_sec = (prev_off_rule->until_hour * 60 + prev_off_rule->until_min) * 60 + prev_off_rule->until_sec;
      leap_offset_rule_interval = curr_off_rule->gmt_off - prev_off_rule->gmt_off;
    }

  if (curr_off_rule->ds_type == DS_TYPE_FIXED)
    {
      int curr_time_offset;
      full_date_t offset_rule_diff;

      curr_time_offset =
    tz_offset (src_is_utc, curr_off_rule->until_time_type, gmt_std_offset_sec, curr_off_rule->ds_ruleset);
      if (prev_off_rule != NULL)
    {
      src_offset_prev_off_rule =
        tz_offset (src_is_utc, prev_off_rule->until_time_type, gmt_std_offset_sec, curr_off_rule->ds_ruleset);
    }

      offset_rule_diff =
    FULL_DATE (src_julian_date, src_time_sec + src_offset_prev_off_rule) - FULL_DATE (prev_rule_julian_date,
                                              prev_rule_time_sec);

      if (FULL_DATE (src_julian_date, src_time_sec + curr_time_offset) < FULL_DATE (rule_julian_date, rule_time_sec))
    {
      int add_ds_save_time_diff = 0;
      int prev_rule_save_time = 0;
      full_date_t leap = 0;

      if (prev_off_rule != NULL)
        {
          if (prev_off_rule->until_time_type == TZ_TIME_TYPE_LOCAL_WALL)
        {
          add_ds_save_time_diff = curr_off_rule->ds_ruleset;
          if (offset_rule_diff <= 2 * SECONDS_IN_A_DAY)
            {
              err_status = get_saving_time_from_offset_rule (prev_off_rule, tzd, &prev_rule_save_time);
              if (err_status != NO_ERROR)
            {
              goto exit;
            }
            }
          add_ds_save_time_diff -= prev_rule_save_time;
          leap = leap_offset_rule_interval + add_ds_save_time_diff;
        }
          else if (prev_off_rule->until_time_type == TZ_TIME_TYPE_LOCAL_STD)
        {
          leap = leap_offset_rule_interval;
        }
        }

      if (try_offset_rule_overlap == false && leap >= 0 && (offset_rule_diff < leap))
        {
          /* invalid time, abort */
          err_status = ER_TZ_DURING_OFFSET_RULE_LEAP;
          er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, err_status, 0);
          goto exit;
        }

      if (src_is_utc == false && tz_info->zone.dst_str[0] != '\0' && curr_off_rule->var_format == NULL
          && (curr_off_rule->std_format == NULL
          || strcasecmp (curr_off_rule->std_format, tz_info->zone.dst_str) != 0)
          && (curr_off_rule->save_format == NULL
          || strcasecmp (curr_off_rule->save_format, tz_info->zone.dst_str) != 0))
        {
          if (next_off_rule == NULL || try_offset_rule_overlap == true)
        {
          err_status = ER_TZ_INVALID_COMBINATION;
          er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, err_status, 0);
          goto exit;
        }
          else
        {
          try_offset_rule_overlap = true;
          APPLY_NEXT_OFF_RULE ();
          goto detect_dst;
        }
        }
      else if (src_is_utc == false && tz_info->zone.dst_str[0] != '\0' && try_offset_rule_overlap == true)
        {
          bool overlap = false;

          if (ABS (offset_rule_diff) <= 3 * SECONDS_IN_A_DAY)
        {
          int prev_rule_save_time = 0;
          int save_time_diff = 0;

          err_status = get_saving_time_from_offset_rule (prev_off_rule, tzd, &prev_rule_save_time);
          if (err_status != NO_ERROR)
            {
              goto exit;
            }

          save_time_diff = curr_off_rule->ds_ruleset - prev_rule_save_time;
          overlap =
            is_in_overlap_interval (prev_off_rule->until_time_type, offset_rule_diff, leap_offset_rule_interval,
                        save_time_diff);
        }

          if (overlap == false)
        {
          /* invalid time, abort */
          err_status = ER_TZ_INVALID_COMBINATION;
          er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, err_status, 0);
          goto exit;
        }
        }

      applying_ds_id = 0;
      /* finished */
      goto exit;
    }

      /* try next GMT offset zone */
      if (next_off_rule == NULL)
    {
      err_status = ER_TZ_INVALID_COMBINATION;
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, err_status, 0);
      goto exit;
    }
      APPLY_NEXT_OFF_RULE ();
      goto detect_dst;
    }

  /* the zone uses DST rules */
  assert (curr_off_rule->ds_type == DS_TYPE_RULESET_ID);
  assert (curr_off_rule->ds_ruleset < tzd->ds_ruleset_count);
  ds_ruleset = &(tzd->ds_rulesets[curr_off_rule->ds_ruleset]);

  applying_ds_rule = NULL;
  second_best_applying_ds_rule = NULL;
  if (src_is_utc == false && tz_info->zone.dst_str[0] != '\0')
    {
      check_user_dst = true;
    }

  if ((src_year > ds_ruleset->to_year_max && src_month > TZ_MON_JAN) || (src_year > ds_ruleset->to_year_max + 1))
    {
      curr_ds_id = ds_ruleset->count;
    }
  else
    {
      curr_ds_id = 0;
    }

  for (; curr_ds_id < ds_ruleset->count; curr_ds_id++)
    {
      int ds_rule_julian_date;
      full_date_t date_diff;
      bool rule_matched = false;
      bool is_in_leap_interval = false;
      bool check_prev_year = true;
      int year_to_apply_rule = 0;
      TZ_DS_RULE *wall_ds_rule = NULL;

      assert (curr_ds_id + ds_ruleset->index_start < tzd->ds_rule_count);
      curr_ds_rule = &(tzd->ds_rules[curr_ds_id + ds_ruleset->index_start]);

      if (src_year + 1 < curr_ds_rule->from_year)
    {
      /* no more rules will match */
      break;
    }

      if (src_year - 1 > curr_ds_rule->to_year
      || (src_year > ds_ruleset->to_year_max && curr_ds_rule->in_month < TZ_MON_DEC))
    {
      full_date_t diff;
      full_date_t ds_rule_date = 0;

      /* We don't need here the date difference so we use only rule date */
      err_status =
        tz_get_ds_change_julian_date_diff (0, curr_ds_rule, curr_ds_rule->to_year, &ds_rule_julian_date, NULL);
      if (err_status != NO_ERROR)
        {
          goto exit;
        }

      ds_rule_date = FULL_DATE (ds_rule_julian_date, curr_ds_rule->at_time);
      diff = FULL_DATE (src_julian_date, src_time_sec) - ds_rule_date;

      if (second_best_applying_date_diff == -1 || diff < second_best_applying_date_diff)
        {
          second_best_applying_date_diff = diff;
          second_best_applying_ds_id = curr_ds_id;
          second_best_applying_ds_rule = curr_ds_rule;
        }

      /* this rule cannot apply */
      continue;
    }
      else if (src_year < curr_ds_rule->from_year && curr_ds_rule->in_month > TZ_MON_JAN)
    {
      continue;
    }

      year_to_apply_rule = get_year_to_apply_rule (src_year, curr_ds_rule);
      err_status =
    tz_get_ds_change_julian_date_diff (src_julian_date, curr_ds_rule, year_to_apply_rule, &ds_rule_julian_date,
                       &date_diff);
      if (err_status != NO_ERROR)
    {
      goto exit;
    }

      if (date_diff >= DATE_DIFF_MATCH_SAFE_THRESHOLD_SEC)
    {
      /* a date difference of at two days */
      rule_matched = true;
    }
      else if (ABS (date_diff) < DATE_DIFF_MATCH_SAFE_THRESHOLD_SEC)
    {
      int wall_ds_rule_id;
      int wall_safe_julian_date;
      full_date_t leap_interval;
      int ds_time_offset = 0;
      full_date_t ds_rule_date = 0;
      full_date_t utc_src_offset = 0;
      int save_time = 0;
      bool at_time_type_is_utc = false;
      int ds_rule_time_offset_curr_off_rule = 0, add_save_time = 0;
      int ds_rule_time_offset_prev_off_rule = 0;
      int add_leap_offset_rule_interval = 0;

      /* there may be an ambiguity : check the time deviation of a date before current candidate DS rule applies
       * (to be safe, 4 days before) */
      wall_safe_julian_date = src_julian_date - 2 * DATE_DIFF_MATCH_SAFE_THRESHOLD_DAYS;

      err_status =
        tz_fast_find_ds_rule (tzd, ds_ruleset, wall_safe_julian_date, src_year, src_month, &wall_ds_rule_id);
      if (err_status != NO_ERROR)
        {
          goto exit;
        }

      if (wall_ds_rule_id != -1)
        {
          assert (wall_ds_rule_id + ds_ruleset->index_start < tzd->ds_rule_count);
          wall_ds_rule = &(tzd->ds_rules[wall_ds_rule_id + ds_ruleset->index_start]);
          save_time = wall_ds_rule->save_time;
        }
      else if (prev_off_rule != NULL)
        {
          err_status = get_saving_time_from_offset_rule (prev_off_rule, tzd, &save_time);
          if (err_status != NO_ERROR)
        {
          goto exit;
        }
          add_leap_offset_rule_interval = leap_offset_rule_interval;
        }

      if (curr_ds_rule->at_time_type == TZ_TIME_TYPE_UTC)
        {
          at_time_type_is_utc = true;
        }

      if (wall_ds_rule != NULL && prev_off_rule != NULL)
        {
          bool utc_time = false;
          int ds_rule_time_offset;
          full_date_t date_diff;
          int wall_rule_julian_date;
          int add_save_time = 0;
          int year_to_apply_rule;

          year_to_apply_rule = get_year_to_apply_rule (src_year, wall_ds_rule);
          err_status =
        tz_get_ds_change_julian_date_diff (wall_safe_julian_date, wall_ds_rule, year_to_apply_rule,
                           &wall_rule_julian_date, &date_diff);
          if (err_status != NO_ERROR)
        {
          goto exit;
        }
          if (date_diff < 0)
        {
          year_to_apply_rule = year_to_apply_rule - 1;
          err_status =
            tz_get_ds_change_julian_date_diff (0, wall_ds_rule, year_to_apply_rule, &wall_rule_julian_date,
                               NULL);
          if (err_status != NO_ERROR)
            {
              goto exit;
            }
        }

          if (wall_ds_rule->at_time_type == TZ_TIME_TYPE_UTC)
        {
          utc_time = true;
        }

          ds_rule_time_offset = tz_offset (utc_time, prev_off_rule->until_time_type, gmt_std_offset_sec, 0);
          date_diff =
        FULL_DATE (wall_rule_julian_date,
               wall_ds_rule->at_time + ds_rule_time_offset) - FULL_DATE (prev_rule_julian_date,
                                             prev_rule_time_sec);

          if (date_diff < 0)
        {
          int prev_rule_save_time;

          err_status = get_saving_time_from_offset_rule (prev_off_rule, tzd, &prev_rule_save_time);
          if (err_status != NO_ERROR)
            {
              goto exit;
            }

          ds_rule_time_offset =
            tz_offset (at_time_type_is_utc, prev_off_rule->until_time_type, gmt_std_offset_sec, save_time);
          if (curr_ds_rule->at_time_type == TZ_TIME_TYPE_LOCAL_STD)
            {
              add_save_time = save_time;
            }

          if (FULL_DATE (ds_rule_julian_date, curr_ds_rule->at_time + add_save_time + ds_rule_time_offset) <=
              FULL_DATE (prev_rule_julian_date, prev_rule_time_sec))
            {
              save_time = prev_rule_save_time;
              add_leap_offset_rule_interval = leap_offset_rule_interval;
            }
        }
        }

      if (curr_ds_rule->at_time_type == TZ_TIME_TYPE_LOCAL_STD)
        {
          add_save_time = save_time;
        }

      if (prev_off_rule != NULL)
        {
          ds_rule_time_offset_prev_off_rule =
        tz_offset (at_time_type_is_utc, prev_off_rule->until_time_type, gmt_std_offset_sec, save_time);
          if (FULL_DATE
          (ds_rule_julian_date,
           curr_ds_rule->at_time + add_save_time + ds_rule_time_offset_prev_off_rule) >
          FULL_DATE (prev_rule_julian_date, prev_rule_time_sec))
        {
          add_leap_offset_rule_interval = 0;
        }
        }

      ds_rule_time_offset_curr_off_rule =
        tz_offset (at_time_type_is_utc, curr_off_rule->until_time_type, gmt_std_offset_sec, save_time);

      if (FULL_DATE (ds_rule_julian_date, curr_ds_rule->at_time + add_save_time + ds_rule_time_offset_curr_off_rule)
          >= FULL_DATE (rule_julian_date, rule_time_sec))
        {
          continue;
        }

      /* the difference between the input date and the rule date must be made in the same time reference, in our
       * case it is the local time reference */
      /* ds_time_offset is used to adjust the time at which the new daylight saving rule applies */

      if (curr_ds_rule->at_time_type == TZ_TIME_TYPE_UTC)
        {
          ds_time_offset = gmt_std_offset_sec + curr_ds_rule->save_time;
        }
      else if (curr_ds_rule->at_time_type == TZ_TIME_TYPE_LOCAL_STD)
        {
          ds_time_offset = curr_ds_rule->save_time;
        }
      else if (curr_ds_rule->at_time_type == TZ_TIME_TYPE_LOCAL_WALL)
        {
          /* wall clock: may indicate either the daylight time or standard time */
          ds_time_offset = curr_ds_rule->save_time - save_time;
        }
      if (curr_ds_rule->at_time_type != TZ_TIME_TYPE_UTC)
        {
          ds_time_offset += add_leap_offset_rule_interval;
        }

      if (src_is_utc == true)
        {
          /* UTC time always exists, thus we don't need to take into consideration leap interval overlap */
          leap_interval = 0;
          utc_src_offset = gmt_std_offset_sec + curr_ds_rule->save_time;
        }
      else
        {
          leap_interval = save_time - curr_ds_rule->save_time;
          leap_interval -= add_leap_offset_rule_interval;
        }

      ds_rule_date = FULL_DATE (ds_rule_julian_date, ds_time_offset + curr_ds_rule->at_time);
      date_diff = FULL_DATE (src_julian_date, src_time_sec + utc_src_offset) - ds_rule_date;

      if (date_diff >= 0)
        {
          rule_matched = true;

          if (leap_interval > 0 && date_diff < leap_interval)
        {
          is_in_leap_interval = true;
        }
        }
      else
        {
          if (leap_interval < 0 && ABS (date_diff) <= ABS (leap_interval))
        {
          int offset;

          offset = tz_offset (src_is_utc, curr_off_rule->until_time_type, gmt_std_offset_sec, 0);

          if (FULL_DATE (src_julian_date, src_time_sec + offset) < FULL_DATE (rule_julian_date, rule_time_sec))
            {
              /* invalid time, abort */
              err_status = ER_TZ_DURING_DS_LEAP;
              er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, err_status, 0);
              goto exit;
            }
          else
            {
              applying_ds_id = -1;
              second_best_applying_ds_id = -1;
              break;
            }
        }
        }
    }

    match_ds_rule:
      if (rule_matched)
    {
      assert (date_diff >= 0);

      if (applying_date_diff > 0 && date_diff > applying_date_diff && !applying_is_in_leap_interval)
        {
          /* a better rule was previously found */
          continue;
        }

      if (is_in_leap_interval || applying_is_in_leap_interval)
        {
          if (check_user_dst)
        {
          rule_matched =
            tz_check_ds_match_string (curr_off_rule, curr_ds_rule, tz_info->zone.dst_str,
                          ds_ruleset->default_abrev);
        }
          else if (applying_ds_id != -1)
        {
          /* ambiguity : user did not specified a daylight saving; a previous rule was matched, but also the
           * current one is; We are during the DS interval change, we choose the one before (timewise) DS
           * change is performed */
          if (applying_date_diff > date_diff || applying_with_prev_year)
            {
              rule_matched = false;
            }
        }
        }

      if (rule_matched
          && (applying_date_diff < 0 || date_diff < applying_date_diff || applying_is_in_leap_interval))
        {
          if (is_in_leap_interval == true && wall_ds_rule != NULL && check_user_dst == true
          && tz_check_ds_match_string (curr_off_rule, wall_ds_rule, tz_info->zone.dst_str,
                           ds_ruleset->default_abrev) == true)
        {
          continue;
        }
          /* this is the best rule so far (ignoring user DST) */
          applying_date_diff = date_diff;
          applying_ds_rule = curr_ds_rule;
          applying_ds_id = curr_ds_id;
          applying_is_in_leap_interval = is_in_leap_interval;
          if (check_prev_year == false)
        {
          applying_with_prev_year = true;
        }
        }
    }
      else if (curr_ds_rule->from_year < src_year && check_prev_year == true && date_diff < 0)
    {
      err_status =
        tz_get_ds_change_julian_date_diff (src_julian_date, curr_ds_rule, src_year - 1, &ds_rule_julian_date,
                           &date_diff);
      if (err_status != NO_ERROR)
        {
          goto exit;
        }

      assert (date_diff >= 0);
      rule_matched = true;
      check_prev_year = false;

      goto match_ds_rule;
    }
    }

  if (applying_ds_id == -1)
    {
      applying_ds_id = second_best_applying_ds_id;
      applying_ds_rule = second_best_applying_ds_rule;
    }

  if (applying_ds_id != -1)
    {
      save_time = applying_ds_rule->save_time;
    }
  src_offset_curr_off_rule = tz_offset (src_is_utc, curr_off_rule->until_time_type, gmt_std_offset_sec, save_time);
  if (prev_off_rule != NULL)
    {
      src_offset_prev_off_rule = tz_offset (src_is_utc, prev_off_rule->until_time_type, gmt_std_offset_sec, save_time);
    }

  if (applying_ds_id != -1)
    {
      full_date_t offset_rule_diff;
      int add_ds_save_time = 0;
      full_date_t leap = 0;

      if (FULL_DATE (src_julian_date, src_time_sec + src_offset_curr_off_rule) >=
      FULL_DATE (rule_julian_date, rule_time_sec))
    {
      APPLY_NEXT_OFF_RULE ();
      goto detect_dst;
    }

      offset_rule_diff =
    FULL_DATE (src_julian_date, src_time_sec + src_offset_prev_off_rule) - FULL_DATE (prev_rule_julian_date,
                                              prev_rule_time_sec);

      if (prev_off_rule != NULL)
    {
      if (prev_off_rule->until_time_type == TZ_TIME_TYPE_LOCAL_WALL)
        {
          int prev_rule_save_time = 0;

          add_ds_save_time = save_time;
          if (offset_rule_diff <= 2 * SECONDS_IN_A_DAY)
        {
          err_status = get_saving_time_from_offset_rule (prev_off_rule, tzd, &prev_rule_save_time);
          if (err_status != NO_ERROR)
            {
              goto exit;
            }
        }
          add_ds_save_time -= prev_rule_save_time;
          leap = leap_offset_rule_interval + add_ds_save_time;
        }
      else if (prev_off_rule->until_time_type == TZ_TIME_TYPE_LOCAL_STD)
        {
          leap = leap_offset_rule_interval;
        }
    }

      if (try_offset_rule_overlap == false && leap >= 0 && (offset_rule_diff < leap))
    {
      /* invalid time, abort */
      err_status = ER_TZ_DURING_OFFSET_RULE_LEAP;
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, err_status, 0);
      goto exit;
    }

      if (check_user_dst)
    {
      if (tz_check_ds_match_string
          (curr_off_rule, applying_ds_rule, tz_info->zone.dst_str, ds_ruleset->default_abrev) == false)
        {
          if (next_off_rule == NULL || try_offset_rule_overlap == true)
        {
          err_status = ER_TZ_INVALID_COMBINATION;
          er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, err_status, 0);
          goto exit;
        }
          /* try the next offset rule to see if we have an ambiguous time */
          else
        {
          try_offset_rule_overlap = true;
          APPLY_NEXT_OFF_RULE ();
          goto detect_dst;
        }
        }
      /* we need to see if we have an overlap with the previous rule in this case */
      else if (try_offset_rule_overlap == true)
        {
          bool overlap = false;

          if (ABS (offset_rule_diff) <= 3 * SECONDS_IN_A_DAY)
        {
          int prev_rule_save_time = 0;
          int save_time_diff = 0;

          err_status = get_saving_time_from_offset_rule (prev_off_rule, tzd, &prev_rule_save_time);
          if (err_status != NO_ERROR)
            {
              goto exit;
            }

          save_time_diff = save_time - prev_rule_save_time;
          overlap =
            is_in_overlap_interval (prev_off_rule->until_time_type, offset_rule_diff, leap_offset_rule_interval,
                        save_time_diff);
        }

          if (overlap == false)
        {
          /* invalid time, abort */
          err_status = ER_TZ_INVALID_COMBINATION;
          er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, err_status, 0);
          goto exit;
        }
        }
    }
    }
  else
    {
      /* try next GMT offset zone */
      if (next_off_rule == NULL
      || (FULL_DATE (src_julian_date, src_time_sec + src_offset_curr_off_rule) <
          FULL_DATE (rule_julian_date, rule_time_sec)))
    {
      full_date_t offset_rule_diff;
      int add_ds_save_time = 0;
      full_date_t leap = 0;

      offset_rule_diff =
        FULL_DATE (src_julian_date, src_time_sec + src_offset_prev_off_rule) - FULL_DATE (prev_rule_julian_date,
                                                  prev_rule_time_sec);

      if (prev_off_rule != NULL)
        {
          if (prev_off_rule->until_time_type == TZ_TIME_TYPE_LOCAL_WALL)
        {
          int prev_rule_save_time = 0;

          add_ds_save_time = save_time;
          if (offset_rule_diff <= 2 * SECONDS_IN_A_DAY)
            {
              err_status = get_saving_time_from_offset_rule (prev_off_rule, tzd, &prev_rule_save_time);
              if (err_status != NO_ERROR)
            {
              goto exit;
            }
            }
          add_ds_save_time -= prev_rule_save_time;
          leap = leap_offset_rule_interval + add_ds_save_time;
        }
          else if (prev_off_rule->until_time_type == TZ_TIME_TYPE_LOCAL_STD)
        {
          leap = leap_offset_rule_interval;
        }
        }

      if (try_offset_rule_overlap == false && leap >= 0 && (offset_rule_diff < leap))
        {
          /* invalid time, abort */
          err_status = ER_TZ_DURING_OFFSET_RULE_LEAP;
          er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, err_status, 0);
          goto exit;
        }

      if (curr_ds_id == ds_ruleset->count)
        {
          applying_ds_id = get_closest_ds_rule (src_julian_date, src_time_sec, ds_ruleset, tzd, BACKWARD);
        }
      else
        {
          applying_ds_id = get_closest_ds_rule (src_julian_date, src_time_sec, ds_ruleset, tzd, FORWARD);
        }

      assert (applying_ds_id + ds_ruleset->index_start < tzd->ds_rule_count);
      applying_ds_rule = &(tzd->ds_rules[applying_ds_id + ds_ruleset->index_start]);

      /* check if provided DS specifier matches the offset rule format */
      if (tz_info->zone.dst_str[0] != '\0'
          && tz_check_ds_match_string (curr_off_rule, applying_ds_rule, tz_info->zone.dst_str,
                       ds_ruleset->default_abrev) == false)
        {
          if (next_off_rule == NULL || try_offset_rule_overlap == true)
        {
          err_status = ER_TZ_INVALID_DST;
          er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, err_status, 0);
          goto exit;
        }
          else
        {
          try_offset_rule_overlap = true;
          APPLY_NEXT_OFF_RULE ();
          goto detect_dst;
        }
        }
      else if (tz_info->zone.dst_str[0] != '\0'
           && tz_check_ds_match_string (curr_off_rule, applying_ds_rule, tz_info->zone.dst_str,
                        ds_ruleset->default_abrev) == true && try_offset_rule_overlap == true)
        {
          bool overlap = false;

          if (ABS (offset_rule_diff) <= 3 * SECONDS_IN_A_DAY)
        {
          int prev_rule_save_time = 0;
          int save_time_diff = 0;

          err_status = get_saving_time_from_offset_rule (prev_off_rule, tzd, &prev_rule_save_time);
          if (err_status != NO_ERROR)
            {
              goto exit;
            }

          save_time_diff = save_time - prev_rule_save_time;
          overlap =
            is_in_overlap_interval (prev_off_rule->until_time_type, offset_rule_diff, leap_offset_rule_interval,
                        save_time_diff);
        }
          if (overlap == false)
        {
          /* invalid time, abort */
          err_status = ER_TZ_INVALID_COMBINATION;
          er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, err_status, 0);
          goto exit;
        }
        }

      goto exit;
    }
      APPLY_NEXT_OFF_RULE ();
      goto detect_dst;
    }

  assert (applying_ds_rule != NULL);

  total_offset_sec += applying_ds_rule->save_time;

  assert (curr_offset_id != -1);
  assert (applying_ds_id != -1);

exit:
  if (err_status == NO_ERROR)
    {
      if (curr_offset_id >= 0)
    {
      if (curr_off_rule->ds_type == DS_TYPE_FIXED)
        {
          total_offset_sec += curr_off_rule->ds_ruleset;
        }

      tz_info->zone.offset_id = curr_offset_id;

      if (applying_ds_id >= 0)
        {
          tz_info->zone.dst_id = applying_ds_id;
        }
    }

      if (only_tz_adjust == true)
    {
      *dest_dt = *src_dt;
    }
      else
    {
      if (src_dt->date == 0)
        {
          assert (src_dt->time == 0);
          *dest_dt = *src_dt;
        }
      else
        {
          err_status =
        db_add_int_to_datetime ((DB_DATETIME *) src_dt, -1000 * TIME_OFFSET (src_is_utc, total_offset_sec),
                    dest_dt);
        }
    }
    }

  return err_status;
}

/*
 * tz_conv_tz_datetime_w_zone_info () - Converts a source DATETIME from one timezone to another
 *
 * Return: error code
 * src_dt(in): object containing source datetime value;
 * src_zone_info_in(in): (partial) decoded timezone info associated with source datetime
 * dest_zone_info_in(in): (partial) decoded timezone info for the desired timezone
 * dest_dt(out): destination datetime value
 * src_zone_info_out(out): complete timezone information for source
 * dest_zone_info_out(out): complete timezone information for destination
 */
static int
tz_conv_tz_datetime_w_zone_info (const DB_DATETIME * src_dt, const TZ_DECODE_INFO * src_zone_info_in,
                 const TZ_DECODE_INFO * dest_zone_info_in, DB_DATETIME * dest_dt,
                 TZ_DECODE_INFO * src_zone_info_out, TZ_DECODE_INFO * dest_zone_info_out)
{
  int err_status = NO_ERROR;
  DB_DATETIME dt_utc;
  TZ_DECODE_INFO tmp_zone_info;

  assert (src_dt != NULL);
  assert (src_zone_info_in != NULL);
  assert (dest_zone_info_in != NULL);
  assert (dest_dt != NULL);

  tmp_zone_info = *src_zone_info_in;
  if (src_zone_info_in->type == TZ_REGION_OFFSET && src_zone_info_in->offset == 0)
    {
      /* the source is UTC */
      dt_utc = *src_dt;
    }
  else
    {
      /* convert to UTC */
      err_status = tz_datetime_utc_conv (src_dt, &tmp_zone_info, false, false, &dt_utc);
      if (err_status != NO_ERROR)
    {
      goto exit;
    }
    }

  if (src_zone_info_out != NULL)
    {
      *src_zone_info_out = tmp_zone_info;
    }

  assert (TZ_IS_ZONE_VALID_DECODE_INFO (src_zone_info_in));

  if (src_zone_info_in->type == dest_zone_info_in->type
      && ((src_zone_info_in->type == TZ_REGION_ZONE
       && src_zone_info_in->zone.zone_id == dest_zone_info_in->zone.zone_id)
      || (src_zone_info_in->type == TZ_REGION_OFFSET && src_zone_info_in->offset == dest_zone_info_in->offset)))
    {
      /* same zone, copy value and zone information */
      *dest_dt = *src_dt;
      if (dest_zone_info_out != NULL)
    {
      *dest_zone_info_out = tmp_zone_info;
    }
      return err_status;
    }

  tmp_zone_info = *dest_zone_info_in;
  if (dest_zone_info_in->type == TZ_REGION_OFFSET && dest_zone_info_in->offset == 0)
    {
      /* the destination is UTC */
      *dest_dt = dt_utc;
    }
  else
    {
      err_status = tz_datetime_utc_conv (&dt_utc, &tmp_zone_info, true, false, dest_dt);
    }

  if (dest_zone_info_out != NULL)
    {
      *dest_zone_info_out = tmp_zone_info;
    }

exit:
  return err_status;
}

/*
 * tz_conv_tz_datetime_w_region () - Converts a source DATETIME from one timezone to another (uses region arguments)
 *
 * Return: error code
 * src_dt(in): object containing source datetime value;
 * src_tz_region(in): timezone region associated with source datetime
 * dest_tz_region(in): desired timezone region
 * dest_dt(out): destination datetime value
 * src_tz_id_out(out): compressed timezone identifier of the source
 * dest_tz_id_out(out): compressed timezone identifier of the destination
 */
int
tz_conv_tz_datetime_w_region (const DB_DATETIME * src_dt, const TZ_REGION * src_tz_region,
                  const TZ_REGION * dest_tz_region, DB_DATETIME * dest_dt, TZ_ID * src_tz_id_out,
                  TZ_ID * dest_tz_id_out)
{
  int err_status = NO_ERROR;
  TZ_DECODE_INFO src_zone_info;
  TZ_DECODE_INFO dest_zone_info_in;
  TZ_DECODE_INFO src_zone_info_out = { TZ_REGION_OFFSET, {0} };
  TZ_DECODE_INFO dest_zone_info_out = { TZ_REGION_OFFSET, {0} };

  assert (src_dt != NULL);
  assert (src_tz_region != NULL);
  assert (dest_tz_region != NULL);
  assert (dest_dt != NULL);

  tz_decode_tz_region (src_tz_region, &src_zone_info);
  tz_decode_tz_region (dest_tz_region, &dest_zone_info_in);

  err_status =
    tz_conv_tz_datetime_w_zone_info (src_dt, &src_zone_info, &dest_zone_info_in, dest_dt, &src_zone_info_out,
                     &dest_zone_info_out);
  if (src_tz_id_out != NULL)
    {
      tz_encode_tz_id (&src_zone_info_out, src_tz_id_out);
    }

  if (dest_tz_id_out != NULL)
    {
      tz_encode_tz_id (&dest_zone_info_out, dest_tz_id_out);
    }

  return err_status;
}

/*
 * tz_conv_tz_datetime_w_zone_name () - Converts a source DATETIME from one timezone to another
 *
 * Return: error code
 * src_dt(in): object containing source datetime value
 * source_zone(in): source timezone string
 * len_source(in): length of source timezone string
 * dest_zone(in): destination timezone string
 * len_dest(in): length of destination timezone string
 * dest_dt(out): object containing output datetime value
 */
int
tz_conv_tz_datetime_w_zone_name (const DB_DATETIME * src_dt, const char *source_zone, int len_source,
                 const char *dest_zone, int len_dest, DB_DATETIME * dest_dt)
{
  int err_status;
  TZ_REGION source, dest;

  err_status = tz_str_to_region (source_zone, len_source, &source);
  if (err_status != NO_ERROR)
    {
      return err_status;
    }

  err_status = tz_str_to_region (dest_zone, len_dest, &dest);
  if (err_status != NO_ERROR)
    {
      return err_status;
    }

  return tz_conv_tz_datetime_w_region (src_dt, &source, &dest, dest_dt, NULL, NULL);
}

/*
 * tz_explain_tz_id () - get timezone information
 *
 * Return: print characters
 * tz_id(in): complete valid zone identifier
 * tzr(out): buffer string for timezone region info
 * tzr_size(in): size of tz_str
 * tzdst(out): buffer string for daylight saving time info
 * tzdst_size (in) : size of tzdst
 * tzh(out): time zone hour offset
 * tzm(out): time zone minute offset
 * Return: error or no error
 */
int
tz_explain_tz_id (const TZ_ID * tz_id, char *tzr, const int tzr_size, char *tzdst, const int tzdst_size, int *tzh,
          int *tzm)
{
#define LEN_MIN_HOUR 6
#define LEN_MIN_HOUR_SEC 9
  const TZ_DATA *tzd = tz_get_data ();
  int zone_name_size;
  int zone_id;
  int zone_offset_id;
  int dst_format_size;
  TZ_TIMEZONE *timezone = NULL;
  TZ_OFFSET_RULE *zone_off_rule;
  TZ_DECODE_INFO tz_info;
  char dst_format[TZ_MAX_FORMAT_SIZE];
  const char *p_dst_format = NULL;
  int total_offset = 0;
  int er_status = NO_ERROR;

  tz_decode_tz_id (tz_id, true, &tz_info);
  if (tz_info.type == TZ_REGION_OFFSET)
    {
      int offset = tz_info.offset;
      int hour, min, sec;
      char sign;

      if (tz_info.offset == TZ_INVALID_OFFSET)
    {
#if !defined (CS_MODE)
      assert (BO_IS_SERVER_RESTARTED ());
#endif
      er_status = ER_TZ_INTERNAL_ERROR;
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, er_status, 0);
      return er_status;
    }

      sign = (tz_info.offset < 0) ? '-' : '+';

      if (offset < TZ_MIN_OFFSET || offset > TZ_MAX_OFFSET)
    {
      er_status = ER_DATE_CONVERSION;
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, er_status, 0);
      return er_status;
    }

      offset = (offset < 0) ? (-offset) : offset;
      db_time_decode ((DB_TIME *) (&offset), &hour, &min, &sec);

      *tzh = hour;
      *tzm = min;
      if (sign == '-')
    {
      *tzh = -(*tzh);
      *tzm = -(*tzm);
    }

      if (sec > 0)
    {
      sprintf (tzr, "%c%02d:%02d:%02d", sign, hour, min, sec);
      tzr[LEN_MIN_HOUR_SEC] = '\0';
    }
      else
    {
      sprintf (tzr, "%c%02d:%02d", sign, hour, min);
      tzr[LEN_MIN_HOUR] = '\0';
    }
      return er_status;
    }

  if (tzd == NULL)
    {
      er_status = ER_TZ_LOAD_ERROR;
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, er_status, 1, "timezone lib not loaded");
      return er_status;
    }

  assert (tz_info.zone.p_zone_off_rule != NULL);
  total_offset = tz_info.zone.p_zone_off_rule->gmt_off;
  if (tz_info.zone.p_zone_off_rule->ds_type == DS_TYPE_RULESET_ID && tz_info.zone.p_ds_rule != NULL)
    {
      total_offset += tz_info.zone.p_ds_rule->save_time;
    }
  *tzh = total_offset / 3600;
  *tzm = (total_offset % 3600) / 60;
  zone_id = tz_info.zone.zone_id;
  zone_offset_id = tz_info.zone.offset_id;
  zone_name_size = strlen (tzd->names[zone_id].name);

  timezone = &(tzd->timezones[tzd->names[zone_id].zone_id]);
  zone_off_rule = &(tzd->offset_rules[timezone->gmt_off_rule_start + zone_offset_id]);

  p_dst_format = zone_off_rule->std_format;
  if (zone_off_rule->ds_type == DS_TYPE_RULESET_ID)
    {
      TZ_DS_RULESET *ds_ruleset = NULL;
      TZ_DS_RULE *ds_rule = NULL;
      int dst_id = tz_info.zone.dst_id;
      const char *ds_abbr = NULL;

      ds_ruleset = &(tzd->ds_rulesets[zone_off_rule->ds_ruleset]);
      if (dst_id < ds_ruleset->count)
    {
      ds_rule = &(tzd->ds_rules[dst_id + ds_ruleset->index_start]);
      ds_abbr = ds_rule->letter_abbrev;
    }

      if (zone_off_rule->var_format != NULL)
    {
      if (ds_abbr == NULL)
        {
          p_dst_format = NULL;
        }
      else
        {
          snprintf (dst_format, sizeof (dst_format) - 1, zone_off_rule->var_format,
            (ds_abbr != NULL && *ds_abbr != '-') ? ds_abbr : "");
          p_dst_format = dst_format;
        }
    }
      else
    {
      if (ds_rule != NULL && ds_rule->save_time != 0 && zone_off_rule->save_format != NULL)
        {
          p_dst_format = zone_off_rule->save_format;
        }
    }
    }

  if (p_dst_format != NULL)
    {
      dst_format_size = strlen (p_dst_format);
    }
  else
    {
      dst_format_size = 0;
    }

  if (zone_name_size + 1 > tzr_size || dst_format_size + 1 > tzdst_size)
    {
      er_status = ER_FAILED;
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, er_status, 0);
      return er_status;
    }

  if (p_dst_format != NULL)
    {
      snprintf (tzr, tzr_size, "%s", tzd->names[zone_id].name);
      snprintf (tzdst, tzdst_size, "%s", p_dst_format);
      tzdst[dst_format_size] = '\0';
    }
  else
    {
      snprintf (tzr, tzr_size, "%s", tzd->names[zone_id].name);
    }

  tzr[zone_name_size] = '\0';

  return er_status;
#undef LEN_MIN_HOUR
#undef LEN_MIN_HOUR_SEC
}

/*
 * tz_create_datetimetz_from_offset () - creates a datetime with timezone info from a timezone hour offset and a
 *                   a timezone minute offset
 *
 *
 * Return: error or no error
 * dt (in): local datetime value
 * tzh (in): timezone hour offset
 * tzm (in): timezone minute offset
 * dt_tz (out): object containing datetime value (adjusted to UTC) and
 *          timezone info
 *
 */
int
tz_create_datetimetz_from_offset (const DB_DATETIME * dt, const int tzh, const int tzm, DB_DATETIMETZ * dt_tz)
{
  int err_status = NO_ERROR;
  DB_DATETIME utc_dt;
  TZ_DECODE_INFO tz_info;

  tz_info.type = TZ_REGION_OFFSET;
  tz_info.offset = tzh * 3600 + tzm * 60;

  if (tz_info.offset < TZ_MIN_OFFSET || tz_info.offset > TZ_MAX_OFFSET)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_DATE_CONVERSION, 0);
      err_status = ER_DATE_CONVERSION;
      return err_status;
    }

  err_status = tz_datetime_utc_conv (dt, &tz_info, false, false, &utc_dt);
  if (err_status != NO_ERROR)
    {
      return err_status;
    }
  tz_encode_tz_id (&tz_info, &(dt_tz->tz_id));
  dt_tz->datetime = utc_dt;
  return err_status;
}

/*
 * tz_create_timestamptz_from_offset () - creates a timestamp with timezone info from a timezone hour and
 *                    a timezone minute offset
 *
 * Return: error code
 * date(in): local date value
 * time(in): local time value
 * tzh(in): timezone hour offset
 * tzm(in): timezone minute offset
 * timestamp_tz(out): object containing timestamp value (adjusted to UTC) and timezone info
 *
 */
int
tz_create_timestamptz_from_offset (const DB_DATE * date, const DB_TIME * time, const int tzh, const int tzm,
                   DB_TIMESTAMPTZ * timestamp_tz)
{
  int err_status = NO_ERROR;
  DB_DATETIME dt, utc_dt;
  TZ_DECODE_INFO tz_info;
  DB_DATE date_utc;
  DB_TIME time_utc;

  tz_info.type = TZ_REGION_OFFSET;
  tz_info.offset = tzh * 3600 + tzm * 60;

  if (tz_info.offset < TZ_MIN_OFFSET || tz_info.offset > TZ_MAX_OFFSET)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_DATE_CONVERSION, 0);
      err_status = ER_DATE_CONVERSION;
      return err_status;
    }

  dt.date = *date;
  dt.time = (*time) * 1000;

  err_status = tz_datetime_utc_conv (&dt, &tz_info, false, false, &utc_dt);
  if (err_status != NO_ERROR)
    {
      goto exit;
    }
  date_utc = utc_dt.date;
  time_utc = utc_dt.time / 1000;

  err_status = db_timestamp_encode_utc (&date_utc, &time_utc, &timestamp_tz->timestamp);
  if (err_status != NO_ERROR)
    {
      goto exit;
    }
  tz_encode_tz_id (&tz_info, &(timestamp_tz->tz_id));

exit:
  return err_status;
}

/*
 * tz_get_best_match_zone() - searches for the longest timezone matching the start of the string
 *
 * Returns: matched zone id (negative if not matched)
 * name(in): string (null terminated)
 * size(out): matched size
 *
 */
int
tz_get_best_match_zone (const char *name, int *size)
{
  const TZ_DATA *tzd;
  int index_bot, index_top;
  int cmp_res;

  assert (name != NULL);

  tzd = tz_get_data ();
  if (tzd == NULL)
    {
      return -1;
    }

  index_bot = 0;
  index_top = tzd->name_count - 1;

  while (index_bot <= index_top)
    {
      int mid = index_bot + ((index_top - index_bot) >> 1);
      cmp_res = starts_with (tzd->names[mid].name, name);

      if (cmp_res <= 0)
    {
      index_bot = mid + 1;
    }
      else
    {
      index_top = mid - 1;
    }
    }

  if (index_bot == 0 || starts_with (tzd->names[index_bot - 1].name, name) != 0)
    {
      return -1;
    }

  *size = strlen (tzd->names[index_bot - 1].name);
  return index_bot - 1;
}

/*
 * tz_create_datetimetz_from_zoneid_and_tzd () - creates a datetime with timezone info from a datetime, a zone id
 *                           and daylight saving time info
 *
 *
 * Return: error or no error
 * dt(in): local datetime value
 * default_tz_region(in): default timezone region (used when zone_id is invalid)
 * zone_id(in): the zone id
 * tzd(in): daylight saving time info string
 * tzd_len(in): length of the tzd string
 * dt_tz(out): object containing datetime value(adjusted to UTC) and timezone info
 */
int
tz_create_datetimetz_from_zoneid_and_tzd (const DB_DATETIME * dt, TZ_REGION * default_tz_region, const int zone_id,
                      const char *tzd, const int tzd_len, bool is_time_tz, DB_DATETIMETZ * dt_tz)
{
  int err_status = NO_ERROR;
  DB_DATETIME utc_dt;
  TZ_DECODE_INFO tz_info;
  int offset = 0;

  if (zone_id != -1)
    {
      tz_info.type = TZ_REGION_ZONE;
      tz_info.zone.zone_id = zone_id;
      tz_info.zone.dst_id = TZ_DS_ID_MAX;
      tz_info.zone.offset_id = TZ_OFFSET_ID_MAX;
    }
  else
    {
      tz_decode_tz_region (default_tz_region, &tz_info);
    }

  if (tzd != NULL)
    {
      if (tzd_len + 1 > (int) sizeof (tz_info.zone.dst_str))
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TZ_INVALID_DST, 0);
      return ER_TZ_INVALID_DST;
    }
      strncpy (tz_info.zone.dst_str, tzd, tzd_len);
      tz_info.zone.dst_str[tzd_len] = '\0';
    }
  else
    {
      tz_info.zone.dst_str[0] = '\0';
    }

  err_status = tz_datetime_utc_conv (dt, &tz_info, false, false, &utc_dt);
  if (err_status != NO_ERROR)
    {
      return err_status;
    }

  if (is_time_tz == true)
    {
      offset = (int) (dt->date - utc_dt.date) * 3600 * 24 + (int) (dt->time - utc_dt.time) / 1000;
      tz_info.type = TZ_REGION_OFFSET;
      tz_info.offset = offset;
    }

  tz_encode_tz_id (&tz_info, &(dt_tz->tz_id));
  dt_tz->datetime = utc_dt;

  return err_status;
}

/*
 * tz_create_timestamptz_from_zoneid_and_tzd () - creates a timestamp with timezone info from a datetime, a zone id
 *                            and daylight saving time info
 *
 * Return: error or no error
 * date(in): local date value
 * time(in): local time value
 * default_tz_region(in): default timezone region (used when zone_id is invalid)
 * zone_id(in): the zone id
 * tzd(in): daylight saving time info string
 * tzd_len(in): length of the tzd string
 * timestamp_tz(out): object containing timestamp value(adjusted to UTC) and timezone info
 */
int
tz_create_timestamptz_from_zoneid_and_tzd (const DB_DATE * date, const DB_TIME * time, TZ_REGION * default_tz_region,
                       const int zone_id, const char *tzd, const int tzd_len,
                       DB_TIMESTAMPTZ * timestamp_tz)
{
  int err_status = NO_ERROR;
  DB_DATETIME utc_dt, dt;
  TZ_DECODE_INFO tz_info;
  DB_DATE date_utc;
  DB_TIME time_utc;

  if (zone_id != -1)
    {
      tz_info.type = TZ_REGION_ZONE;
      tz_info.zone.zone_id = zone_id;
      tz_info.zone.dst_id = TZ_DS_ID_MAX;
      tz_info.zone.offset_id = TZ_OFFSET_ID_MAX;
    }
  else
    {
      tz_decode_tz_region (default_tz_region, &tz_info);
    }

  if (tzd != NULL)
    {
      if (tzd_len + 1 > (int) sizeof (tz_info.zone.dst_str))
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TZ_INVALID_DST, 0);
      return ER_TZ_INVALID_DST;
    }
      strncpy (tz_info.zone.dst_str, tzd, tzd_len);
      tz_info.zone.dst_str[tzd_len] = '\0';
    }
  else
    {
      tz_info.zone.dst_str[0] = '\0';
    }

  dt.date = *date;
  dt.time = *(time) * 1000;
  err_status = tz_datetime_utc_conv (&dt, &tz_info, false, false, &utc_dt);

  if (err_status != NO_ERROR)
    {
      return err_status;
    }

  date_utc = utc_dt.date;
  time_utc = utc_dt.time / 1000;
  err_status = db_timestamp_encode_utc (&date_utc, &time_utc, &(timestamp_tz->timestamp));
  if (err_status != NO_ERROR)
    {
      return err_status;
    }

  tz_encode_tz_id (&tz_info, &(timestamp_tz->tz_id));
  return err_status;
}

/*
 * starts_with() - verifies if prefix is a prefix of str
 *                 otherwise, verifies if prefix is lexicographically greater or smaller than str
 * Return 0 if prefix is a prefix of str
 * Return > 0 if prefix is lexicographically greater than str
 * Return < 0 if prefix is lexicographically smaller than str
 * prefix(in): the prefix string
 * str(in): the string to search for the prefix
 *
 */
static int
starts_with (const char *prefix, const char *str)
{
  while (*prefix != '\0' && (*prefix) == (*str))
    {
      prefix++, str++;
    }
  if (*prefix == '\0')
    {
      return 0;
    }

  return *(const unsigned char *) prefix - *(const unsigned char *) str;
}

#if defined(LINUX)
/*
 * find_timezone_from_clock() - Tries to put in timezone_name the local timezone taken from /etc/sysconfig/clock
 * Return >= 0 if no error
 * Return < 0 if error
 * timezone_name(out): the local timezone
 * buf_len(in): number of elements in the buffer without the null ending
 *              character
 */
static int
find_timezone_from_clock (char *timezone_name, int buf_len)
{
#define MAX_LINE_SIZE 256
  int cnt = 0;
  FILE *fp;
  char str[MAX_LINE_SIZE + 1];
  char *zone = NULL;

  fp = fopen ("/etc/sysconfig/clock", "r");
  if (fp == NULL)
    {
      return -1;
    }

  while (fgets (str, MAX_LINE_SIZE + 1, fp) != NULL)
    {
      if (*str == '#')
    {
      continue;
    }
      if (cnt == 0)
    {
      zone = strstr (str, "ZONE");
      if (zone != NULL)
        {
          zone += 4;
          while (*zone == ' ')
        {
          zone++;
        }

          if (*zone != '=')
        {
          goto error;
        }

          zone++;
          while (*zone == ' ')
        {
          zone++;
        }

          if (*zone != '"')
        {
          goto error;
        }

          zone++;
          while (*zone != '"' && *zone != '\0')
        {
          timezone_name[cnt++] = *zone;
          zone++;
          if (cnt > buf_len)
            {
              goto error;
            }
        }
          if (*zone == '"')
        {
          break;
        }
        }
    }
      else
    {
      zone = str;
      while (*zone != '"' && *zone != '\0')
        {
          timezone_name[cnt++] = *zone;
          zone++;
          if (cnt > buf_len)
        {
          goto error;
        }
        }
      break;
    }
    }

  timezone_name[cnt] = '\0';
  fclose (fp);
  return 0;

error:
  fclose (fp);
  return -1;
#undef MAX_LINE_SIZE
}

/*
 * find_timezone_from_localtime() - Tries to put in timezona_name the local timezone taken from /etc/localtime
 * Return >= 0 if no error
 * Return < 0 if error
 * timezone_name(out): the local timezone
 * buf_len(in): number of elements in the buffer without the null ending
 *              character
 */
static int
find_timezone_from_localtime (char *timezone_name, int buf_len)
{
  char linkname[PATH_MAX + 1];
  char *p;
  const char *zone_info = "zoneinfo";
  bool start = false;
  ssize_t r;
  int cnt = 0;

  memset (linkname, 0, sizeof (linkname));
  r = readlink ("/etc/localtime", linkname, PATH_MAX + 1);
  if (r < 0)
    {
      return r;
    }

  if (r > PATH_MAX)
    {
      return -1;
    }

  p = strtok (linkname, "/");
  while (p != NULL)
    {
      if (!start)
    {
      if (strcmp (p, zone_info) == 0)
        {
          start = true;
        }
    }
      else
    {
      while (*p != '\0')
        {
          if (cnt > buf_len)
        {
          goto error;
        }
          timezone_name[cnt++] = *p;
          p++;
        }
      if (cnt > buf_len)
        {
          goto error;
        }
      timezone_name[cnt++] = '/';
    }
      p = strtok (NULL, "/");
    }

  timezone_name[cnt - 1] = '\0';
  return 0;

error:
  return -1;
}
#endif

#if defined(WINDOWS)
/*
 * tz_get_iana_zone_id_by_windows_zone() - returns the ID of the leftmost timezone corresponding to the specified
 *                     windows zone name
 *
 * Returns: ID of zone, or -1 if not found
 * windows_zone_name(in): windows zone name to search for (null-terminated)
 */
static int
tz_get_iana_zone_id_by_windows_zone (const char *windows_zone_name)
{
  const TZ_DATA *tzd;
  int mid = 0;
  int index_bot, index_top;
  int cmp_res;

  assert (windows_zone_name != NULL);

  tzd = tz_get_data ();
  if (tzd == NULL)
    {
      return -1;
    }

  index_bot = 0;
  index_top = tzd->windows_iana_map_count - 1;

  while (index_bot <= index_top)
    {
      mid = index_bot + ((index_top - index_bot) >> 1);
      cmp_res = strcmp (windows_zone_name, tzd->windows_iana_map[mid].windows_zone);

      if (cmp_res <= 0)
    {
      index_top = mid - 1;
    }
      else
    {
      index_bot = mid + 1;
    }
    }

  if (strcmp (tzd->windows_iana_map[index_bot].windows_zone, windows_zone_name) != 0)
    {
      return -1;
    }
  return tzd->windows_iana_map[index_bot].iana_zone_id;
}
#endif

/*
 * resolve_timezone() - Puts in timezone the local timezone
 * Return >= 0 if no error
 * Return < 0 if error
 * timezone(out): the local timezone
 * buf_len(in): number of elements in the buffer without the null ending character
 */
int
tz_resolve_os_timezone (char *timezone, int buf_len)
{
#if defined (WINDOWS)
  const TZ_DATA *tz_data;
  int len_iana_zone, iana_zone_id;
  char tzname[64];
  size_t tzret;

  tz_data = tz_get_data ();
  tzset ();
  _get_tzname (&tzret, tzname, sizeof (tzname), 0);
  iana_zone_id = tz_get_iana_zone_id_by_windows_zone (tzname);
  if (iana_zone_id < 0)
    {
      return iana_zone_id;
    }
  len_iana_zone = strlen (tz_data->timezone_names[iana_zone_id]);
  if (buf_len < len_iana_zone)
    {
      return -1;
    }
  memmove (timezone, tz_data->timezone_names[iana_zone_id], len_iana_zone);
  timezone[len_iana_zone] = '\0';
  return 0;
#elif defined(AIX)
  char *env = NULL;
  int ret = 0;

  env = getenv ("TZ");
  if (env == NULL)
    {
      return -1;
    }
  else
    {
      strncpy (timezone, env, buf_len);
      timezone[buf_len] = '\0';
    }
  ret = tz_get_zone_id_by_name (timezone, strlen (timezone));
  return ret;
#else
  int ret = 0;

  ret = find_timezone_from_clock (timezone, buf_len);
  if (ret < 0)
    {
      ret = find_timezone_from_localtime (timezone, buf_len);
    }
  if (ret >= 0)
    {
      ret = tz_get_zone_id_by_name (timezone, strlen (timezone));
    }
  return ret;
#endif
}

/*
 * tz_set_tz_region_system() - set a reference to the global tz_region_system variable
 *
 * Returns: reference to the tz_region_system variable
 */
void
tz_set_tz_region_system (const TZ_REGION * tz_region)
{
  tz_Region_system = *tz_region;
}

/*
 * tz_get_client_tz_region_session() - get a reference to the global tz_Region_session variable
 *
 * Returns: reference to the tz_Region_session variable
 */
#if !defined(SERVER_MODE)
TZ_REGION *
tz_get_client_tz_region_session (void)
{
  return &tz_Region_session;
}
#endif

/*
 * tz_get_server_tz_region_session() - get a reference to the session TZ_REGION
 *
 * Returns: reference to the session TZ_REGION variable
 */
#if defined(SERVER_MODE)
TZ_REGION *
tz_get_server_tz_region_session (void)
{
  THREAD_ENTRY *thread_p;
  THREAD_ENTRY *worker_thread_p;
  TZ_REGION *session_tz_region;

  thread_p = thread_get_thread_entry_info ();

  if (thread_p->type == TT_DAEMON && thread_p->is_cdc_daemon && prm_get_integer_value (PRM_ID_SUPPLEMENTAL_LOG) > 0)
    {
      return &tz_Region_system;
    }

  session_tz_region = session_get_session_tz_region (thread_p);

  if (session_tz_region == NULL)
    {
      if (thread_p->emulate_tid != thread_id_t ())
    {
      worker_thread_p = thread_get_manager ()->find_by_tid (thread_p->emulate_tid);
      if (worker_thread_p != NULL)
        {
          session_tz_region = session_get_session_tz_region (worker_thread_p);
        }
    }
      else if (thread_p->type == TT_VACUUM_WORKER)
    {
      /* just use system region */
      session_tz_region = &tz_Region_system;
    }
    }

  return session_tz_region;
}
#endif

#if !defined (CS_MODE)
/*
 * tz_timezones_start_scan() - start scan function for 'SHOW TIMEZONES'
 *   return: NO_ERROR, or ER_code
 *   thread_p(in): thread entry
 *   show_type(in):
 *   arg_values(in):
 *   arg_cnt(in):
 *   ptr(in/out): 'show timezones' context
 */
int
tz_timezones_start_scan (THREAD_ENTRY * thread_p, int show_type, DB_VALUE ** arg_values, int arg_cnt, void **ptr)
{
  int error = NO_ERROR;
  SHOWSTMT_ARRAY_CONTEXT *ctx = NULL;
  DB_VALUE *vals = NULL;
  const int col_num = 1;
  const TZ_DATA *tzd;
  int i;

  tzd = tz_get_data ();
  if (tzd == NULL)
    {
      /* no timezones, just return */
      return error;
    }

  *ptr = NULL;

  ctx = showstmt_alloc_array_context (thread_p, tzd->name_count, col_num);
  if (ctx == NULL)
    {
      error = er_errid ();
      return error;
    }

  for (i = 0; i < tzd->name_count; i++)
    {
      vals = showstmt_alloc_tuple_in_context (thread_p, ctx);
      if (vals == NULL)
    {
      error = er_errid ();
      goto exit_on_error;
    }
      /* Geographic timezone name */
      db_make_string (&vals[0], tzd->names[i].name);
    }

  *ptr = ctx;
  return NO_ERROR;

exit_on_error:
  if (ctx != NULL)
    {
      showstmt_free_array_context (thread_p, ctx);
    }

  return error;
}

/*
 * tz_full_timezones_start_scan() - start scan function for 'SHOW FULL TIMEZONES'
 *   return: NO_ERROR, or ER_code
 *   thread_p(in): thread entry
 *   show_type(in):
 *   arg_values(in):
 *   arg_cnt(in):
 *   ptr(in/out): 'show timezones' context
 */
int
tz_full_timezones_start_scan (THREAD_ENTRY * thread_p, int show_type, DB_VALUE ** arg_values, int arg_cnt, void **ptr)
{
  int error = NO_ERROR;
  SHOWSTMT_ARRAY_CONTEXT *ctx = NULL;
  DB_VALUE *vals = NULL;
  const int col_num = 4;
  const TZ_DATA *tzd;
  int i;
  time_t cur_time;
  DB_DATETIME utc_datetime, dummy_datetime;
  int year, month, day, hour, minute, second;
  TZ_DECODE_INFO tzinfo;
  TZ_TIMEZONE timezone;
  TZ_OFFSET_RULE zone_off_rule;
  char dst_format[TZ_MAX_FORMAT_SIZE];
  const char *dst_name = NULL;
  char gmt_offset[MAX_LEN_OFFSET];
  char dst_offset[MAX_LEN_OFFSET];
  char empty_string[1];

  empty_string[0] = '\0';
  tzd = tz_get_data ();
  if (tzd == NULL)
    {
      /* no timezones, just return */
      return error;
    }

  *ptr = NULL;

  ctx = showstmt_alloc_array_context (thread_p, tzd->name_count, col_num);
  if (ctx == NULL)
    {
      error = er_errid ();
      return error;
    }

  /* Get the current time in seconds */
  time (&cur_time);
  tz_timestamp_decode_no_leap_sec ((int) cur_time, &year, &month, &day, &hour, &minute, &second);

  error = db_datetime_encode (&utc_datetime, month + 1, day, year, hour, minute, second, 0);
  if (error != NO_ERROR)
    {
      goto exit_on_error;
    }

  for (i = 0; i < tzd->name_count; i++)
    {
      int idx = 0;
      int dst_save_time = 0;
      int zone_id;

      vals = showstmt_alloc_tuple_in_context (thread_p, ctx);
      if (vals == NULL)
    {
      error = er_errid ();
      goto exit_on_error;
    }
      /* Geographic timezone name */
      db_make_string (&vals[idx++], tzd->names[i].name);

      /* First get the zone id */
      zone_id = i;

      tzinfo.type = TZ_REGION_ZONE;
      tzinfo.zone.zone_id = zone_id;
      tzinfo.zone.dst_str[0] = '\0';

      error = tz_datetime_utc_conv (&utc_datetime, &tzinfo, true, true, &dummy_datetime);
      if (error != NO_ERROR)
    {
      goto exit_on_error;
    }

      timezone = tzd->timezones[tzd->names[i].zone_id];
      zone_off_rule = tzd->offset_rules[timezone.gmt_off_rule_start + tzinfo.zone.offset_id];

      /* Timezone offset */
      error = tz_print_tz_offset (gmt_offset, zone_off_rule.gmt_off);
      if (error != NO_ERROR)
    {
      goto exit_on_error;
    }
      db_make_string_copy (&vals[idx++], gmt_offset);

      dst_name = zone_off_rule.std_format;
      if (zone_off_rule.ds_type == DS_TYPE_RULESET_ID)
    {
      TZ_DS_RULESET ds_ruleset;
      TZ_DS_RULE ds_rule;
      int dst_id;
      const char *ds_abbr = NULL;

      ds_ruleset = tzd->ds_rulesets[zone_off_rule.ds_ruleset];
      dst_id = tzinfo.zone.dst_id;

      if (dst_id != TZ_DS_ID_MAX)
        {
          ds_rule = tzd->ds_rules[dst_id + ds_ruleset.index_start];
          dst_save_time = ds_rule.save_time;
          ds_abbr = ds_rule.letter_abbrev;

          if (zone_off_rule.var_format != NULL)
        {
          snprintf (dst_format, sizeof (dst_format) - 1, zone_off_rule.var_format,
                (ds_abbr != NULL && *ds_abbr != '-') ? ds_abbr : "");
          dst_name = dst_format;
        }
          else
        {
          if (dst_save_time != 0)
            {
              dst_name = zone_off_rule.save_format;
            }
        }
          if (dst_name == NULL)
        {
          dst_name = empty_string;
        }
        }
    }

      /* Now put the daylight saving time offset and name */
      if (dst_name != NULL)
    {
      error = tz_print_tz_offset (dst_offset, dst_save_time);
      if (error != NO_ERROR)
        {
          goto exit_on_error;
        }
      db_make_string_copy (&vals[idx++], dst_offset);
      db_make_string_copy (&vals[idx++], dst_name);
    }
      else
    {
      db_make_null (&vals[idx++]);
      db_make_null (&vals[idx++]);
    }
    }

  *ptr = ctx;
  return NO_ERROR;

exit_on_error:
  if (ctx != NULL)
    {
      showstmt_free_array_context (thread_p, ctx);
    }

  return error;
}
#endif

/*
 * tz_load_with_library_path() - opens the timezone library and loads the data into the tzd
 *                   parameter using the given library path
 * tzd(in/out) : timezone data
 * timezone_library_path(in) : path to timezone library
 * Returns: 0 (NO_ERROR) if success, -1 otherwise
 */
int
tz_load_with_library_path (TZ_DATA * tzd, const char *timezone_library_path)
{
  int err_status = NO_ERROR;
  void *tz_Lib_handle = NULL;

  err_status = tz_load_library (timezone_library_path, &tz_Lib_handle);
  if (err_status != NO_ERROR)
    {
      goto error_exit;
    }

  err_status = tz_load_data_from_lib (tzd, tz_Lib_handle);
  if (err_status != NO_ERROR)
    {
      goto error_exit;
    }

  return NO_ERROR;

error_exit:
  assert (er_errid () != NO_ERROR);

  if (er_errid () == NO_ERROR)
    {
      err_status = ER_TZ_LOAD_ERROR;
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TZ_LOAD_ERROR, 1, timezone_library_path);
    }

  return err_status;
}

/*
 * tz_check_geographic_tz() - verifies if the encoded timezone information contains geographic or offset info
 *
 * tzd(in) : timezone data
 * Returns: error if geographic timezone, NO_ERROR if offset timezone
 */
int
tz_check_geographic_tz (const TZ_ID * tz_id)
{
  TZ_DECODE_INFO tz_info;

  tz_decode_tz_id (tz_id, false, &tz_info);

  if (tz_info.type == TZ_REGION_ZONE)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TZ_GEOGRAPHIC_ZONE, 0);
      return ER_TZ_GEOGRAPHIC_ZONE;
    }
  return NO_ERROR;
}

/*
 * tz_check_session_has_geographic_tz() - verifies if the session timezone contains geographic or offset info
 *
 * Returns: error if geographic timezone, NO_ERROR if offset timezone
 */
int
tz_check_session_has_geographic_tz (void)
{
  TZ_REGION session_tz_region;
  tz_get_session_tz_region (&session_tz_region);

  if (session_tz_region.type != TZ_REGION_OFFSET)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TZ_GEOGRAPHIC_ZONE, 0);
      return ER_TZ_GEOGRAPHIC_ZONE;
    }
  return NO_ERROR;
}

#if !defined(SERVER_MODE)
/*
 * put_timezone_checksum - Saves the timezone checksum into DB
 * return: error code
 *
 * Note: This is called during database creation;
 *
 */
int
put_timezone_checksum (char *checksum)
{
  DB_VALUE value;
  int au_save;
  int err_status = NO_ERROR;

  db_make_string (&value, checksum);

  AU_DISABLE (au_save);
  err_status = db_put_internal (Au_root, "timezone_checksum", &value);
  AU_ENABLE (au_save);

  return err_status;
}
#endif /* SERVER_MODE */

/*
 * check_timezone_compat - checks compatibility between a client timezone checksum and a server timezone checksum
 *
 * Returns : error code
 * client_checksum(in): client checksum
 * server_checksum(in): server checksum
 * client_text(in): text to display in message error for client(this can be "server" when checking server vs database)
 * server_text(in): text to display in message error for server(this can be "database" when checking server vs database)
 */
int
check_timezone_compat (const char *client_checksum, const char *server_checksum, const char *client_text,
               const char *server_text)
{
  assert (client_checksum != NULL);
  assert (server_checksum != NULL);
  assert (client_text != NULL);
  assert (server_text != NULL);

  if (strcmp (client_checksum, server_checksum) != 0)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_TZ_INCOMPATIBLE_TIMEZONE_LIBRARIES, 2, client_text, server_text);
      return ER_TZ_INCOMPATIBLE_TIMEZONE_LIBRARIES;
    }

  return NO_ERROR;
}

/*
 * tz_tzid_convert_region_to_offset - if the timezone encoded in tz_id is of region type the function converts
 *                                    it to an offset type
 *
 * Returns :
 * tz_id(in): timezone id
 */
void
tz_tzid_convert_region_to_offset (TZ_ID * tz_id)
{
  unsigned int flag;

  flag = ((*tz_id) & TZ_MASK_TZ_ID_FLAG) >> TZ_BIT_SHIFT_TZ_ID_FLAG;

  if (flag == 0)
    {
      TZ_DECODE_INFO tz_info;
      int total_offset;

      tz_decode_tz_id (tz_id, true, &tz_info);
      assert (tz_info.zone.p_zone_off_rule != NULL);
      total_offset = tz_info.zone.p_zone_off_rule->gmt_off;

      if (tz_info.zone.p_zone_off_rule->ds_type == DS_TYPE_RULESET_ID && tz_info.zone.p_ds_rule != NULL)
    {
      total_offset += tz_info.zone.p_ds_rule->save_time;
    }

      tz_info.type = TZ_REGION_OFFSET;
      tz_info.offset = total_offset;
      tz_encode_tz_id (&tz_info, tz_id);
    }
}

/*
 * tz_create_datetimetz_from_utc () - Creates a datetimetz from an UTC time and a timezone region
 *
 * Return: error code
 * src_dt(in): datetime value in UTC reference
 * dest_region(in): timezone region for dest_dt_tz
 * dest_dt_tz(out): DATETIMETZ value
 *
 */
int
tz_create_datetimetz_from_utc (const DB_DATETIME * src_dt, const TZ_REGION * dest_region, DB_DATETIMETZ * dest_dt_tz)
{
  int er_status = NO_ERROR;
  TZ_DECODE_INFO tz_info;

  tz_decode_tz_region (dest_region, &tz_info);

  er_status = tz_datetime_utc_conv (src_dt, &tz_info, true, true, &(dest_dt_tz->datetime));
  if (er_status != NO_ERROR)
    {
      return er_status;
    }

  tz_encode_tz_id (&tz_info, &(dest_dt_tz->tz_id));

  return er_status;
}

/*
 * tz_create_datetimetz_from_parts() - creates a datetimetz from month, day, year, hour, minutes, seconds and
 *                    milliseconds
 *
 *  Returns error or no error
 *  m(in): month
 *  d(in): day
 *  y(in): year
 *  h(in): hour
 *  mi(in): minute
 *  s(in): second
 *  ms(in): millisecond
 *  tz_id(in): timezone id
 *  dt_tz(out): result datetimetz
 */
int
tz_create_datetimetz_from_parts (const int m, const int d, const int y, const int h, const int mi, const int s,
                 const int ms, const TZ_ID * tz_id, DB_DATETIMETZ * dt_tz)
{
  DB_DATETIME utc_datetime;
  DB_DATETIMETZ dt_tz_utc;
  int error_status = NO_ERROR;

  error_status = db_datetime_encode (&utc_datetime, m, d, y, h, mi, s, ms);
  if (error_status != NO_ERROR)
    {
      return error_status;
    }

  dt_tz_utc.datetime = utc_datetime;

  if (tz_id != NULL)
    {
      dt_tz_utc.tz_id = *tz_id;
      error_status = tz_datetimetz_fix_zone (&dt_tz_utc, dt_tz);
    }
  else
    {
      TZ_REGION tz_session_region;

      tz_get_session_tz_region (&tz_session_region);
      error_status = tz_create_datetimetz_from_utc (&utc_datetime, &tz_session_region, dt_tz);
    }

  return error_status;
}

/*
 * set_new_zone_id() - Sets the new timezone id for the new timezone library using the old timezone library
 *
 *  Returns error or no error
 *  tz_info (in/out): pointer to tz_info
 *
 */
static int
set_new_zone_id (TZ_DECODE_INFO * tz_info)
{
  const char *timezone_name = NULL;
  unsigned int new_zone_id;
  int err_status = NO_ERROR;

  timezone_name = tz_Timezone_data.names[tz_info->zone.zone_id].name;
  tz_set_data (tz_get_new_timezone_data ());
  new_zone_id = tz_get_zone_id_by_name (timezone_name, strlen (timezone_name));
  if (new_zone_id == (unsigned int) -1)
    {
      err_status = ER_TZ_INVALID_TIMEZONE;
    }
  else
    {
      tz_info->zone.zone_id = new_zone_id;
    }

  return err_status;
}

/*
 * conv_tz() - Converts a tz type from one time library to another
 *
 *  Returns error or no error
 *  p_out (out): pointer to output timezone data type
 *  p_in (in): pointer to input timezone data type
 *  type (in): timezone type
 *
 * NOTE: It does not make sense to use the function as NON-SA_MODE.
 */
int
conv_tz (void *p_out, const void *p_in, DB_TYPE type)
{
  int err_status = NO_ERROR;
  TZ_DATA save_data;

  /* Save the state */
  save_data = tz_Timezone_data;
  switch (type)
    {
    case DB_TYPE_TIMESTAMPTZ:
      {
    DB_TIMESTAMPTZ *current, *to_be;
    DB_DATE date_local;
    DB_TIME time_local;
    TZ_DECODE_INFO tz_info;
    DB_DATETIME src_dt, dest_dt;
    DB_TIMESTAMP timestamp_utc;

    current = (DB_TIMESTAMPTZ *) p_in;
    to_be = (DB_TIMESTAMPTZ *) p_out;

    tz_decode_tz_id (&(current->tz_id), true, &tz_info);
    if (tz_info.type == TZ_REGION_OFFSET)
      {
        *to_be = *current;
        return NO_ERROR;
      }

#if defined (SA_MODE)
    if (tz_Is_backward_compatible_timezone[tz_info.zone.zone_id] == true)
      {
        err_status = set_new_zone_id (&tz_info);
        if (err_status != NO_ERROR)
          {
        er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, err_status, 0);
        goto exit;
          }
        tz_encode_tz_id (&tz_info, &(to_be->tz_id));
        to_be->timestamp = current->timestamp;
        goto exit;
      }
#else /* !SA_MODE */
    /* NON SA_MODE have no interest. */
#endif /* !SA_MODE */

    err_status = db_timestamp_decode_w_tz_id (&(current->timestamp), &(current->tz_id), &date_local, &time_local);
    if (err_status != NO_ERROR)
      {
        goto exit;
      }

    src_dt.date = date_local;
    src_dt.time = time_local * 1000;

    err_status = set_new_zone_id (&tz_info);
    if (err_status != NO_ERROR)
      {
        er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, err_status, 0);
        goto exit;
      }

    err_status = tz_datetime_utc_conv (&src_dt, &tz_info, false, false, &dest_dt);
    /* If we get an error it means that the abbreviation does no longer exist
     * Then we must try to convert without the abbreviation
     */
    if (err_status != NO_ERROR)
      {
        tz_info.zone.dst_str[0] = '\0';
        err_status = tz_datetime_utc_conv (&src_dt, &tz_info, false, false, &dest_dt);
        if (err_status != NO_ERROR)
          {
        goto exit;
          }
      }

    dest_dt.time /= 1000;
    tz_encode_tz_id (&tz_info, &(to_be->tz_id));
    /* Encode UTC time for timestamptz */
    db_timestamp_encode_utc (&dest_dt.date, &dest_dt.time, &timestamp_utc);
    to_be->timestamp = timestamp_utc;
      }
      break;

    case DB_TYPE_DATETIMETZ:
      {
    DB_DATETIMETZ *current, *to_be;
    TZ_DECODE_INFO tz_info;
    DB_DATETIME dest_dt;
    DB_DATETIME datetime_local;

    current = (DB_DATETIMETZ *) p_in;
    to_be = (DB_DATETIMETZ *) p_out;

    tz_decode_tz_id (&(current->tz_id), true, &tz_info);
    if (tz_info.type == TZ_REGION_OFFSET)
      {
        *to_be = *current;
        return NO_ERROR;
      }

#if defined (SA_MODE)
    if (tz_Is_backward_compatible_timezone[tz_info.zone.zone_id] == true)
      {
        err_status = set_new_zone_id (&tz_info);
        if (err_status != NO_ERROR)
          {
        er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, err_status, 0);
        goto exit;
          }
        tz_encode_tz_id (&tz_info, &(to_be->tz_id));
        to_be->datetime = current->datetime;
        goto exit;
      }
#else /* !SA_MODE */
    /* NON SA_MODE have no interest. */
#endif /* !SA_MODE */

    err_status = tz_utc_datetimetz_to_local (&(current->datetime), &(current->tz_id), &datetime_local);
    if (err_status)
      {
        goto exit;
      }

    err_status = set_new_zone_id (&tz_info);
    if (err_status != NO_ERROR)
      {
        er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, err_status, 0);
        goto exit;
      }

    err_status = tz_datetime_utc_conv (&datetime_local, &tz_info, false, false, &dest_dt);
    /* If we get an error it means that the abbreviation does no longer exist
     * then we must try to convert without the abbreviation
     */
    if (err_status != NO_ERROR)
      {
        tz_info.zone.dst_str[0] = '\0';
        err_status = tz_datetime_utc_conv (&datetime_local, &tz_info, false, false, &dest_dt);
        if (err_status != NO_ERROR)
          {
        goto exit;
          }
      }

    tz_encode_tz_id (&tz_info, &(to_be->tz_id));
    to_be->datetime = dest_dt;
      }
      break;

    case DB_TYPE_TIMESTAMPLTZ:
      {
    DB_TIMESTAMP *current, *to_be;
    TZ_ID ses_tz_id;
    DB_DATE date_local;
    DB_TIME time_local;
    DB_DATETIME src_dt, dest_dt;
    TZ_DECODE_INFO tz_info;

    current = (DB_TIMESTAMP *) p_in;
    to_be = (DB_TIMESTAMP *) p_out;

    err_status = tz_create_session_tzid_for_timestamp (current, &ses_tz_id);
    if (err_status != NO_ERROR)
      {
        goto exit;
      }

    tz_decode_tz_id (&ses_tz_id, true, &tz_info);

#if defined (SA_MODE)
    if ((tz_info.type == TZ_REGION_OFFSET) || (tz_Is_backward_compatible_timezone[tz_info.zone.zone_id] == true))
      {
        *to_be = *current;
        return NO_ERROR;
      }
#else /* !SA_MODE */
    /* NON SA_MODE have no interest. */
#endif /* !SA_MODE */

    err_status = db_timestamp_decode_w_tz_id (current, &ses_tz_id, &date_local, &time_local);
    if (err_status != NO_ERROR)
      {
        goto exit;
      }

    src_dt.date = date_local;
    src_dt.time = time_local * 1000;

    err_status = set_new_zone_id (&tz_info);
    if (err_status != NO_ERROR)
      {
        er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, err_status, 0);
        goto exit;
      }

    err_status = tz_datetime_utc_conv (&src_dt, &tz_info, false, false, &dest_dt);
    if (err_status != NO_ERROR)
      {
        goto exit;
      }

    dest_dt.time /= 1000;
    db_timestamp_encode_utc (&dest_dt.date, &dest_dt.time, to_be);
      }
      break;

    case DB_TYPE_DATETIMELTZ:
      {
    DB_DATETIME *current, *to_be;
    TZ_ID ses_tz_id;
    TZ_DECODE_INFO tz_info;
    DB_DATETIME datetime_local;

    current = (DB_DATETIME *) p_in;
    to_be = (DB_DATETIME *) p_out;

    err_status = tz_create_session_tzid_for_datetime (current, true, &ses_tz_id);
    if (err_status != NO_ERROR)
      {
        goto exit;
      }

    tz_decode_tz_id (&ses_tz_id, true, &tz_info);

#if defined (SA_MODE)
    if ((tz_info.type == TZ_REGION_OFFSET) || (tz_Is_backward_compatible_timezone[tz_info.zone.zone_id] == true))
      {
        *to_be = *current;
        return NO_ERROR;
      }
#else /* !SA_MODE */
    /* NON SA_MODE have no interest. */
#endif /* !SA_MODE */

    err_status = tz_utc_datetimetz_to_local (current, &ses_tz_id, &datetime_local);
    if (err_status)
      {
        goto exit;
      }

    err_status = set_new_zone_id (&tz_info);
    if (err_status != NO_ERROR)
      {
        er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, err_status, 0);
        goto exit;
      }

    err_status = tz_datetime_utc_conv (&datetime_local, &tz_info, false, false, to_be);
    if (err_status != NO_ERROR)
      {
        goto exit;
      }
      }
      break;

    default:
      assert (false);
      break;
    }

exit:
  tz_set_data (&save_data);

  return err_status;
}

//TODO: make tz_get_offset_in_mins get into account DST
/*
 * tz_get_offset_in_mins () - time zone offset in minutes from GMT
 */
int
tz_get_offset_in_mins ()
{
  time_t currtime;
  struct tm *timeinfo;

  time (&currtime);
  timeinfo = gmtime (&currtime);
  time_t utc = mktime (timeinfo);
  timeinfo = localtime (&currtime);
  time_t local = mktime (timeinfo);

  // Get offset in minutes from UTC
  int offsetFromUTC = difftime (utc, local) / 60;

  return offsetFromUTC;
}