/* Copyright (c) 2006, 2021, Oracle and/or its affiliates.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License, version 2.0,
   as published by the Free Software Foundation.

   This program is also distributed with certain software (including
   but not limited to OpenSSL) that is licensed under separate terms,
   as designated in a particular file or component or in included license
   documentation.  The authors of MySQL hereby grant you an additional
   permission to link the program and your derivative works with the
   separately licensed software that they have included with MySQL.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License, version 2.0, for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */

#ifndef SQL_TIME_INCLUDED
#define SQL_TIME_INCLUDED

#include "my_global.h"                          /* ulong */
#include "my_time.h"
#include "mysql_time.h"                         /* timestamp_type */
#include "sql_error.h"                          /* Sql_condition */
#include "mysqld.h"                             /* current_thd */

struct Date_time_format
{
  uchar positions[8];
  char  time_separator;			/* Separator between hour and minute */
  uint flag;				/* For future */
  LEX_STRING format;
};

struct Interval
{
  ulong year, month, day, hour;
  ulonglong minute, second, second_part;
  bool neg;
};

struct Known_date_time_format
{
  const char *format_name;
  const char *date_format;
  const char *datetime_format;
  const char *time_format;
};

/* Flags for calc_week() function.  */
#define WEEK_MONDAY_FIRST    1
#define WEEK_YEAR            2
#define WEEK_FIRST_WEEKDAY   4

ulong convert_period_to_month(ulong period);
ulong convert_month_to_period(ulong month);
void mix_date_and_time(MYSQL_TIME *ldate, const MYSQL_TIME *ltime);
void get_date_from_daynr(long daynr,uint *year, uint *month, uint *day);
my_time_t TIME_to_timestamp(THD *thd, const MYSQL_TIME *t, my_bool *not_exist);
bool datetime_with_no_zero_in_date_to_timeval(THD *thd, const MYSQL_TIME *t,
                                              struct timeval *tm,
                                              int *warnings);
bool datetime_to_timeval(THD *thd, const MYSQL_TIME *t,
                         struct timeval *tm, int *warnings);
bool str_to_datetime_with_warn(String *str,  MYSQL_TIME *l_time,
                               my_time_flags_t flags);
bool my_decimal_to_datetime_with_warn(const my_decimal *decimal,
                                      MYSQL_TIME *ltime, my_time_flags_t flags);
bool my_double_to_datetime_with_warn(double nr, MYSQL_TIME *ltime,
                                     my_time_flags_t flags);
bool my_longlong_to_datetime_with_warn(longlong nr, MYSQL_TIME *ltime,
                                       my_time_flags_t flags);
bool my_decimal_to_time_with_warn(const my_decimal *decimal,
                                  MYSQL_TIME *ltime);
bool my_double_to_time_with_warn(double nr, MYSQL_TIME *ltime);
bool my_longlong_to_time_with_warn(longlong nr, MYSQL_TIME *ltime);
bool str_to_time_with_warn(String *str, MYSQL_TIME *l_time);
void time_to_datetime(THD *thd, const MYSQL_TIME *tm, MYSQL_TIME *dt);
inline void datetime_to_time(MYSQL_TIME *ltime)
{
  ltime->year= ltime->month= ltime->day= 0;
  ltime->time_type= MYSQL_TIMESTAMP_TIME;
}
inline void datetime_to_date(MYSQL_TIME *ltime)
{
  ltime->hour= ltime->minute= ltime->second= ltime->second_part= 0;
  ltime->time_type= MYSQL_TIMESTAMP_DATE;
}
inline void date_to_datetime(MYSQL_TIME *ltime)
{
  ltime->time_type= MYSQL_TIMESTAMP_DATETIME;
}
void make_truncated_value_warning(THD *thd,
                                  Sql_condition::enum_severity_level level,
                                  ErrConvString val,
                                  timestamp_type time_type,
                                  const char *field_name);
inline void make_truncated_value_warning(ErrConvString val,
                                         timestamp_type time_type)
{
  make_truncated_value_warning(current_thd, Sql_condition::SL_WARNING,
                               val, time_type, NullS);
}
extern Date_time_format *date_time_format_copy(THD *thd,
					       Date_time_format *format);
const char *get_date_time_format_str(Known_date_time_format *format,
				     timestamp_type type);
void make_date(const Date_time_format *format, const MYSQL_TIME *l_time,
               String *str);
void make_time(const Date_time_format *format, const MYSQL_TIME *l_time,
               String *str, uint dec);
void make_datetime(const Date_time_format *format, const MYSQL_TIME *l_time,
                   String *str, uint dec);
bool my_TIME_to_str(const MYSQL_TIME *ltime, String *str, uint dec);

/* MYSQL_TIME operations */
bool date_add_interval(MYSQL_TIME *ltime, interval_type int_type,
                       Interval interval);
bool calc_time_diff(const MYSQL_TIME *l_time1, const MYSQL_TIME *l_time2,
                    int l_sign, longlong *seconds_out, long *microseconds_out);
int my_time_compare(MYSQL_TIME *a, MYSQL_TIME *b);
void localtime_to_TIME(MYSQL_TIME *to, struct tm *from);
void calc_time_from_sec(MYSQL_TIME *to, longlong seconds, long microseconds);
uint calc_week(MYSQL_TIME *l_time, uint week_behaviour, uint *year);

int calc_weekday(long daynr,bool sunday_first_day_of_week);

/* Character set-aware version of str_to_time() */
bool str_to_time(const CHARSET_INFO *cs, const char *str, size_t length,
                 MYSQL_TIME *l_time, my_time_flags_t flags,
                 MYSQL_TIME_STATUS *status);
static inline bool str_to_time(const String *str, MYSQL_TIME *ltime,
                               my_time_flags_t flags, MYSQL_TIME_STATUS *status)
{
  return str_to_time(str->charset(), str->ptr(), str->length(),
                     ltime, flags, status);
}

bool time_add_nanoseconds_with_round(MYSQL_TIME *ltime, uint nanoseconds,  
                                     int *warnings);
/* Character set-aware version of str_to_datetime() */
bool str_to_datetime(const CHARSET_INFO *cs,
                     const char *str, size_t length,
                     MYSQL_TIME *l_time, my_time_flags_t flags,
                     MYSQL_TIME_STATUS *status);
static inline bool str_to_datetime(const String *str, MYSQL_TIME *ltime,
                                   my_time_flags_t flags,
                                   MYSQL_TIME_STATUS *status)
{
  return str_to_datetime(str->charset(), str->ptr(), str->length(),
                         ltime, flags, status);
}

bool datetime_add_nanoseconds_with_round(MYSQL_TIME *ltime,
                                         uint nanoseconds, int *warnings);

bool parse_date_time_format(timestamp_type format_type,
                            Date_time_format *date_time_format);

extern Date_time_format global_date_format;
extern Date_time_format global_datetime_format;
extern Date_time_format global_time_format;
extern Known_date_time_format known_date_time_formats[];
extern LEX_STRING interval_type_to_name[];

/* Date/time rounding and truncation functions */
inline long my_time_fraction_remainder(long nr, uint decimals)
{
  assert(decimals <= DATETIME_MAX_DECIMALS);
  return nr % (long) log_10_int[DATETIME_MAX_DECIMALS - decimals];
}
inline void my_time_trunc(MYSQL_TIME *ltime, uint decimals)
{
  ltime->second_part-= my_time_fraction_remainder(ltime->second_part, decimals);
}
inline void my_datetime_trunc(MYSQL_TIME *ltime, uint decimals)
{
  return my_time_trunc(ltime, decimals);
}
inline void my_timeval_trunc(struct timeval *tv, uint decimals)
{
  tv->tv_usec-= my_time_fraction_remainder(tv->tv_usec, decimals);
}
bool my_time_round(MYSQL_TIME *ltime, uint decimals);
bool my_datetime_round(MYSQL_TIME *ltime, uint decimals, int *warnings);
bool my_timeval_round(struct timeval *tv, uint decimals);


inline ulonglong TIME_to_ulonglong_datetime_round(const MYSQL_TIME *ltime)
{
  // Catch simple cases
  if (ltime->second_part < 500000)
    return TIME_to_ulonglong_datetime(ltime);
  if (ltime->second < 59)
    return TIME_to_ulonglong_datetime(ltime) + 1;
  // Corner case e.g. 'YYYY-MM-DD hh:mm:59.5'. Proceed with slower method.
  int warnings= 0;
  MYSQL_TIME tmp= *ltime;
  my_datetime_round(&tmp, 0, &warnings);
  return TIME_to_ulonglong_datetime(&tmp);// + TIME_microseconds_round(ltime);
}


inline ulonglong TIME_to_ulonglong_time_round(const MYSQL_TIME *ltime)
{
  if (ltime->second_part < 500000)
    return TIME_to_ulonglong_time(ltime);
  if (ltime->second < 59)
    return TIME_to_ulonglong_time(ltime) + 1;
  // Corner case e.g. 'hh:mm:59.5'. Proceed with slower method.
  MYSQL_TIME tmp= *ltime;
  my_time_round(&tmp, 0);
  return TIME_to_ulonglong_time(&tmp);
}


inline ulonglong TIME_to_ulonglong_round(const MYSQL_TIME *ltime)
{
  switch (ltime->time_type)
  {
  case MYSQL_TIMESTAMP_TIME:
    return TIME_to_ulonglong_time_round(ltime);
  case MYSQL_TIMESTAMP_DATETIME:
    return TIME_to_ulonglong_datetime_round(ltime);
  case MYSQL_TIMESTAMP_DATE:
    return TIME_to_ulonglong_date(ltime);
  default:
    assert(0);
    return 0;
  }
}


inline double TIME_microseconds(const MYSQL_TIME *ltime)
{
  return (double) ltime->second_part / 1000000;
}

inline double TIME_to_double_datetime(const MYSQL_TIME *ltime)
{
  return (double) TIME_to_ulonglong_datetime(ltime) + TIME_microseconds(ltime);
}


inline double TIME_to_double_time(const MYSQL_TIME *ltime)
{
  return (double) TIME_to_ulonglong_time(ltime) + TIME_microseconds(ltime);
}


inline double TIME_to_double(const MYSQL_TIME *ltime)
{
  return (double) TIME_to_ulonglong(ltime) + TIME_microseconds(ltime);
}


static inline bool check_fuzzy_date(const MYSQL_TIME *ltime,
                                    my_time_flags_t fuzzydate)
{
  return !(fuzzydate & TIME_FUZZY_DATE) && (!ltime->month || !ltime->day);
}

static inline bool
non_zero_date(const MYSQL_TIME *ltime)
{
  return ltime->year || ltime->month || ltime->day;
}

static inline bool
non_zero_time(const MYSQL_TIME *ltime)
{
  return ltime->hour || ltime->minute || ltime->second || ltime->second_part;
}

longlong TIME_to_longlong_packed(const MYSQL_TIME *tm,
                                 enum enum_field_types type);
void TIME_from_longlong_packed(MYSQL_TIME *ltime,
                               enum enum_field_types type,
                               longlong packed_value);
my_decimal *my_decimal_from_datetime_packed(my_decimal *dec,
                                            enum enum_field_types type,
                                            longlong packed_value);

longlong longlong_from_datetime_packed(enum enum_field_types type,
                                       longlong packed_value);

double double_from_datetime_packed(enum enum_field_types type,
                                   longlong packed_value);

static inline
timestamp_type field_type_to_timestamp_type(enum enum_field_types type)
{
  switch (type)
  {
  case MYSQL_TYPE_TIME: return MYSQL_TIMESTAMP_TIME;
  case MYSQL_TYPE_DATE: return MYSQL_TIMESTAMP_DATE;
  case MYSQL_TYPE_TIMESTAMP:
  case MYSQL_TYPE_DATETIME: return MYSQL_TIMESTAMP_DATETIME;
  default: return MYSQL_TIMESTAMP_NONE;
  }
}
#endif /* SQL_TIME_INCLUDED */
