/*
   Copyright (c) 2000, 2012, Oracle and/or its affiliates.
   Copyright (c) 2009, 2020, MariaDB

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

   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 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-1335  USA */


/**
  @file

  @brief
  This file defines all time functions

  @todo
    Move month and days to language files
*/

#ifdef USE_PRAGMA_IMPLEMENTATION
#pragma implementation				// gcc: Class implementation
#endif

#include "mariadb.h"
#include "sql_priv.h"
/*
  It is necessary to include set_var.h instead of item.h because there
  are dependencies on include order for set_var.h and item.h. This
  will be resolved later.
*/
#include "sql_class.h"                          // set_var.h: THD
#include "set_var.h"
#include "sql_locale.h"          // MY_LOCALE my_locale_en_US
#include "strfunc.h"             // check_word
#include "sql_type_int.h"        // Longlong_hybrid
#include "sql_time.h"            // make_truncated_value_warning,
                                 // get_date_from_daynr,
                                 // calc_weekday, calc_week,
                                 // convert_month_to_period,
                                 // convert_period_to_month,
                                 // TIME_to_timestamp,
                                 // calc_time_diff,
                                 // calc_time_from_sec,
                                 // get_date_time_format_str
#include "tztime.h"              // struct Time_zone
#include "sql_class.h"           // THD
#include <m_ctype.h>
#include <time.h>

/** Day number for Dec 31st, 9999. */
#define MAX_DAY_NUMBER 3652424L

Func_handler_date_add_interval_datetime_arg0_time
  func_handler_date_add_interval_datetime_arg0_time;

Func_handler_date_add_interval_datetime func_handler_date_add_interval_datetime;
Func_handler_date_add_interval_date     func_handler_date_add_interval_date;
Func_handler_date_add_interval_time     func_handler_date_add_interval_time;
Func_handler_date_add_interval_string   func_handler_date_add_interval_string;

Func_handler_add_time_datetime func_handler_add_time_datetime_add(1);
Func_handler_add_time_datetime func_handler_add_time_datetime_sub(-1);
Func_handler_add_time_time     func_handler_add_time_time_add(1);
Func_handler_add_time_time     func_handler_add_time_time_sub(-1);
Func_handler_add_time_string   func_handler_add_time_string_add(1);
Func_handler_add_time_string   func_handler_add_time_string_sub(-1);

Func_handler_str_to_date_datetime_sec  func_handler_str_to_date_datetime_sec;
Func_handler_str_to_date_datetime_usec func_handler_str_to_date_datetime_usec;
Func_handler_str_to_date_date          func_handler_str_to_date_date;
Func_handler_str_to_date_time_sec      func_handler_str_to_date_time_sec;
Func_handler_str_to_date_time_usec     func_handler_str_to_date_time_usec;


/*
  Date formats corresponding to compound %r and %T conversion specifiers

  Note: We should init at least first element of "positions" array
        (first member) or hpux11 compiler will die horribly.
*/
static DATE_TIME_FORMAT time_ampm_format= {{0}, '\0', 0,
                                           {(char *)"%I:%i:%S %p", 11}};
static DATE_TIME_FORMAT time_24hrs_format= {{0}, '\0', 0,
                                            {(char *)"%H:%i:%S", 8}};

/**
  Extract datetime value to MYSQL_TIME struct from string value
  according to format string.

  @param format		date/time format specification
  @param val			String to decode
  @param length		Length of string
  @param l_time		Store result here
  @param cached_timestamp_type  It uses to get an appropriate warning
                                in the case when the value is truncated.
  @param sub_pattern_end    if non-zero then we are parsing string which
                            should correspond compound specifier (like %T or
                            %r) and this parameter is pointer to place where
                            pointer to end of string matching this specifier
                            should be stored.

  @note
    Possibility to parse strings matching to patterns equivalent to compound
    specifiers is mainly intended for use from inside of this function in
    order to understand %T and %r conversion specifiers, so number of
    conversion specifiers that can be used in such sub-patterns is limited.
    Also most of checks are skipped in this case.

  @note
    If one adds new format specifiers to this function he should also
    consider adding them to get_date_time_result_type() function.

  @retval
    0	ok
  @retval
    1	error
*/

static bool extract_date_time(THD *thd, DATE_TIME_FORMAT *format,
			      const char *val, uint length, MYSQL_TIME *l_time,
                              timestamp_type cached_timestamp_type,
                              const char **sub_pattern_end,
                              const char *date_time_type,
                              date_conv_mode_t fuzzydate)
{
  int weekday= 0, yearday= 0, daypart= 0;
  int week_number= -1;
  int error= 0;
  int  strict_week_number_year= -1;
  int frac_part;
  bool usa_time= 0;
  bool UNINIT_VAR(sunday_first_n_first_week_non_iso);
  bool UNINIT_VAR(strict_week_number);
  bool UNINIT_VAR(strict_week_number_year_type);
  const char *val_begin= val;
  const char *val_end= val + length;
  const char *ptr= format->format.str;
  const char *end= ptr + format->format.length;
  CHARSET_INFO *cs= &my_charset_bin;
  DBUG_ENTER("extract_date_time");

  if (!sub_pattern_end)
    bzero((char*) l_time, sizeof(*l_time));

  l_time->time_type= cached_timestamp_type;

  for (; ptr != end && val != val_end; ptr++)
  {
    /* Skip pre-space between each argument */
    if ((val+= cs->scan(val, val_end, MY_SEQ_SPACES)) >= val_end)
      break;

    if (*ptr == '%' && ptr+1 != end)
    {
      int val_len;
      char *tmp;

      error= 0;

      val_len= (uint) (val_end - val);
      switch (*++ptr) {
	/* Year */
      case 'Y':
	tmp= (char*) val + MY_MIN(4, val_len);
	l_time->year= (int) my_strtoll10(val, &tmp, &error);
        if ((int) (tmp-val) <= 2)
          l_time->year= year_2000_handling(l_time->year);
	val= tmp;
	break;
      case 'y':
	tmp= (char*) val + MY_MIN(2, val_len);
	l_time->year= (int) my_strtoll10(val, &tmp, &error);
	val= tmp;
        l_time->year= year_2000_handling(l_time->year);
	break;

	/* Month */
      case 'm':
      case 'c':
	tmp= (char*) val + MY_MIN(2, val_len);
	l_time->month= (int) my_strtoll10(val, &tmp, &error);
	val= tmp;
	break;
      case 'M':
	if ((l_time->month= check_word(my_locale_en_US.month_names,
				       val, val_end, &val)) <= 0)
	  goto err;
	break;
      case 'b':
	if ((l_time->month= check_word(my_locale_en_US.ab_month_names,
				       val, val_end, &val)) <= 0)
	  goto err;
	break;
	/* Day */
      case 'd':
      case 'e':
	tmp= (char*) val + MY_MIN(2, val_len);
	l_time->day= (int) my_strtoll10(val, &tmp, &error);
	val= tmp;
	break;
      case 'D':
	tmp= (char*) val + MY_MIN(2, val_len);
	l_time->day= (int) my_strtoll10(val, &tmp, &error);
	/* Skip 'st, 'nd, 'th .. */
	val= tmp + MY_MIN((int) (val_end-tmp), 2);
	break;

	/* Hour */
      case 'h':
      case 'I':
      case 'l':
	usa_time= 1;
	/* fall through */
      case 'k':
      case 'H':
	tmp= (char*) val + MY_MIN(2, val_len);
	l_time->hour= (int) my_strtoll10(val, &tmp, &error);
	val= tmp;
	break;

	/* Minute */
      case 'i':
	tmp= (char*) val + MY_MIN(2, val_len);
	l_time->minute= (int) my_strtoll10(val, &tmp, &error);
	val= tmp;
	break;

	/* Second */
      case 's':
      case 'S':
	tmp= (char*) val + MY_MIN(2, val_len);
	l_time->second= (int) my_strtoll10(val, &tmp, &error);
	val= tmp;
	break;

	/* Second part */
      case 'f':
	tmp= (char*) val_end;
	if (tmp - val > 6)
	  tmp= (char*) val + 6;
	l_time->second_part= (int) my_strtoll10(val, &tmp, &error);
	frac_part= 6 - (int) (tmp - val);
	if (frac_part > 0)
	  l_time->second_part*= (ulong) log_10_int[frac_part];
	val= tmp;
	break;

	/* AM / PM */
      case 'p':
	if (val_len < 2 || ! usa_time)
	  goto err;
	if (!my_charset_latin1.strnncoll(val, 2, "PM", 2))
	  daypart= 12;
	else if (my_charset_latin1.strnncoll(val, 2, "AM", 2))
	  goto err;
	val+= 2;
	break;

	/* Exotic things */
      case 'W':
	if ((weekday= check_word(my_locale_en_US.day_names, val, val_end, &val)) <= 0)
	  goto err;
	break;
      case 'a':
	if ((weekday= check_word(my_locale_en_US.ab_day_names, val, val_end, &val)) <= 0)
	  goto err;
	break;
      case 'w':
	tmp= (char*) val + 1;
	if (unlikely((weekday= (int) my_strtoll10(val, &tmp, &error)) < 0 ||
                     weekday >= 7))
	  goto err;
        /* We should use the same 1 - 7 scale for %w as for %W */
        if (!weekday)
          weekday= 7;
	val= tmp;
	break;
      case 'j':
	tmp= (char*) val + MY_MIN(val_len, 3);
	yearday= (int) my_strtoll10(val, &tmp, &error);
	val= tmp;
	break;

        /* Week numbers */
      case 'V':
      case 'U':
      case 'v':
      case 'u':
        sunday_first_n_first_week_non_iso= (*ptr=='U' || *ptr== 'V');
        strict_week_number= (*ptr=='V' || *ptr=='v');
	tmp= (char*) val + MY_MIN(val_len, 2);
	if (unlikely((week_number=
                      (int) my_strtoll10(val, &tmp, &error)) < 0 ||
                     (strict_week_number && !week_number) ||
                     week_number > 53))
          goto err;
	val= tmp;
	break;

        /* Year used with 'strict' %V and %v week numbers */
      case 'X':
      case 'x':
        strict_week_number_year_type= (*ptr=='X');
        tmp= (char*) val + MY_MIN(4, val_len);
        strict_week_number_year= (int) my_strtoll10(val, &tmp, &error);
        val= tmp;
        break;

        /* Time in AM/PM notation */
      case 'r':
        /*
          We can't just set error here, as we don't want to generate two
          warnings in case of errors
        */
        if (extract_date_time(thd, &time_ampm_format, val,
                              (uint)(val_end - val), l_time,
                              cached_timestamp_type, &val, "time", fuzzydate))
          DBUG_RETURN(1);
        break;

        /* Time in 24-hour notation */
      case 'T':
        if (extract_date_time(thd, &time_24hrs_format, val,
                              (uint)(val_end - val), l_time,
                              cached_timestamp_type, &val, "time", fuzzydate))
          DBUG_RETURN(1);
        break;

        /* Conversion specifiers that match classes of characters */
      case '.':
	while (my_ispunct(cs, *val) && val != val_end)
	  val++;
	break;
      case '@':
	while (my_isalpha(cs, *val) && val != val_end)
	  val++;
	break;
      case '#':
	while (my_isdigit(cs, *val) && val != val_end)
	  val++;
	break;
      default:
	goto err;
      }
      if (unlikely(error))                  // Error from my_strtoll10
	goto err;
    }
    else if (!my_isspace(cs, *ptr))
    {
      if (*val != *ptr)
	goto err;
      val++;
    }
  }
  if (usa_time)
  {
    if (l_time->hour > 12 || l_time->hour < 1)
      goto err;
    l_time->hour= l_time->hour%12+daypart;
  }

  /*
    If we are recursively called for parsing string matching compound
    specifiers we are already done.
  */
  if (sub_pattern_end)
  {
    *sub_pattern_end= val;
    DBUG_RETURN(0);
  }

  if (yearday > 0)
  {
    uint days;
    days= calc_daynr(l_time->year,1,1) +  yearday - 1;
    if (get_date_from_daynr(days,&l_time->year,&l_time->month,&l_time->day))
      goto err;
  }

  if (week_number >= 0 && weekday)
  {
    int days;
    uint weekday_b;

    /*
      %V,%v require %X,%x resprectively,
      %U,%u should be used with %Y and not %X or %x
    */
    if ((strict_week_number &&
         (strict_week_number_year < 0 ||
          strict_week_number_year_type !=
          sunday_first_n_first_week_non_iso)) ||
        (!strict_week_number && strict_week_number_year >= 0))
      goto err;

    /* Number of days since year 0 till 1st Jan of this year */
    days= calc_daynr((strict_week_number ? strict_week_number_year :
                                           l_time->year),
                     1, 1);
    /* Which day of week is 1st Jan of this year */
    weekday_b= calc_weekday(days, sunday_first_n_first_week_non_iso);

    /*
      Below we are going to sum:
      1) number of days since year 0 till 1st day of 1st week of this year
      2) number of days between 1st week and our week
      3) and position of our day in the week
    */
    if (sunday_first_n_first_week_non_iso)
    {
      days+= ((weekday_b == 0) ? 0 : 7) - weekday_b +
             (week_number - 1) * 7 +
             weekday % 7;
    }
    else
    {
      days+= ((weekday_b <= 3) ? 0 : 7) - weekday_b +
             (week_number - 1) * 7 +
             (weekday - 1);
    }

    if (get_date_from_daynr(days,&l_time->year,&l_time->month,&l_time->day))
      goto err;
  }

  if (l_time->month > 12 || l_time->day > 31 || l_time->hour > 23 || 
      l_time->minute > 59 || l_time->second > 59)
    goto err;

  int was_cut;
  if (check_date(l_time, fuzzydate | TIME_INVALID_DATES, &was_cut))
    goto err;

  if (val != val_end)
  {
    do
    {
      if (!my_isspace(&my_charset_latin1,*val))
      {
        ErrConvString err(val_begin, length, &my_charset_bin);
        make_truncated_value_warning(thd, Sql_condition::WARN_LEVEL_WARN,
                                     &err, cached_timestamp_type,
                                     nullptr, nullptr, nullptr);
	break;
      }
    } while (++val != val_end);
  }
  DBUG_RETURN(0);

err:
  {
    char buff[128];
    strmake(buff, val_begin, MY_MIN(length, sizeof(buff)-1));
    push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
                        ER_WRONG_VALUE_FOR_TYPE,
                        ER_THD(thd, ER_WRONG_VALUE_FOR_TYPE),
                        date_time_type, buff, "str_to_date");
  }
  DBUG_RETURN(1);
}


/**
  Create a formatted date/time value in a string.
*/

static bool make_date_time(const String *format, const MYSQL_TIME *l_time,
                           timestamp_type type, const MY_LOCALE *locale,
                           String *str)
{
  char intbuff[15];
  uint hours_i;
  uint weekday;
  ulong length;
  const uchar *ptr, *end;

  str->length(0);

  if (l_time->neg)
    str->append_wc('-');

  end= (ptr= (const uchar *) format->ptr()) + format->length();

  for ( ; ; )
  {
    my_wc_t wc;
    int mblen= format->charset()->cset->mb_wc(format->charset(), &wc, ptr, end);
    if (mblen < 1)
      return false;
    ptr+= mblen;

    if (wc != '%' || ptr >= end)
      str->append_wc(wc);
    else
    {
      mblen= format->charset()->cset->mb_wc(format->charset(), &wc, ptr, end);
      if (mblen < 1)
        return false;
      ptr+= mblen;

      switch (wc) {
      case 'M':
        if (type == MYSQL_TIMESTAMP_TIME || !l_time->month)
          return 1;
        str->append(locale->month_names->type_names[l_time->month-1],
                    (uint) strlen(locale->month_names->type_names[l_time->month-1]),
                    system_charset_info);
        break;
      case 'b':
        if (type == MYSQL_TIMESTAMP_TIME || !l_time->month)
          return 1;
        str->append(locale->ab_month_names->type_names[l_time->month-1],
                    (uint) strlen(locale->ab_month_names->type_names[l_time->month-1]),
                    system_charset_info);
        break;
      case 'W':
        if (type == MYSQL_TIMESTAMP_TIME || !(l_time->month || l_time->year))
          return 1;
        weekday= calc_weekday(calc_daynr(l_time->year,l_time->month,
                              l_time->day),0);
        str->append(locale->day_names->type_names[weekday],
                    (uint) strlen(locale->day_names->type_names[weekday]),
                    system_charset_info);
        break;
      case 'a':
        if (type == MYSQL_TIMESTAMP_TIME || !(l_time->month || l_time->year))
          return 1;
        weekday=calc_weekday(calc_daynr(l_time->year,l_time->month,
                             l_time->day),0);
        str->append(locale->ab_day_names->type_names[weekday],
                    (uint) strlen(locale->ab_day_names->type_names[weekday]),
                    system_charset_info);
        break;
      case 'D':
	if (type == MYSQL_TIMESTAMP_TIME)
	  return 1;
	str->append_zerofill(l_time->day, 1);
	if (l_time->day >= 10 &&  l_time->day <= 19)
	  str->append(STRING_WITH_LEN("th"));
	else
	{
	  switch (l_time->day %10) {
	  case 1:
	    str->append(STRING_WITH_LEN("st"));
	    break;
	  case 2:
	    str->append(STRING_WITH_LEN("nd"));
	    break;
	  case 3:
	    str->append(STRING_WITH_LEN("rd"));
	    break;
	  default:
	    str->append(STRING_WITH_LEN("th"));
	    break;
	  }
	}
	break;
      case 'Y':
        if (type == MYSQL_TIMESTAMP_TIME)
          return 1;
	str->append_zerofill(l_time->year, 4);
	break;
      case 'y':
        if (type == MYSQL_TIMESTAMP_TIME)
          return 1;
	str->append_zerofill(l_time->year % 100, 2);
	break;
      case 'm':
        if (type == MYSQL_TIMESTAMP_TIME)
          return 1;
	str->append_zerofill(l_time->month, 2);
	break;
      case 'c':
        if (type == MYSQL_TIMESTAMP_TIME)
          return 1;
	str->append_zerofill(l_time->month, 1);
	break;
      case 'd':
	if (type == MYSQL_TIMESTAMP_TIME)
	  return 1;
	str->append_zerofill(l_time->day, 2);
	break;
      case 'e':
	if (type == MYSQL_TIMESTAMP_TIME)
	  return 1;
	str->append_zerofill(l_time->day, 1);
	break;
      case 'f':
	str->append_zerofill((uint) l_time->second_part, 6);
	break;
      case 'H':
	str->append_zerofill(l_time->hour, 2);
	break;
      case 'h':
      case 'I':
	hours_i= (l_time->hour%24 + 11)%12+1;
	str->append_zerofill(hours_i, 2);
	break;
      case 'i':					/* minutes */
	str->append_zerofill(l_time->minute, 2);
	break;
      case 'j':
      {
	if (type == MYSQL_TIMESTAMP_TIME || !l_time->month || !l_time->year)
	  return 1;
        long value= calc_daynr(l_time->year,l_time->month, l_time->day) -
                    calc_daynr(l_time->year,1,1) + 1;
	str->append_zerofill((uint) value, 3);
	break;
      }
      case 'k':
	str->append_zerofill(l_time->hour, 1);
	break;
      case 'l':
	hours_i= (l_time->hour%24 + 11)%12+1;
	str->append_zerofill(hours_i, 1);
	break;
      case 'p':
	hours_i= l_time->hour%24;
	str->append(hours_i < 12 ? "AM" : "PM",2);
	break;
      case 'r':
	length= sprintf(intbuff, ((l_time->hour % 24) < 12) ?
                    "%02d:%02d:%02d AM" : "%02d:%02d:%02d PM",
		    (l_time->hour+11)%12+1,
		    l_time->minute,
		    l_time->second);
	str->append(intbuff, length);
	break;
      case 'S':
      case 's':
	str->append_zerofill(l_time->second, 2);
	break;
      case 'T':
	length= sprintf(intbuff, "%02d:%02d:%02d",
		    l_time->hour, l_time->minute, l_time->second);
	str->append(intbuff, length);
	break;
      case 'U':
      case 'u':
      {
	uint year;
	if (type == MYSQL_TIMESTAMP_TIME)
	  return 1;

        uint value= calc_week(l_time,
                              wc == 'U' ? WEEK_FIRST_WEEKDAY :
                                          WEEK_MONDAY_FIRST,
                              &year);
	str->append_zerofill(value, 2);
      }
      break;
      case 'v':
      case 'V':
      {
        uint year;
        if (type == MYSQL_TIMESTAMP_TIME)
          return 1;
        uint value= calc_week(l_time, wc == 'V' ?
                                      (WEEK_YEAR | WEEK_FIRST_WEEKDAY) :
                                      (WEEK_YEAR | WEEK_MONDAY_FIRST),
                              &year);
        str->append_zerofill(value, 2);
      }
      break;
      case 'x':
      case 'X':
      {
        uint year;
        if (type == MYSQL_TIMESTAMP_TIME)
          return 1;
        (void) calc_week(l_time,
                         (wc == 'X' ?
                          WEEK_YEAR | WEEK_FIRST_WEEKDAY :
                          WEEK_YEAR | WEEK_MONDAY_FIRST),
                         &year);
        str->append_zerofill(year, 4);
      }
      break;
      case 'w':
	if (type == MYSQL_TIMESTAMP_TIME || !(l_time->month || l_time->year))
	  return 1;
	weekday=calc_weekday(calc_daynr(l_time->year,l_time->month,
					l_time->day),1);
	str->append_zerofill(weekday, 1);
	break;

      default:
	str->append_wc(wc);
	break;
      }
    }
  }
  return 0;
}


/**
  @details
  Get a array of positive numbers from a string object.
  Each number is separated by 1 non digit character
  Return error if there is too many numbers.
  If there is too few numbers, assume that the numbers are left out
  from the high end. This allows one to give:
  DAY_TO_SECOND as "D MM:HH:SS", "MM:HH:SS" "HH:SS" or as seconds.

  @param length:         length of str
  @param cs:             charset of str
  @param values:         array of results
  @param count:          count of elements in result array
  @param transform_msec: if value is true we suppose
                         that the last part of string value is microseconds
                         and we should transform value to six digit value.
                         For example, '1.1' -> '1.100000'
*/

#define MAX_DIGITS_IN_TIME_SPEC 20

static bool get_interval_info(const char *str, size_t length,CHARSET_INFO *cs,
                              size_t count, ulonglong *values,
                              bool transform_msec)
{
  const char *end=str+length;
  uint i;
  size_t field_length= 0;

  while (str != end && !my_isdigit(cs,*str))
    str++;

  for (i=0 ; i < count ; i++)
  {
    ulonglong value;
    const char *start= str;
    const char *local_end= end;

    /*
      We limit things to 19 digits to not get an overflow. This is ok as
      this function is meant to read up to microseconds
    */
    if ((local_end-str) > MAX_DIGITS_IN_TIME_SPEC)
      local_end= str+ MAX_DIGITS_IN_TIME_SPEC;

    for (value= 0; str != local_end && my_isdigit(cs, *str) ; str++)
      value= value*10 + *str - '0';

    if ((field_length= (size_t)(str - start)) >= MAX_DIGITS_IN_TIME_SPEC)
      return true;
    values[i]= value;
    while (str != end && !my_isdigit(cs,*str))
      str++;
    if (str == end && i != count-1)
    {
      i++;
      /* Change values[0...i-1] -> values[0...count-1] */
      bmove_upp((uchar*) (values+count), (uchar*) (values+i),
		sizeof(*values)*i);
      bzero((uchar*) values, sizeof(*values)*(count-i));
      break;
    }
  }

  if (transform_msec && field_length > 0)
  {
    if (field_length < 6)
      values[count - 1] *= log_10_int[6 - field_length];
    else if (field_length > 6)
      values[count - 1] /= log_10_int[field_length - 6];
  }

  return (str != end);
}


longlong Item_func_period_add::val_int()
{
  DBUG_ASSERT(fixed());
  ulong period=(ulong) args[0]->val_int();
  int months=(int) args[1]->val_int();

  if ((null_value=args[0]->null_value || args[1]->null_value) ||
      period == 0L)
    return 0; /* purecov: inspected */
  return (longlong)
    convert_month_to_period((uint) ((int) convert_period_to_month(period)+
				    months));
}


longlong Item_func_period_diff::val_int()
{
  DBUG_ASSERT(fixed());
  ulong period1=(ulong) args[0]->val_int();
  ulong period2=(ulong) args[1]->val_int();

  if ((null_value=args[0]->null_value || args[1]->null_value))
    return 0; /* purecov: inspected */
  return (longlong) ((long) convert_period_to_month(period1)-
		     (long) convert_period_to_month(period2));
}



longlong Item_func_to_days::val_int()
{
  DBUG_ASSERT(fixed());
  THD *thd= current_thd;
  Datetime d(thd, args[0], Datetime::Options(TIME_NO_ZEROS, thd));
  return (null_value= !d.is_valid_datetime()) ? 0 : d.daynr();
}


longlong Item_func_to_seconds::val_int_endpoint(bool left_endp,
                                                bool *incl_endp)
{
  DBUG_ASSERT(fixed());
  // val_int_endpoint() is called only if args[0] is a temporal Item_field
  Datetime_from_temporal dt(current_thd, args[0], TIME_FUZZY_DATES);
  if ((null_value= !dt.is_valid_datetime()))
  {
    /* got NULL, leave the incl_endp intact */
    return LONGLONG_MIN;
  }
  /* Set to NULL if invalid date, but keep the value */
  null_value= dt.check_date(TIME_NO_ZEROS);
  /*
    Even if the evaluation return NULL, seconds is useful for pruning
  */
  return dt.to_seconds();
}

longlong Item_func_to_seconds::val_int()
{
  DBUG_ASSERT(fixed());
  THD *thd= current_thd;
  /*
    Unlike val_int_endpoint(), we cannot use Datetime_from_temporal here.
    The argument can be of a non-temporal data type.
  */
  Datetime dt(thd, args[0], Datetime::Options(TIME_NO_ZEROS, thd));
  return (null_value= !dt.is_valid_datetime()) ? 0 : dt.to_seconds();
}

/*
  Get information about this Item tree monotonicity

  SYNOPSIS
    Item_func_to_days::get_monotonicity_info()

  DESCRIPTION
  Get information about monotonicity of the function represented by this item
  tree.

  RETURN
    See enum_monotonicity_info.
*/

enum_monotonicity_info Item_func_to_days::get_monotonicity_info() const
{
  if (args[0]->type() == Item::FIELD_ITEM)
  {
    if (args[0]->field_type() == MYSQL_TYPE_DATE)
      return MONOTONIC_STRICT_INCREASING_NOT_NULL;
    if (args[0]->field_type() == MYSQL_TYPE_DATETIME)
      return MONOTONIC_INCREASING_NOT_NULL;
  }
  return NON_MONOTONIC;
}

enum_monotonicity_info Item_func_to_seconds::get_monotonicity_info() const
{
  if (args[0]->type() == Item::FIELD_ITEM)
  {
    if (args[0]->field_type() == MYSQL_TYPE_DATE ||
        args[0]->field_type() == MYSQL_TYPE_DATETIME)
      return MONOTONIC_STRICT_INCREASING_NOT_NULL;
  }
  return NON_MONOTONIC;
}


longlong Item_func_to_days::val_int_endpoint(bool left_endp, bool *incl_endp)
{
  DBUG_ASSERT(fixed());
  // val_int_endpoint() is only called if args[0] is a temporal Item_field
  Datetime_from_temporal dt(current_thd, args[0], TIME_CONV_NONE);
  longlong res;
  if ((null_value= !dt.is_valid_datetime()))
  {
    /* got NULL, leave the incl_endp intact */
    return LONGLONG_MIN;
  }
  res= (longlong) dt.daynr();
  /* Set to NULL if invalid date, but keep the value */
  null_value= dt.check_date(TIME_NO_ZEROS);
  if (null_value)
  {
    /*
      Even if the evaluation return NULL, the calc_daynr is useful for pruning
    */
    if (args[0]->field_type() != MYSQL_TYPE_DATE)
      *incl_endp= TRUE;
    return res;
  }
  
  if (args[0]->field_type() == MYSQL_TYPE_DATE)
  {
    // TO_DAYS() is strictly monotonic for dates, leave incl_endp intact
    return res;
  }
 
  /*
    Handle the special but practically useful case of datetime values that
    point to day bound ("strictly less" comparison stays intact):

      col < '2007-09-15 00:00:00'  -> TO_DAYS(col) <  TO_DAYS('2007-09-15')
      col > '2007-09-15 23:59:59'  -> TO_DAYS(col) >  TO_DAYS('2007-09-15')

    which is different from the general case ("strictly less" changes to
    "less or equal"):

      col < '2007-09-15 12:34:56'  -> TO_DAYS(col) <= TO_DAYS('2007-09-15')
  */
  const MYSQL_TIME &ltime= dt.get_mysql_time()[0];
  if ((!left_endp && dt.hhmmssff_is_zero()) ||
       (left_endp && ltime.hour == 23 && ltime.minute == 59 &&
        ltime.second == 59))
    /* do nothing */
    ;
  else
    *incl_endp= TRUE;
  return res;
}


longlong Item_func_dayofyear::val_int()
{
  DBUG_ASSERT(fixed());
  THD *thd= current_thd;
  Datetime d(thd, args[0], Datetime::Options(TIME_NO_ZEROS, thd));
  return (null_value= !d.is_valid_datetime()) ? 0 : d.dayofyear();
}

longlong Item_func_dayofmonth::val_int()
{
  DBUG_ASSERT(fixed());
  THD *thd= current_thd;
  Datetime d(thd, args[0], Datetime::Options(TIME_CONV_NONE, thd));
  return (null_value= !d.is_valid_datetime()) ? 0 : d.get_mysql_time()->day;
}

longlong Item_func_month::val_int()
{
  DBUG_ASSERT(fixed());
  THD *thd= current_thd;
  Datetime d(thd, args[0], Datetime::Options(TIME_CONV_NONE, thd));
  return (null_value= !d.is_valid_datetime()) ? 0 : d.get_mysql_time()->month;
}


bool Item_func_monthname::fix_length_and_dec(THD *thd)
{
  CHARSET_INFO *cs= thd->variables.collation_connection;
  locale= thd->variables.lc_time_names;
  collation.set(cs, DERIVATION_COERCIBLE, locale->repertoire());
  decimals=0;
  max_length= locale->max_month_name_length * collation.collation->mbmaxlen;
  set_maybe_null();
  return FALSE;
}


String* Item_func_monthname::val_str(String* str)
{
  DBUG_ASSERT(fixed());
  const char *month_name;
  uint err;
  THD *thd= current_thd;
  Datetime d(thd, args[0], Datetime::Options(TIME_CONV_NONE, thd));
  if ((null_value= (!d.is_valid_datetime() || !d.get_mysql_time()->month)))
    return (String *) 0;

  month_name= locale->month_names->type_names[d.get_mysql_time()->month - 1];
  str->copy(month_name, (uint) strlen(month_name), &my_charset_utf8mb3_bin,
	    collation.collation, &err);
  return str;
}


/**
  Returns the quarter of the year.
*/

longlong Item_func_quarter::val_int()
{
  DBUG_ASSERT(fixed());
  THD *thd= current_thd;
  Datetime d(thd, args[0], Datetime::Options(TIME_CONV_NONE, thd));
  return (null_value= !d.is_valid_datetime()) ? 0 : d.quarter();
}

longlong Item_func_hour::val_int()
{
  DBUG_ASSERT(fixed());
  THD *thd= current_thd;
  Time tm(thd, args[0], Time::Options_for_cast(thd));
  return (null_value= !tm.is_valid_time()) ? 0 : tm.get_mysql_time()->hour;
}

longlong Item_func_minute::val_int()
{
  DBUG_ASSERT(fixed());
  THD *thd= current_thd;
  Time tm(thd, args[0], Time::Options_for_cast(thd));
  return (null_value= !tm.is_valid_time()) ? 0 : tm.get_mysql_time()->minute;
}

/**
  Returns the second in time_exp in the range of 0 - 59.
*/
longlong Item_func_second::val_int()
{
  DBUG_ASSERT(fixed());
  THD *thd= current_thd;
  Time tm(thd, args[0], Time::Options_for_cast(thd));
  return (null_value= !tm.is_valid_time()) ? 0 : tm.get_mysql_time()->second;
}


uint week_mode(uint mode)
{
  uint week_format= (mode & 7);
  if (!(week_format & WEEK_MONDAY_FIRST))
    week_format^= WEEK_FIRST_WEEKDAY;
  return week_format;
}

/**
 @verbatim
  The bits in week_format(for calc_week() function) has the following meaning:
   WEEK_MONDAY_FIRST (0)  If not set	Sunday is first day of week
      		   	  If set	Monday is first day of week
   WEEK_YEAR (1)	  If not set	Week is in range 0-53

   	Week 0 is returned for the last week of the previous year (for
	a date at start of january) In this case one can get 53 for the
	first week of next year.  This flag ensures that the week is
	relevant for the given year. Note that this flag is only
	relevant if WEEK_JANUARY is not set.

			  If set	 Week is in range 1-53.

	In this case one may get week 53 for a date in January (when
	the week is that last week of previous year) and week 1 for a
	date in December.

  WEEK_FIRST_WEEKDAY (2)  If not set	Weeks are numbered according
			   		to ISO 8601:1988
			  If set	The week that contains the first
					'first-day-of-week' is week 1.
	
	ISO 8601:1988 means that if the week containing January 1 has
	four or more days in the new year, then it is week 1;
	Otherwise it is the last week of the previous year, and the
	next week is week 1.
 @endverbatim
*/

longlong Item_func_week::val_int()
{
  DBUG_ASSERT(fixed());
  uint week_format;
  THD *thd= current_thd;
  Datetime d(thd, args[0], Datetime::Options(TIME_NO_ZEROS, thd));
  if ((null_value= !d.is_valid_datetime()))
    return 0;
  if (arg_count > 1)
    week_format= (uint)args[1]->val_int();
  else
    week_format= thd->variables.default_week_format;
  return d.week(week_mode(week_format));
}


longlong Item_func_yearweek::val_int()
{
  DBUG_ASSERT(fixed());
  THD *thd= current_thd;
  Datetime d(thd, args[0], Datetime::Options(TIME_NO_ZEROS, thd));
  return (null_value= !d.is_valid_datetime()) ? 0 :
         d.yearweek((week_mode((uint) args[1]->val_int()) | WEEK_YEAR));
}


longlong Item_func_weekday::val_int()
{
  DBUG_ASSERT(fixed());
  THD *thd= current_thd;
  Datetime dt(thd, args[0], Datetime::Options(TIME_NO_ZEROS, thd));
  if ((null_value= !dt.is_valid_datetime()))
    return 0;
  return dt.weekday(odbc_type) + MY_TEST(odbc_type);
}

bool Item_func_dayname::fix_length_and_dec(THD *thd)
{
  CHARSET_INFO *cs= thd->variables.collation_connection;
  locale= thd->variables.lc_time_names;  
  collation.set(cs, DERIVATION_COERCIBLE, locale->repertoire());
  decimals=0;
  max_length= locale->max_day_name_length * collation.collation->mbmaxlen;
  set_maybe_null();
  return FALSE;
}


String* Item_func_dayname::val_str(String* str)
{
  DBUG_ASSERT(fixed());
  const char *day_name;
  uint err;
  THD *thd= current_thd;
  Datetime dt(thd, args[0], Datetime::Options(TIME_NO_ZEROS, thd));

  if ((null_value= !dt.is_valid_datetime()))
    return (String*) 0;

  day_name= locale->day_names->type_names[dt.weekday(false)];
  str->copy(day_name, (uint) strlen(day_name), &my_charset_utf8mb3_bin,
	    collation.collation, &err);
  return str;
}


longlong Item_func_year::val_int()
{
  DBUG_ASSERT(fixed());
  THD *thd= current_thd;
  Datetime d(thd, args[0], Datetime::Options(TIME_CONV_NONE, thd));
  return (null_value= !d.is_valid_datetime()) ? 0 : d.get_mysql_time()->year;
}


/*
  Get information about this Item tree monotonicity

  SYNOPSIS
    Item_func_year::get_monotonicity_info()

  DESCRIPTION
  Get information about monotonicity of the function represented by this item
  tree.

  RETURN
    See enum_monotonicity_info.
*/

enum_monotonicity_info Item_func_year::get_monotonicity_info() const
{
  if (args[0]->type() == Item::FIELD_ITEM &&
      (args[0]->field_type() == MYSQL_TYPE_DATE ||
       args[0]->field_type() == MYSQL_TYPE_DATETIME))
    return MONOTONIC_INCREASING;
  return NON_MONOTONIC;
}


longlong Item_func_year::val_int_endpoint(bool left_endp, bool *incl_endp)
{
  DBUG_ASSERT(fixed());
  // val_int_endpoint() is cally only if args[0] is a temporal Item_field
  Datetime_from_temporal dt(current_thd, args[0], TIME_CONV_NONE);
  if ((null_value= !dt.is_valid_datetime()))
  {
    /* got NULL, leave the incl_endp intact */
    return LONGLONG_MIN;
  }

  /*
    Handle the special but practically useful case of datetime values that
    point to year bound ("strictly less" comparison stays intact) :

      col < '2007-01-01 00:00:00'  -> YEAR(col) <  2007

    which is different from the general case ("strictly less" changes to
    "less or equal"):

      col < '2007-09-15 23:00:00'  -> YEAR(col) <= 2007
  */
  const MYSQL_TIME &ltime= dt.get_mysql_time()[0];
  if (!left_endp && ltime.day == 1 && ltime.month == 1 && 
      dt.hhmmssff_is_zero())
    ; /* do nothing */
  else
    *incl_endp= TRUE;
  return ltime.year;
}


bool Item_func_unix_timestamp::get_timestamp_value(my_time_t *seconds,
                                                   ulong *second_part)
{
  DBUG_ASSERT(fixed());
  if (args[0]->type() == FIELD_ITEM)
  {						// Optimize timestamp field
    Field *field=((Item_field*) args[0])->field;
    if (field->type() == MYSQL_TYPE_TIMESTAMP)
    {
      if ((null_value= field->is_null()))
        return 1;
      *seconds= field->get_timestamp(second_part);
      return 0;
    }
  }

  Timestamp_or_zero_datetime_native_null native(current_thd, args[0], true);
  if ((null_value= native.is_null() || native.is_zero_datetime()))
    return true;
  Timestamp tm(native);
  *seconds= tm.tv().tv_sec;
  *second_part= tm.tv().tv_usec;
  return false;
}


longlong Item_func_unix_timestamp::int_op()
{
  if (arg_count == 0)
    return (longlong) current_thd->query_start();
  
  ulong second_part;
  my_time_t seconds;
  if (get_timestamp_value(&seconds, &second_part))
    return 0;

  return seconds;
}


my_decimal *Item_func_unix_timestamp::decimal_op(my_decimal* buf)
{
  ulong second_part;
  my_time_t seconds;
  if (get_timestamp_value(&seconds, &second_part))
    return 0;

  return seconds2my_decimal(seconds < 0, seconds < 0 ? -seconds : seconds,
                            second_part, buf);
}


enum_monotonicity_info Item_func_unix_timestamp::get_monotonicity_info() const
{
  if (args[0]->type() == Item::FIELD_ITEM &&
      (args[0]->field_type() == MYSQL_TYPE_TIMESTAMP))
    return MONOTONIC_INCREASING;
  return NON_MONOTONIC;
}


longlong Item_func_unix_timestamp::val_int_endpoint(bool left_endp, bool *incl_endp)
{
  DBUG_ASSERT(fixed());
  DBUG_ASSERT(arg_count == 1 &&
              args[0]->type() == Item::FIELD_ITEM &&
              args[0]->field_type() == MYSQL_TYPE_TIMESTAMP);
  Field *field= ((Item_field*)args[0])->field;
  /* Leave the incl_endp intact */
  ulong unused;
  my_time_t ts= field->get_timestamp(&unused);
  null_value= field->is_null();
  return ts;
}


longlong Item_func_time_to_sec::int_op()
{
  DBUG_ASSERT(fixed());
  THD *thd= current_thd;
  Time tm(thd, args[0], Time::Options_for_cast(thd));
  return ((null_value= !tm.is_valid_time())) ? 0 : tm.to_seconds();
}


my_decimal *Item_func_time_to_sec::decimal_op(my_decimal* buf)
{
  DBUG_ASSERT(fixed());
  THD *thd= current_thd;
  Time tm(thd, args[0], Time::Options_for_cast(thd));
  if ((null_value= !tm.is_valid_time()))
    return 0;
  const MYSQL_TIME *ltime= tm.get_mysql_time();
  longlong seconds= tm.to_seconds_abs();
  return seconds2my_decimal(ltime->neg, seconds, ltime->second_part, buf);
}


static inline
uint32 adjust_interval_field_uint32(ulonglong value, int32 multiplier)
{
  return value > ((ulonglong) (uint32) (UINT_MAX32)) / multiplier ?
         (uint32) UINT_MAX32 :
         (uint32) (value * multiplier);
}


/**
  Convert a string to a interval value.

  To make code easy, allow interval objects without separators.
*/

bool get_interval_value(THD *thd, Item *args,
                        interval_type int_type, INTERVAL *interval)
{
  ulonglong array[5];
  ulonglong UNINIT_VAR(value);
  const char *UNINIT_VAR(str);
  size_t UNINIT_VAR(length);
  CHARSET_INFO *UNINIT_VAR(cs);
  char buf[100];
  String str_value(buf, sizeof(buf), &my_charset_bin);

  bzero((char*) interval,sizeof(*interval));
  if (int_type == INTERVAL_SECOND && args->decimals)
  {
    VDec val(args);
    if (val.is_null())
      return true;
    Sec6 d(val.ptr());
    interval->neg= d.neg();
    if (d.sec() >= LONGLONG_MAX)
    {
      ErrConvDecimal err(val.ptr());
      thd->push_warning_truncated_wrong_value("seconds", err.ptr());
      return true;
    }
    interval->second= d.sec();
    interval->second_part= d.usec();
    return false;
  }
  else if ((int) int_type <= INTERVAL_MICROSECOND)
  {
    /*
      Let's use Longlong_hybrid_null to handle correctly:
      - signed and unsigned values
      - the corner case with LONGLONG_MIN
        (avoid undefined behavior with its negation)
    */
    const Longlong_hybrid_null nr= args->to_longlong_hybrid_null();
    if (nr.is_null())
      return true;
    value= nr.abs();
    interval->neg= nr.neg() ? 1 : 0;
  }
  else
  {
    String *res;
    if (!(res= args->val_str_ascii(&str_value)))
      return (1);

    /* record negative intervals in interval->neg */
    str=res->ptr();
    cs= res->charset();
    const char *end=str+res->length();
    while (str != end && my_isspace(cs,*str))
      str++;
    if (str != end && *str == '-')
    {
      interval->neg=1;
      str++;
    }
    length= (size_t) (end-str);		// Set up pointers to new str
  }

  switch (int_type) {
  case INTERVAL_YEAR:
    interval->year= (ulong) value;
    break;
  case INTERVAL_QUARTER:
    interval->month= adjust_interval_field_uint32(value, 3);
    break;
  case INTERVAL_MONTH:
    interval->month= (ulong) value;
    break;
  case INTERVAL_WEEK:
    interval->day= adjust_interval_field_uint32(value, 7);
    break;
  case INTERVAL_DAY:
    interval->day= (ulong) value;
    break;
  case INTERVAL_HOUR:
    interval->hour= (ulong) value;
    break;
  case INTERVAL_MICROSECOND:
    interval->second_part=value;
    break;
  case INTERVAL_MINUTE:
    interval->minute=value;
    break;
  case INTERVAL_SECOND:
    interval->second=value;
    break;
  case INTERVAL_YEAR_MONTH:			// Allow YEAR-MONTH YYYYYMM
    if (get_interval_info(str,length,cs,2,array,0))
      return (1);
    interval->year=  (ulong) array[0];
    interval->month= (ulong) array[1];
    break;
  case INTERVAL_DAY_HOUR:
    if (get_interval_info(str,length,cs,2,array,0))
      return (1);
    interval->day=  (ulong) array[0];
    interval->hour= (ulong) array[1];
    break;
  case INTERVAL_DAY_MICROSECOND:
    if (get_interval_info(str,length,cs,5,array,1))
      return (1);
    interval->day=    (ulong) array[0];
    interval->hour=   (ulong) array[1];
    interval->minute= array[2];
    interval->second= array[3];
    interval->second_part= array[4];
    break;
  case INTERVAL_DAY_MINUTE:
    if (get_interval_info(str,length,cs,3,array,0))
      return (1);
    interval->day=    (ulong) array[0];
    interval->hour=   (ulong) array[1];
    interval->minute= array[2];
    break;
  case INTERVAL_DAY_SECOND:
    if (get_interval_info(str,length,cs,4,array,0))
      return (1);
    interval->day=    (ulong) array[0];
    interval->hour=   (ulong) array[1];
    interval->minute= array[2];
    interval->second= array[3];
    break;
  case INTERVAL_HOUR_MICROSECOND:
    if (get_interval_info(str,length,cs,4,array,1))
      return (1);
    interval->hour=   (ulong) array[0];
    interval->minute= array[1];
    interval->second= array[2];
    interval->second_part= array[3];
    break;
  case INTERVAL_HOUR_MINUTE:
    if (get_interval_info(str,length,cs,2,array,0))
      return (1);
    interval->hour=   (ulong) array[0];
    interval->minute= array[1];
    break;
  case INTERVAL_HOUR_SECOND:
    if (get_interval_info(str,length,cs,3,array,0))
      return (1);
    interval->hour=   (ulong) array[0];
    interval->minute= array[1];
    interval->second= array[2];
    break;
  case INTERVAL_MINUTE_MICROSECOND:
    if (get_interval_info(str,length,cs,3,array,1))
      return (1);
    interval->minute= array[0];
    interval->second= array[1];
    interval->second_part= array[2];
    break;
  case INTERVAL_MINUTE_SECOND:
    if (get_interval_info(str,length,cs,2,array,0))
      return (1);
    interval->minute= array[0];
    interval->second= array[1];
    break;
  case INTERVAL_SECOND_MICROSECOND:
    if (get_interval_info(str,length,cs,2,array,1))
      return (1);
    interval->second= array[0];
    interval->second_part= array[1];
    break;
  case INTERVAL_LAST: /* purecov: begin deadcode */
    DBUG_ASSERT(0); 
    break;            /* purecov: end */
  }
  return 0;
}


bool Item_func_from_days::get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate)
{
  longlong value=args[0]->val_int();
  if ((null_value= (args[0]->null_value ||
                    ((fuzzydate & TIME_NO_ZERO_DATE) && value == 0))))
    return true;
  bzero(ltime, sizeof(MYSQL_TIME));
  if (get_date_from_daynr((long) value, &ltime->year, &ltime->month,
                          &ltime->day))
    return 0;

  ltime->time_type= MYSQL_TIMESTAMP_DATE;
  return 0;
}


/**
    Converts current time in my_time_t to MYSQL_TIME representation for local
    time zone. Defines time zone (local) used for whole CURDATE function.
*/
void Item_func_curdate_local::store_now_in_TIME(THD *thd, MYSQL_TIME *now_time)
{
  thd->variables.time_zone->gmt_sec_to_TIME(now_time, thd->query_start());
  thd->used |= THD::TIME_ZONE_USED;
}


/**
    Converts current time in my_time_t to MYSQL_TIME representation for UTC
    time zone. Defines time zone (UTC) used for whole UTC_DATE function.
*/
void Item_func_curdate_utc::store_now_in_TIME(THD *thd, MYSQL_TIME *now_time)
{
  my_tz_UTC->gmt_sec_to_TIME(now_time, thd->query_start());
  /* 
    We are not flagging this query as using time zone, since it uses fixed
    UTC-SYSTEM time-zone.
  */
}


bool Item_func_curdate::get_date(THD *thd, MYSQL_TIME *res,
				 date_mode_t fuzzydate __attribute__((unused)))
{
  query_id_t query_id= thd->query_id;
  /* Cache value for this query */
  if (last_query_id != query_id)
  {
    last_query_id= query_id;
    store_now_in_TIME(thd, &ltime);
    /* We don't need to set second_part and neg because they already 0 */
    ltime.hour= ltime.minute= ltime.second= 0;
    ltime.time_type= MYSQL_TIMESTAMP_DATE;
  }
  *res=ltime;
  return 0;
}


bool Item_func_curtime::fix_fields(THD *thd, Item **items)
{
  if (decimals > TIME_SECOND_PART_DIGITS)
  {
    my_error(ER_TOO_BIG_PRECISION, MYF(0),
             func_name(), TIME_SECOND_PART_DIGITS);
    return 1;
  }
  return Item_timefunc::fix_fields(thd, items);
}

bool Item_func_curtime::get_date(THD *thd, MYSQL_TIME *res,
                                 date_mode_t fuzzydate __attribute__((unused)))
{
  query_id_t query_id= thd->query_id;
  /* Cache value for this query */
  if (last_query_id != query_id)
  {
    last_query_id= query_id;
    store_now_in_TIME(thd, &ltime);
  }
  *res= ltime;
  return 0;
}

void Item_func_curtime::print(String *str, enum_query_type query_type)
{
  str->append(func_name_cstring());
  str->append('(');
  if (decimals)
    str->append_ulonglong(decimals);
  str->append(')');
}

static void set_sec_part(ulong sec_part, MYSQL_TIME *ltime, Item *item)
{
  DBUG_ASSERT(item->decimals == AUTO_SEC_PART_DIGITS ||
              item->decimals <= TIME_SECOND_PART_DIGITS);
  if (item->decimals)
  {
    ltime->second_part= sec_part;
    if (item->decimals < TIME_SECOND_PART_DIGITS)
      my_datetime_trunc(ltime, item->decimals);
  }
}

/**
    Converts current time in my_time_t to MYSQL_TIME representation for local
    time zone. Defines time zone (local) used for whole CURTIME function.
*/
void Item_func_curtime_local::store_now_in_TIME(THD *thd, MYSQL_TIME *now_time)
{
  thd->variables.time_zone->gmt_sec_to_TIME(now_time, thd->query_start());
  now_time->year= now_time->month= now_time->day= 0;
  now_time->time_type= MYSQL_TIMESTAMP_TIME;
  set_sec_part(thd->query_start_sec_part(), now_time, this);
  thd->used|= THD::TIME_ZONE_USED;
}


/**
    Converts current time in my_time_t to MYSQL_TIME representation for UTC
    time zone. Defines time zone (UTC) used for whole UTC_TIME function.
*/
void Item_func_curtime_utc::store_now_in_TIME(THD *thd, MYSQL_TIME *now_time)
{
  my_tz_UTC->gmt_sec_to_TIME(now_time, thd->query_start());
  now_time->year= now_time->month= now_time->day= 0;
  now_time->time_type= MYSQL_TIMESTAMP_TIME;
  set_sec_part(thd->query_start_sec_part(), now_time, this);
  /* 
    We are not flagging this query as using time zone, since it uses fixed
    UTC-SYSTEM time-zone.
  */
}

bool Item_func_now::fix_fields(THD *thd, Item **items)
{
  if (decimals > TIME_SECOND_PART_DIGITS)
  {
    my_error(ER_TOO_BIG_PRECISION, MYF(0),
             func_name(), TIME_SECOND_PART_DIGITS);
    return 1;
  }
  return Item_datetimefunc::fix_fields(thd, items);
}

void Item_func_now::print(String *str, enum_query_type query_type)
{
  str->append(func_name_cstring());
  str->append('(');
  if (decimals)
    str->append_ulonglong(decimals);
  str->append(')');
}


int Item_func_now_local::save_in_field(Field *field, bool no_conversions)
{
  if (field->type() == MYSQL_TYPE_TIMESTAMP)
  {
    THD *thd= field->get_thd();
    my_time_t ts= thd->query_start();
    ulong sec_part= decimals ? thd->query_start_sec_part() : 0;
    sec_part-= my_time_fraction_remainder(sec_part, decimals);
    field->set_notnull();
    field->store_timestamp(ts, sec_part);
    return 0;
  }
  else
    return Item_datetimefunc::save_in_field(field, no_conversions);
}


/**
    Converts current time in my_time_t to MYSQL_TIME representation for local
    time zone. Defines time zone (local) used for whole NOW function.
*/
void Item_func_now_local::store_now_in_TIME(THD *thd, MYSQL_TIME *now_time)
{
  thd->variables.time_zone->gmt_sec_to_TIME(now_time, thd->query_start());
  set_sec_part(thd->query_start_sec_part(), now_time, this);
  thd->used|= THD::TIME_ZONE_USED;
}


/**
    Converts current time in my_time_t to MYSQL_TIME representation for UTC
    time zone. Defines time zone (UTC) used for whole UTC_TIMESTAMP function.
*/
void Item_func_now_utc::store_now_in_TIME(THD *thd, MYSQL_TIME *now_time)
{
  my_tz_UTC->gmt_sec_to_TIME(now_time, thd->query_start());
  set_sec_part(thd->query_start_sec_part(), now_time, this);
  /* 
    We are not flagging this query as using time zone, since it uses fixed
    UTC-SYSTEM time-zone.
  */
}


bool Item_func_now::get_date(THD *thd, MYSQL_TIME *res,
                             date_mode_t fuzzydate __attribute__((unused)))
{
  query_id_t query_id= thd->query_id;
  /* Cache value for this query */
  if (last_query_id != query_id)
  {
    last_query_id= query_id;
    store_now_in_TIME(thd, &ltime);
  }
  *res= ltime;
  return 0;
}


/**
    Converts current time in my_time_t to MYSQL_TIME representation for local
    time zone. Defines time zone (local) used for whole SYSDATE function.
*/
void Item_func_sysdate_local::store_now_in_TIME(THD *thd, MYSQL_TIME *now_time)
{
  my_hrtime_t now= my_hrtime();
  thd->variables.time_zone->gmt_sec_to_TIME(now_time, hrtime_to_my_time(now));
  set_sec_part(hrtime_sec_part(now), now_time, this);
  thd->used|= THD::TIME_ZONE_USED;
}


bool Item_func_sysdate_local::get_date(THD *thd, MYSQL_TIME *res,
                                       date_mode_t fuzzydate __attribute__((unused)))
{
  store_now_in_TIME(thd, res);
  return 0;
}

bool Item_func_sec_to_time::get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate)
{
  DBUG_ASSERT(fixed());
  VSec9 sec(thd, args[0], "seconds", LONGLONG_MAX);
  if ((null_value= sec.is_null()))
    return true;
  sec.round(decimals, thd->temporal_round_mode());
  if (sec.sec_to_time(ltime, decimals) && !sec.truncated())
    sec.make_truncated_warning(thd, "seconds");
  return false;
}

bool Item_func_date_format::fix_length_and_dec(THD *thd)
{
  if (!is_time_format)
  {
    if (arg_count < 3)
      locale= thd->variables.lc_time_names;
    else
      if (args[2]->basic_const_item())
        locale= args[2]->locale_from_val_str();
  }

  /*
    Must use this_item() in case it's a local SP variable
    (for ->max_length and ->str_value)
  */
  Item *arg1= args[1]->this_item();

  decimals=0;
  CHARSET_INFO *cs= thd->variables.collation_connection;
  my_repertoire_t repertoire= arg1->collation.repertoire;
  if (!thd->variables.lc_time_names->is_ascii)
    repertoire|= MY_REPERTOIRE_EXTENDED;
  collation.set(cs, arg1->collation.derivation, repertoire);
  StringBuffer<STRING_BUFFER_USUAL_SIZE> buffer;
  String *str;
  if (args[1]->basic_const_item() && (str= args[1]->val_str(&buffer)))
  {						// Optimize the normal case
    fixed_length=1;
    max_length= format_length(str) * collation.collation->mbmaxlen;
  }
  else
  {
    fixed_length=0;
    max_length=MY_MIN(arg1->max_length, MAX_BLOB_WIDTH) * 10 *
                   collation.collation->mbmaxlen;
    set_if_smaller(max_length,MAX_BLOB_WIDTH);
  }
  set_maybe_null(); // If wrong date
  return FALSE;
}


bool Item_func_date_format::eq(const Item *item, const Eq_config &config) const
{
  Item_func_date_format *item_func;

  if (item->type() != FUNC_ITEM)
    return 0;
  if (func_name() != ((Item_func*) item)->func_name())
    return 0;
  if (this == item)
    return 1;
  item_func= (Item_func_date_format*) item;
  if (arg_count != item_func->arg_count)
    return 0;
  if (!args[0]->eq(item_func->args[0], config))
    return 0;
  /*
    We must compare format string case sensitive.
    This needed because format modifiers with different case,
    for example %m and %M, have different meaning.
  */
  if (!args[1]->eq(item_func->args[1], 1))
    return 0;
  if (arg_count > 2 && !args[2]->eq(item_func->args[2], 1))
    return 0;
  return 1;
}



uint Item_func_date_format::format_length(const String *format)
{
  uint size=0;
  const char *ptr=format->ptr();
  const char *end=ptr+format->length();

  for (; ptr != end ; ptr++)
  {
    if (*ptr != '%' || ptr == end-1)
      size++;
    else
    {
      switch(*++ptr) {
      case 'M': /* month, textual */
      case 'W': /* day (of the week), textual */
	size += 64; /* large for UTF8 locale data */
	break;
      case 'D': /* day (of the month), numeric plus english suffix */
      case 'Y': /* year, numeric, 4 digits */
      case 'x': /* Year, used with 'v' */
      case 'X': /* Year, used with 'v, where week starts with Monday' */
	size += 4;
	break;
      case 'a': /* locale's abbreviated weekday name (Sun..Sat) */
      case 'b': /* locale's abbreviated month name (Jan.Dec) */
	size += 32; /* large for UTF8 locale data */
	break;
      case 'j': /* day of year (001..366) */
	size += 3;
	break;
      case 'U': /* week (00..52) */
      case 'u': /* week (00..52), where week starts with Monday */
      case 'V': /* week 1..53 used with 'x' */
      case 'v': /* week 1..53 used with 'x', where week starts with Monday */
      case 'y': /* year, numeric, 2 digits */
      case 'm': /* month, numeric */
      case 'd': /* day (of the month), numeric */
      case 'h': /* hour (01..12) */
      case 'I': /* --||-- */
      case 'i': /* minutes, numeric */
      case 'l': /* hour ( 1..12) */
      case 'p': /* locale's AM or PM */
      case 'S': /* second (00..61) */
      case 's': /* seconds, numeric */
      case 'c': /* month (0..12) */
      case 'e': /* day (0..31) */
	size += 2;
	break;
      case 'k': /* hour ( 0..23) */
      case 'H': /* hour (00..23; value > 23 OK, padding always 2-digit) */
	size += 7; /* docs allow > 23, range depends on sizeof(unsigned int) */
	break;
      case 'r': /* time, 12-hour (hh:mm:ss [AP]M) */
	size += 11;
	break;
      case 'T': /* time, 24-hour (hh:mm:ss) */
	size += 8;
	break;
      case 'f': /* microseconds */
	size += 6;
	break;
      case 'w': /* day (of the week), numeric */
      case '%':
      default:
	size++;
	break;
      }
    }
  }
  return size;
}


String *Item_func_date_format::val_str(String *str)
{
  StringBuffer<64> format_buffer;
  String *format;
  MYSQL_TIME l_time;
  uint size;
  const MY_LOCALE *lc= 0;
  DBUG_ASSERT(fixed());
  date_conv_mode_t mode= is_time_format ? TIME_TIME_ONLY : TIME_CONV_NONE;
  THD *thd= current_thd;

  if ((null_value= args[0]->get_date(thd, &l_time,
                                     Temporal::Options(mode, thd))))
    return 0;
  
  if (!(format= args[1]->val_str(&format_buffer)) || !format->length())
    goto null_date;

  if (!is_time_format && !(lc= locale) && !(lc= args[2]->locale_from_val_str()))
    goto null_date; // invalid locale

  if (fixed_length)
    size=max_length;
  else
    size=format_length(format);

  if (size < MAX_DATE_STRING_REP_LENGTH)
    size= MAX_DATE_STRING_REP_LENGTH;

  DBUG_ASSERT(format != str);
  if (str->alloc(size))
    goto null_date;

  /* Create the result string */
  str->set_charset(collation.collation);
  if (!make_date_time(format, &l_time,
                      is_time_format ? MYSQL_TIMESTAMP_TIME :
                                       MYSQL_TIMESTAMP_DATE,
                      lc, str))
    return str;

null_date:
  null_value=1;
  return 0;
}

/*
  Oracle has many formatting models, we list all but only part of them
  are implemented, because some models depend on oracle functions
  which mariadb is not supported.

  Models for datetime, used by TO_CHAR/TO_DATE. Normal format characters are
  stored as short integer < 128, while format characters are stored as a
  integer > 128
*/

enum enum_tochar_formats
{
  FMT_BASE= 128,
  FMT_AD,
  FMT_AD_DOT,
  FMT_AM,
  FMT_AM_DOT,
  FMT_BC,
  FMT_BC_DOT,
  FMT_CC,
  FMT_SCC,
  FMT_D,
  FMT_DAY,
  FMT_DD,
  FMT_DDD,
  FMT_DL,
  FMT_DS,
  FMT_DY,
  FMT_E,
  FMT_EE,
  FMT_FF,
  FMT_FM,
  FMT_FX,
  FMT_HH,
  FMT_HH12,
  FMT_HH24,
  FMT_IW,
  FMT_I,
  FMT_IY,
  FMT_IYY,
  FMT_IYYY,
  FMT_J,
  FMT_MI,
  FMT_MM,
  FMT_MON,
  FMT_MONTH,
  FMT_PM,
  FMT_PM_DOT,
  FMT_RM,
  FMT_RR,
  FMT_RRRR,
  FMT_SS,
  FMT_SSSSSS,
  FMT_TS,
  FMT_TZD,
  FMT_TZH,
  FMT_TZM,
  FMT_TZR,
  FMT_W,
  FMT_WW,
  FMT_X,
  FMT_Y,
  FMT_YY,
  FMT_YYY,
  FMT_YYYY,
  FMT_YYYY_COMMA,
  FMT_YEAR,
  FMT_SYYYY,
  FMT_SYEAR
};

/**
   Flip 'quotation_flag' if we found a quote (") character.

   @param cftm             Character or FMT... format descriptor
   @param quotation_flag   Points to 'true' if we are inside a quoted string

   @return true  If we are inside a quoted string or if we found a '"' character
   @return false Otherwise
*/

static inline bool check_quotation(uint16 cfmt, bool *quotation_flag)
{
  if (cfmt == '"')
  {
    *quotation_flag= !*quotation_flag;
    return true;
  }
  return *quotation_flag;
}

#define INVALID_CHARACTER(x) (((x) >= 'A' && (x) <= 'Z') ||((x) >= '0' && (x) <= '9') || (x) >= 127 || ((x) < 32))


/**
  Special characters are directly output in the result

  @return 0  If found not acceptable character
  @return #  Number of copied characters
*/

static uint parse_special(char cfmt, const char *ptr, const char *end,
                         uint16 *array)
{
  int offset= 0;
  char tmp1;

  /* Non-printable character and Multibyte encoded characters */
  if (INVALID_CHARACTER(cfmt))
    return 0;

  /*
   * '&' with text is used for variable input, but '&' with other
   * special charaters like '|'. '*' is used as separator
   */
  if (cfmt == '&' && ptr + 1 < end)
  {
    tmp1= my_toupper(system_charset_info, *(ptr+1));
    if (tmp1 >= 'A' && tmp1 <= 'Z')
      return 0;
  }

  do {
    /*
      Continuously store the special characters in fmt_array until non-special
      characters appear
     */
    *array++= (uint16) (uchar) *ptr++;
    offset++;
    if (ptr == end)
      break;
    tmp1= my_toupper(system_charset_info, *ptr);
  } while (!INVALID_CHARACTER(tmp1) && tmp1 != '"');
  return offset;
}


/**
  Parse the format string, convert it to an compact array and calculate the
  length of output string

  @param format   Format string
  @param fmt_len  Function will store max length of formated date string here

  @return 0 ok. fmt_len is updated
  @return 1 error.  In this case 'warning_string' is set to error message
*/

bool Item_func_tochar::parse_format_string(const String *format, uint *fmt_len)
{
  const char *ptr, *end;
  uint16 *tmp_fmt= fmt_array;
  uint tmp_len= 0;
  int offset= 0;
  bool quotation_flag= false;

  ptr= format->ptr();
  end= ptr + format->length();

  if (format->length() > MAX_DATETIME_FORMAT_MODEL_LEN)
  {
    warning_message.append(STRING_WITH_LEN("datetime format string is too "
                                           "long"));
    return 1;
  }

  for (; ptr < end; ptr++, tmp_fmt++)
  {
    uint ulen;
    char cfmt, next_char;

    cfmt= my_toupper(system_charset_info, *ptr);

    /*
      Oracle datetime format support text in double quotation marks like
      'YYYY"abc"MM"xyz"DD', When this happens, store the text and quotation
      marks, and use the text as a separator in make_date_time_oracle.

      NOTE: the quotation mark is not print in return value. for example:
      select TO_CHAR(sysdate, 'YYYY"abc"MM"xyzDD"') will return 2021abc01xyz11
     */
    if (check_quotation(cfmt, &quotation_flag))
    {
      *tmp_fmt= *ptr;
      tmp_len+= 1;
      continue;
    }

    switch (cfmt) {
    case 'A':                                   // AD/A.D./AM/A.M.
      if (ptr+1 >= end)
        goto error;
      next_char= my_toupper(system_charset_info, *(ptr+1));
      if (next_char == 'D')
      {
        *tmp_fmt= FMT_AD;
        ptr+= 1;
        tmp_len+= 2;
      }
      else if (next_char == 'M')
      {
        *tmp_fmt= FMT_AM;
        ptr+= 1;
        tmp_len+= 2;
      }
      else if (next_char == '.' && ptr+3 < end && *(ptr+3) == '.')
      {
        if (my_toupper(system_charset_info, *(ptr+2)) == 'D')
        {
          *tmp_fmt= FMT_AD_DOT;
          ptr+= 3;
          tmp_len+= 4;
        }
        else if (my_toupper(system_charset_info, *(ptr+2)) == 'M')
        {
          *tmp_fmt= FMT_AM_DOT;
          ptr+= 3;
          tmp_len+= 4;
        }
        else
          goto error;
      }
      else
        goto error;
      break;
    case 'B':                                     // BC and B.C
      if (ptr+1 >= end)
        goto error;
      next_char= my_toupper(system_charset_info, *(ptr+1));
      if (next_char == 'C')
      {
        *tmp_fmt= FMT_BC;
        ptr+= 1;
        tmp_len+= 2;
      }
      else if (next_char == '.' && ptr+3 < end &&
               my_toupper(system_charset_info, *(ptr+2)) == 'C' &&
               *(ptr+3) == '.')
      {
        *tmp_fmt= FMT_BC_DOT;
        ptr+= 3;
        tmp_len+= 4;
      }
      else
        goto error;
      break;
    case 'P':                                   // PM or P.M.
      next_char= my_toupper(system_charset_info, *(ptr+1));
      if (next_char == 'M')
      {
        *tmp_fmt= FMT_PM;
        ptr+= 1;
        tmp_len+= 2;
      }
      else if (next_char == '.' &&
               my_toupper(system_charset_info, *(ptr+2)) == 'M' &&
               my_toupper(system_charset_info, *(ptr+3)) == '.')
      {
        *tmp_fmt= FMT_PM_DOT;
        ptr+= 3;
        tmp_len+= 4;
      }
      else
        goto error;
      break;
    case 'Y':                                   // Y, YY, YYY o YYYYY
      if (ptr + 1 == end || my_toupper(system_charset_info, *(ptr+1)) != 'Y')
      {
        *tmp_fmt= FMT_Y;
        tmp_len+= 1;
        break;
      }
      if (ptr + 2 == end ||
          my_toupper(system_charset_info, *(ptr+2)) != 'Y') /* YY */
      {
        *tmp_fmt= FMT_YY;
        ulen= 2;
      }
      else
      {
        if (ptr + 3 < end && my_toupper(system_charset_info, *(ptr+3)) == 'Y')
        {
          *tmp_fmt= FMT_YYYY;
          ulen= 4;
        }
        else
        {
          *tmp_fmt= FMT_YYY;
          ulen= 3;
        }
      }
      ptr+= ulen-1;
      tmp_len+= ulen;
      break;

    case 'R':                                   // RR or RRRR
      if (ptr + 1 == end || my_toupper(system_charset_info, *(ptr+1)) != 'R')
        goto error;

      if (ptr + 2 == end || my_toupper(system_charset_info, *(ptr+2)) != 'R')
      {
        *tmp_fmt= FMT_RR;
        ulen= 2;
      }
      else
      {
        if (ptr + 3 >= end || my_toupper(system_charset_info, *(ptr+3)) != 'R')
          goto error;
        *tmp_fmt= FMT_RRRR;
        ulen= 4;
      }
      ptr+= ulen-1;
      tmp_len+= ulen;
      break;
    case 'M':
    {
      char tmp1;
      if (ptr + 1 >= end)
        goto error;

      tmp1= my_toupper(system_charset_info, *(ptr+1));
      if (tmp1 == 'M')
      {
        *tmp_fmt= FMT_MM;
        tmp_len+= 2;
        ptr+= 1;
      }
      else if (tmp1 == 'I')
      {
        *tmp_fmt= FMT_MI;
        tmp_len+= 2;
        ptr+= 1;
      }
      else if (tmp1 == 'O')
      {
        if (ptr + 2 >= end)
          goto error;
        char tmp2= my_toupper(system_charset_info, *(ptr+2));
        if (tmp2 != 'N')
          goto error;

        if (ptr + 4 >= end ||
            my_toupper(system_charset_info, *(ptr+3)) != 'T' ||
            my_toupper(system_charset_info, *(ptr+4)) != 'H')
        {
          *tmp_fmt= FMT_MON;
          tmp_len+= 3;
          ptr+= 2;
        }
        else
        {
          *tmp_fmt= FMT_MONTH;
          tmp_len+= (locale->max_month_name_length *
                     my_charset_utf8mb3_bin.mbmaxlen);
          ptr+= 4;
        }
      }
      else
        goto error;
    }
    break;
    case 'D':                                   // DD, DY, or DAY
    {
      if (ptr + 1 >= end)
        goto error;
      char tmp1= my_toupper(system_charset_info, *(ptr+1));

      if (tmp1 == 'D')
      {
        *tmp_fmt= FMT_DD;
        tmp_len+= 2;
      }
      else if (tmp1 == 'Y')
      {
        *tmp_fmt= FMT_DY;
        tmp_len+= 3;
      }
      else if (tmp1 == 'A')                     // DAY
      {
        if (ptr + 2 == end || my_toupper(system_charset_info, *(ptr+2)) != 'Y')
          goto error;
        *tmp_fmt= FMT_DAY;
        tmp_len+= locale->max_day_name_length * my_charset_utf8mb3_bin.mbmaxlen;
        ptr+= 1;
      }
      else
        goto error;
      ptr+= 1;
    }
    break;
    case 'H':                                   // HH, HH12 or HH23
    {
      char tmp1, tmp2, tmp3;
      if (ptr + 1 >= end)
        goto error;
      tmp1= my_toupper(system_charset_info, *(ptr+1));

      if (tmp1 != 'H')
        goto error;

      if (ptr+3 >= end)
      {
        *tmp_fmt= FMT_HH;
        ptr+= 1;
      }
      else
      {
        tmp2= *(ptr+2);
        tmp3= *(ptr+3);

        if (tmp2 == '1' && tmp3 == '2')
        {
          *tmp_fmt= FMT_HH12;
          ptr+= 3;
        }
        else if (tmp2 == '2' && tmp3 == '4')
        {
          *tmp_fmt= FMT_HH24;
          ptr+= 3;
        }
        else
        {
          *tmp_fmt= FMT_HH;
          ptr+= 1;
        }
      }
      tmp_len+= 2;
      break;
    }
    case 'S':                                   // SS
      if (ptr + 1 == end || my_toupper(system_charset_info, *(ptr+1)) != 'S')
        goto error;

      *tmp_fmt= FMT_SS;
      tmp_len+= 2;
      ptr+= 1;
      break;
    case '|':
      /*
        If only one '|' just ignore it, else append others, for example:
        TO_CHAR('2000-11-05', 'YYYY|MM||||DD') --> 200011|||05
      */
      if (ptr + 1 == end || *(ptr+1) != '|')
      {
        tmp_fmt--;
        break;
      }
      ptr++;                                    // Skip first '|'
      do
      {
        *tmp_fmt++= *ptr++;
        tmp_len++;
      } while ((ptr < end) && *ptr == '|');
      ptr--;                                    // Fix ptr for above for loop
      tmp_fmt--;
      break;

    default:
      offset= parse_special(cfmt, ptr, end, tmp_fmt);
      if (!offset)
        goto error;
      /* ptr++ is in the for loop, so we must move ptr to offset-1 */
      ptr+= (offset-1);
      tmp_fmt+= (offset-1);
      tmp_len+= offset;
      break;
    }
  }
  *fmt_len= tmp_len;
  *tmp_fmt= 0;
  return 0;

error:
  warning_message.append(STRING_WITH_LEN("date format not recognized at "));
  warning_message.append(ptr, MY_MIN(8, end- ptr));
  return 1;
}


static inline bool append_val(int val, int size, String *str)
{
  return str->append_zerofill(val, size);
}


static bool make_date_time_oracle(const uint16 *fmt_array,
                                  const MYSQL_TIME *l_time,
                                  const MY_LOCALE *locale,
                                  String *str)
{
  bool quotation_flag= false;
  const uint16 *ptr= fmt_array;
  uint hours_i;
  uint weekday;

  str->length(0);

  while (*ptr)
  {
    if (check_quotation(*ptr, &quotation_flag))
    {
      /* don't display '"' in the result, so if it is '"', skip it */
      if (*ptr != '"')
      {
        DBUG_ASSERT(*ptr <= 255);
        str->append((char) *ptr);
      }
      ptr++;
      continue;
    }

    switch (*ptr) {

    case FMT_AM:
    case FMT_PM:
      if (l_time->hour > 11)
        str->append("PM", 2);
      else
        str->append("AM", 2);
      break;

    case FMT_AM_DOT:
    case FMT_PM_DOT:
      if (l_time->hour > 11)
        str->append(STRING_WITH_LEN("P.M."));
      else
        str->append(STRING_WITH_LEN("A.M."));
      break;

    case FMT_AD:
    case FMT_BC:
      if (l_time->year > 0)
        str->append(STRING_WITH_LEN("AD"));
      else
        str->append(STRING_WITH_LEN("BC"));
      break;

    case FMT_AD_DOT:
    case FMT_BC_DOT:
      if (l_time->year > 0)
        str->append(STRING_WITH_LEN("A.D."));
      else
        str->append(STRING_WITH_LEN("B.C."));
      break;

    case FMT_Y:
      if (append_val(l_time->year%10, 1, str))
        goto err_exit;
      break;

    case FMT_YY:
    case FMT_RR:
      if (append_val(l_time->year%100, 2, str))
        goto err_exit;
      break;

    case FMT_YYY:
      if (append_val(l_time->year%1000, 3, str))
        goto err_exit;
      break;

    case FMT_YYYY:
    case FMT_RRRR:
      if (append_val(l_time->year, 4, str))
        goto err_exit;
      break;

    case FMT_MM:
      if (append_val(l_time->month, 2, str))
        goto err_exit;
      break;

    case FMT_MON:
      {
        if (l_time->month == 0)
        {
          str->append("00", 2);
        }
        else
        {
          const char *month_name= (locale->ab_month_names->
                                   type_names[l_time->month-1]);
          size_t m_len= strlen(month_name);
          str->append(month_name, m_len, system_charset_info);
        }
      }
      break;

    case FMT_MONTH:
      {
        if (l_time->month == 0)
        {
          str->append("00", 2);
        }
        else
        {
          const char *month_name= (locale->month_names->
                                   type_names[l_time->month-1]);
          size_t month_byte_len= strlen(month_name);
          size_t month_char_len;
          str->append(month_name, month_byte_len, system_charset_info);
          month_char_len= my_numchars_mb(&my_charset_utf8mb3_general_ci,
                                         month_name, month_name +
                                         month_byte_len);
          if (str->fill(str->length() + locale->max_month_name_length -
                        month_char_len, ' '))
            goto err_exit;
        }
      }
      break;

    case FMT_DD:
      if (append_val(l_time->day, 2, str))
        goto err_exit;
      break;

    case FMT_DY:
      {
        if (l_time->day == 0)
          str->append("00", 2);
        else
        {
          weekday= calc_weekday(calc_daynr(l_time->year,l_time->month,
                                          l_time->day), 0);
          const char *day_name= locale->ab_day_names->type_names[weekday];
          str->append(day_name, strlen(day_name), system_charset_info);
        }
      }
      break;

    case FMT_DAY:
      {
        if (l_time->day == 0)
          str->append("00", 2, system_charset_info);
        else
        {
          const char *day_name;
          size_t day_byte_len, day_char_len;
          weekday=calc_weekday(calc_daynr(l_time->year,l_time->month,
                                          l_time->day), 0);
          day_name= locale->day_names->type_names[weekday];
          day_byte_len= strlen(day_name);
          str->append(day_name, day_byte_len, system_charset_info);
          day_char_len= my_numchars_mb(&my_charset_utf8mb3_general_ci,
                                       day_name, day_name + day_byte_len);
          if (str->fill(str->length() + locale->max_day_name_length -
                        day_char_len, ' '))
            goto err_exit;
        }
      }
      break;

    case FMT_HH12:
    case FMT_HH:
      hours_i= (l_time->hour%24 + 11)%12+1;
      if (append_val(hours_i, 2, str))
        goto err_exit;
      break;

    case FMT_HH24:
      if (append_val(l_time->hour, 2, str))
        goto err_exit;
      break;

    case FMT_MI:
      if (append_val(l_time->minute, 2, str))
        goto err_exit;
      break;

    case FMT_SS:
      if (append_val(l_time->second, 2, str))
        goto err_exit;
      break;

    default:
      str->append((char) *ptr);
    }

    ptr++;
  };
  return false;

err_exit:
  return true;
}


bool Item_func_tochar::fix_length_and_dec(THD *thd)
{
  CHARSET_INFO *cs= thd->variables.collation_connection;
  Item *arg1= args[1]->this_item();
  my_repertoire_t repertoire= arg1->collation.repertoire;
  StringBuffer<STRING_BUFFER_USUAL_SIZE> buffer;
  String *str;

  locale= thd->variables.lc_time_names;
  if (!thd->variables.lc_time_names->is_ascii)
    repertoire|= MY_REPERTOIRE_EXTENDED;
  collation.set(cs, arg1->collation.derivation, repertoire);

  /* first argument must be datetime or string */
  enum_field_types arg0_mysql_type= args[0]->field_type();

  max_length= 0;
  switch (arg0_mysql_type) {
  case MYSQL_TYPE_TIME:
  case MYSQL_TYPE_DATE:
  case MYSQL_TYPE_DATETIME:
  case MYSQL_TYPE_TIMESTAMP:
  case MYSQL_TYPE_VARCHAR:
  case MYSQL_TYPE_STRING:
    break;
  default:
  {
    my_printf_error(ER_STD_INVALID_ARGUMENT,
                    ER(ER_STD_INVALID_ARGUMENT),
                    MYF(0),
                    "data type of first argument must be type "
                    "date/datetime/time or string",
                    func_name());
    return TRUE;
  }
  }
  if (args[1]->basic_const_item() && (str= args[1]->val_str(&buffer)))
  {
    uint ulen;
    fixed_length= 1;
    if (parse_format_string(str, &ulen))
    {
      my_printf_error(ER_STD_INVALID_ARGUMENT,
                      ER(ER_STD_INVALID_ARGUMENT),
                      MYF(0),
                      warning_message.c_ptr(),
                      func_name());
      return TRUE;
    }
    max_length= (uint32) (ulen * collation.collation->mbmaxlen);
  }
  else
  {
    fixed_length= 0;
    max_length= (uint32) MY_MIN(arg1->max_length * 10 *
                                collation.collation->mbmaxlen,
                                MAX_BLOB_WIDTH);
  }
  set_maybe_null();
  return FALSE;
}


String *Item_func_tochar::val_str(String* str)
 {
  THD *thd= current_thd;
  StringBuffer<64> format_buffer;
  String *format;
  MYSQL_TIME l_time;
  const MY_LOCALE *lc= locale;
  date_conv_mode_t mode= TIME_CONV_NONE;
  size_t max_result_length= max_length;

  if (warning_message.length())
    goto null_date;

  if ((null_value= args[0]->get_date(thd, &l_time,
                                     Temporal::Options(mode, thd))))
    return 0;

  if (!fixed_length)
  {
    uint ulen;
    if (!(format= args[1]->val_str(&format_buffer)) || !format->length() ||
        parse_format_string(format, &ulen))
      goto null_date;
    max_result_length= ((size_t) ulen) * collation.collation->mbmaxlen;
  }

  if (str->alloc(max_result_length))
    goto null_date;

  /* Create the result string */
  str->set_charset(collation.collation);
  if (!make_date_time_oracle(fmt_array, &l_time, lc, str))
    return str;

null_date:

  if (warning_message.length())
  {
    push_warning_printf(thd,
                        Sql_condition::WARN_LEVEL_WARN,
                        ER_STD_INVALID_ARGUMENT,
                        ER_THD(thd, ER_STD_INVALID_ARGUMENT),
                        warning_message.c_ptr(),
                        func_name());
    if (!fixed_length)
      warning_message.length(0);
  }

  null_value= 1;
  return 0;
}


bool Item_func_from_unixtime::fix_length_and_dec(THD *thd)
{
  thd->used|= THD::TIME_ZONE_USED;
  tz= thd->variables.time_zone;
  Type_std_attributes::set(
    Type_temporal_attributes_not_fixed_dec(MAX_DATETIME_WIDTH,
                                           args[0]->decimals, false),
    DTCollation_numeric());
  set_maybe_null();
  return FALSE;
}


bool Item_func_from_unixtime::get_date(THD *thd, MYSQL_TIME *ltime,
				       date_mode_t fuzzydate __attribute__((unused)))
{
  bzero((char *)ltime, sizeof(*ltime));
  ltime->time_type= MYSQL_TIMESTAMP_TIME;

  VSec9 sec(thd, args[0], "unixtime", TIMESTAMP_MAX_VALUE);
  DBUG_ASSERT(sec.is_null() || sec.sec() <= TIMESTAMP_MAX_VALUE);

  if (sec.is_null() || sec.truncated() || sec.neg())
    return (null_value= 1);

  sec.round(MY_MIN(decimals, TIME_SECOND_PART_DIGITS), thd->temporal_round_mode());
  if (sec.sec() > TIMESTAMP_MAX_VALUE)
    return (null_value= true); // Went out of range after rounding

  tz->gmt_sec_to_TIME(ltime, (my_time_t) sec.sec());
  ltime->second_part= sec.usec();

  return (null_value= 0);
}


bool Item_func_convert_tz::get_date(THD *thd, MYSQL_TIME *ltime,
                                    date_mode_t fuzzydate __attribute__((unused)))
{
  my_time_t my_time_tmp;
  String str;

  if (!from_tz_cached)
  {
    from_tz= my_tz_find(thd, args[1]->val_str_ascii(&str));
    from_tz_cached= args[1]->const_item();
  }

  if (!to_tz_cached)
  {
    to_tz= my_tz_find(thd, args[2]->val_str_ascii(&str));
    to_tz_cached= args[2]->const_item();
  }

  if ((null_value= (from_tz == 0 || to_tz == 0)))
    return true;

  Datetime::Options opt(TIME_NO_ZEROS, thd);
  Datetime *dt= new(ltime) Datetime(thd, args[0], opt);
  if ((null_value= !dt->is_valid_datetime()))
    return true;

  {
    uint not_used;
    my_time_tmp= from_tz->TIME_to_gmt_sec(ltime, &not_used);
    ulong sec_part= ltime->second_part;
    /* my_time_tmp is guaranteed to be in the allowed range */
    if (my_time_tmp)
      to_tz->gmt_sec_to_TIME(ltime, my_time_tmp);
    /* we rely on the fact that no timezone conversion can change sec_part */
    ltime->second_part= sec_part;
  }

  return (null_value= 0);
}


void Item_func_convert_tz::cleanup()
{
  from_tz_cached= to_tz_cached= 0;
  Item_datetimefunc::cleanup();
}


bool Item_date_add_interval::fix_length_and_dec(THD *thd)
{
  enum_field_types arg0_field_type;

  if (!args[0]->type_handler()->is_traditional_scalar_type())
  {
    my_error(ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION, MYF(0),
             args[0]->type_handler()->name().ptr(),
             "interval", func_name());
    return TRUE;
  }
  /*
    The field type for the result of an Item_datefunc is defined as
    follows:

    - If first arg is a MYSQL_TYPE_DATETIME result is MYSQL_TYPE_DATETIME
    - If first arg is a MYSQL_TYPE_DATE and the interval type uses hours,
      minutes or seconds then type is MYSQL_TYPE_DATETIME
      otherwise it's MYSQL_TYPE_DATE
    - if first arg is a MYSQL_TYPE_TIME and the interval type isn't using
      anything larger than days, then the result is MYSQL_TYPE_TIME,
      otherwise - MYSQL_TYPE_DATETIME.
    - Otherwise the result is MYSQL_TYPE_STRING
      (This is because you can't know if the string contains a DATE,
      MYSQL_TIME or DATETIME argument)
  */
  arg0_field_type= args[0]->field_type();

  if (arg0_field_type == MYSQL_TYPE_DATETIME ||
      arg0_field_type == MYSQL_TYPE_TIMESTAMP)
  {
    set_func_handler(&func_handler_date_add_interval_datetime);
  }
  else if (arg0_field_type == MYSQL_TYPE_DATE)
  {
    if (int_type <= INTERVAL_DAY || int_type == INTERVAL_YEAR_MONTH)
      set_func_handler(&func_handler_date_add_interval_date);
    else
      set_func_handler(&func_handler_date_add_interval_datetime);
  }
  else if (arg0_field_type == MYSQL_TYPE_TIME)
  {
    if (int_type >= INTERVAL_DAY && int_type != INTERVAL_YEAR_MONTH)
      set_func_handler(&func_handler_date_add_interval_time);
    else
      set_func_handler(&func_handler_date_add_interval_datetime_arg0_time);
  }
  else
  {
    set_func_handler(&func_handler_date_add_interval_string);
  }
  set_maybe_null();
  return m_func_handler->fix_length_and_dec(this);
}


bool Func_handler_date_add_interval_datetime_arg0_time::
       get_date(THD *thd, Item_handled_func *item,
                MYSQL_TIME *to, date_mode_t fuzzy) const
{
  // time_expr + INTERVAL {YEAR|QUARTER|MONTH|WEEK|YEAR_MONTH}
  push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
                      ER_DATETIME_FUNCTION_OVERFLOW,
                      ER_THD(thd, ER_DATETIME_FUNCTION_OVERFLOW), "time");
  return (item->null_value= true);
}


bool Item_date_add_interval::eq(const Item *item, const Eq_config &config) const
{
  if (!Item_func::eq(item, config))
    return 0;
  Item_date_add_interval *other= (Item_date_add_interval*) item;
  return ((int_type == other->int_type) &&
          (date_sub_interval == other->date_sub_interval));
}

/*
   'interval_names' reflects the order of the enumeration interval_type.
   See item_timefunc.h
 */

static const char *interval_names[]=
{
  "year", "quarter", "month", "week", "day",  
  "hour", "minute", "second", "microsecond",
  "year_month", "day_hour", "day_minute", 
  "day_second", "hour_minute", "hour_second",
  "minute_second", "day_microsecond",
  "hour_microsecond", "minute_microsecond",
  "second_microsecond"
};

void Item_date_add_interval::print(String *str, enum_query_type query_type)
{
  args[0]->print_parenthesised(str, query_type, INTERVAL_PRECEDENCE);
  static LEX_CSTRING minus_interval= { STRING_WITH_LEN(" - interval ") };
  static LEX_CSTRING plus_interval=  { STRING_WITH_LEN(" + interval ") };
  LEX_CSTRING *tmp= date_sub_interval ? &minus_interval : &plus_interval;
  str->append(tmp);
  args[1]->print(str, query_type);
  str->append(' ');
  str->append(interval_names[int_type], strlen(interval_names[int_type]));
}

void Item_extract::print(String *str, enum_query_type query_type)
{
  str->append(STRING_WITH_LEN("extract("));
  str->append(interval_names[int_type], strlen(interval_names[int_type]));
  str->append(STRING_WITH_LEN(" from "));
  args[0]->print(str, query_type);
  str->append(')');
}


bool Item_extract::check_arguments() const
{
  if (!args[0]->type_handler()->can_return_extract_source(int_type))
  {
    char tmp[64];
    my_snprintf(tmp, sizeof(tmp), "extract(%s)", interval_names[int_type]);
    my_error(ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION, MYF(0),
             args[0]->type_handler()->name().ptr(), tmp);
    return true;
  }
  return false;
}


bool Item_extract::fix_length_and_dec(THD *thd)
{
  set_maybe_null(); // If wrong date
  uint32 daylen= args[0]->cmp_type() == TIME_RESULT ? 2 :
                 TIME_MAX_INTERVAL_DAY_CHAR_LENGTH;
  switch (int_type) {
  case INTERVAL_YEAR:             set_date_length(4); break; // YYYY
  case INTERVAL_YEAR_MONTH:       set_date_length(6); break; // YYYYMM
  case INTERVAL_QUARTER:          set_date_length(1); break; // 1..4
  case INTERVAL_MONTH:            set_date_length(2); break; // MM
  case INTERVAL_WEEK:             set_date_length(2); break; // 0..52
  case INTERVAL_DAY:              set_day_length(daylen); break; // DD
  case INTERVAL_DAY_HOUR:         set_day_length(daylen+2); break; // DDhh
  case INTERVAL_DAY_MINUTE:       set_day_length(daylen+4); break; // DDhhmm
  case INTERVAL_DAY_SECOND:       set_day_length(daylen+6); break; // DDhhmmss
  case INTERVAL_HOUR:             set_time_length(2); break; // hh
  case INTERVAL_HOUR_MINUTE:      set_time_length(4); break; // hhmm
  case INTERVAL_HOUR_SECOND:      set_time_length(6); break; // hhmmss
  case INTERVAL_MINUTE:           set_time_length(2); break; // mm
  case INTERVAL_MINUTE_SECOND:    set_time_length(4); break; // mmss
  case INTERVAL_SECOND:           set_time_length(2); break; // ss
  case INTERVAL_MICROSECOND:      set_time_length(6); break; // ffffff
  case INTERVAL_DAY_MICROSECOND:  set_time_length(daylen+12); break; // DDhhmmssffffff
  case INTERVAL_HOUR_MICROSECOND: set_time_length(12); break; // hhmmssffffff
  case INTERVAL_MINUTE_MICROSECOND: set_time_length(10); break; // mmssffffff
  case INTERVAL_SECOND_MICROSECOND: set_time_length(8); break; // ssffffff
  case INTERVAL_LAST: DBUG_ASSERT(0); break; /* purecov: deadcode */
  }
  return FALSE;
}


uint Extract_source::week(THD *thd) const
{
  DBUG_ASSERT(is_valid_extract_source());
  uint year;
  ulong week_format= current_thd->variables.default_week_format;
  return calc_week(this, week_mode(week_format), &year);
}


longlong Item_extract::val_int()
{
  DBUG_ASSERT(fixed());
  THD *thd= current_thd;
  Extract_source dt(thd, args[0], m_date_mode);
  if ((null_value= !dt.is_valid_extract_source()))
    return 0;
  switch (int_type) {
  case INTERVAL_YEAR:                return dt.year();
  case INTERVAL_YEAR_MONTH:          return dt.year_month();
  case INTERVAL_QUARTER:             return dt.quarter();
  case INTERVAL_MONTH:               return dt.month();
  case INTERVAL_WEEK:                return dt.week(thd);
  case INTERVAL_DAY:                 return dt.day();
  case INTERVAL_DAY_HOUR:            return dt.day_hour();
  case INTERVAL_DAY_MINUTE:          return dt.day_minute();
  case INTERVAL_DAY_SECOND:          return dt.day_second();
  case INTERVAL_HOUR:                return dt.hour();
  case INTERVAL_HOUR_MINUTE:         return dt.hour_minute();
  case INTERVAL_HOUR_SECOND:         return dt.hour_second();
  case INTERVAL_MINUTE:              return dt.minute();
  case INTERVAL_MINUTE_SECOND:       return dt.minute_second();
  case INTERVAL_SECOND:              return dt.second();
  case INTERVAL_MICROSECOND:         return dt.microsecond();
  case INTERVAL_DAY_MICROSECOND:     return dt.day_microsecond();
  case INTERVAL_HOUR_MICROSECOND:    return dt.hour_microsecond();
  case INTERVAL_MINUTE_MICROSECOND:  return dt.minute_microsecond();
  case INTERVAL_SECOND_MICROSECOND:  return dt.second_microsecond();
  case INTERVAL_LAST: DBUG_ASSERT(0); break;  /* purecov: deadcode */
  }
  return 0;                                        // Impossible
}

bool Item_extract::eq(const Item *item, const Eq_config &config) const
{
  if (this == item)
    return 1;
  if (item->type() != FUNC_ITEM ||
      functype() != ((Item_func*)item)->functype())
    return 0;

  Item_extract* ie= (Item_extract*)item;
  if (ie->int_type != int_type)
    return 0;

  if (!args[0]->eq(ie->args[0], config))
      return 0;
  return 1;
}


bool Item_char_typecast::eq(const Item *item, const Eq_config &config) const
{
  if (this == item)
    return 1;
  if (item->type() != FUNC_ITEM ||
      functype() != ((Item_func*)item)->functype())
    return 0;

  Item_char_typecast *cast= (Item_char_typecast*)item;
  if (cast_length != cast->cast_length ||
      cast_cs     != cast->cast_cs)
    return 0;

  if (!args[0]->eq(cast->args[0], config))
      return 0;
  return 1;
}

void Item_func::print_cast_temporal(String *str, enum_query_type query_type)
{
  char buf[32];
  str->append(STRING_WITH_LEN("cast("));
  args[0]->print(str, query_type);
  str->append(STRING_WITH_LEN(" as "));
  const Name name= type_handler()->name();
  str->append(name.ptr(), name.length());
  if (decimals && decimals != NOT_FIXED_DEC)
  {
    str->append('(');
    size_t length= (size_t) (longlong10_to_str(decimals, buf, -10) - buf);
    str->append(buf, length);
    str->append(')');
  }
  str->append(')');
}


void Item_char_typecast::print(String *str, enum_query_type query_type)
{
  str->append(STRING_WITH_LEN("cast("));
  args[0]->print(str, query_type);
  str->append(STRING_WITH_LEN(" as char"));
  if (cast_length != ~0U)
  {
    char buf[20];
    size_t length= (size_t) (longlong10_to_str(cast_length, buf, 10) - buf);
    str->append('(');
    str->append(buf, length);
    str->append(')');
  }
  if (cast_cs)
  {
    str->append(STRING_WITH_LEN(" charset "));
    str->append(cast_cs->cs_name);
    /*
      Print the "binary" keyword in cases like:
        CAST('str' AS CHAR CHARACTER SET latin1 BINARY)
    */
    if ((cast_cs->state & MY_CS_BINSORT) &&
        Charset(cast_cs).can_have_collate_clause())
      str->append(STRING_WITH_LEN(" binary"));
  }
  str->append(')');
}


void Item_char_typecast::check_truncation_with_warn(String *src, size_t dstlen)
{
  if (dstlen < src->length())
  {
    THD *thd= current_thd;
    char char_type[40];
    ErrConvString err(src);
    bool save_abort_on_warning= thd->abort_on_warning;
    thd->abort_on_warning&= !m_suppress_warning_to_error_escalation;
    my_snprintf(char_type, sizeof(char_type), "%s(%lu)",
                cast_cs == &my_charset_bin ? "BINARY" : "CHAR",
                (ulong) cast_length);
    push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
                        ER_TRUNCATED_WRONG_VALUE,
                        ER_THD(thd, ER_TRUNCATED_WRONG_VALUE), char_type,
                        err.ptr());
    thd->abort_on_warning= save_abort_on_warning;
  }
}


String *Item_char_typecast::reuse(String *src, size_t length)
{
  DBUG_ASSERT(length <= src->length());
  check_truncation_with_warn(src, length);
  tmp_value.set(src->ptr(), length, cast_cs);
  return &tmp_value;
}


/*
  Make a copy, to handle conversion or fix bad bytes.
*/
String *Item_char_typecast::copy(String *str, CHARSET_INFO *strcs)
{
  String_copier_for_item copier(current_thd);
  if (copier.copy_with_warn(cast_cs, &tmp_value, strcs,
                            str->ptr(), str->length(), cast_length))
  {
    null_value= 1; // EOM
    return 0;
  }
  check_truncation_with_warn(str, (uint)(copier.source_end_pos() - str->ptr()));
  return &tmp_value;
}


uint Item_char_typecast::adjusted_length_with_warn(uint length)
{
  if (length <= current_thd->variables.max_allowed_packet)
    return length;

  THD *thd= current_thd;
  push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
                      ER_WARN_ALLOWED_PACKET_OVERFLOWED,
                      ER_THD(thd, ER_WARN_ALLOWED_PACKET_OVERFLOWED),
                      cast_cs == &my_charset_bin ?
                      "cast_as_binary" : func_name(),
                      thd->variables.max_allowed_packet);
  return thd->variables.max_allowed_packet;
}


String *Item_char_typecast::val_str_generic(String *str)
{
  DBUG_ASSERT(fixed());
  String *res;

  if (has_explicit_length())
    cast_length= adjusted_length_with_warn(cast_length);

  if (!(res= args[0]->val_str(str)))
  {
    null_value= 1;
    return 0;
  }

  if (cast_cs == &my_charset_bin &&
      has_explicit_length() &&
      cast_length > res->length())
  {
    // Special case: pad binary value with trailing 0x00
    DBUG_ASSERT(cast_length <= current_thd->variables.max_allowed_packet);
    if (res->alloced_length() < cast_length)
    {
      str_value.alloc(cast_length);
      str_value.copy(*res);
      res= &str_value;
    }
    bzero((char*) res->ptr() + res->length(), cast_length - res->length());
    res->length(cast_length);
    res->set_charset(&my_charset_bin);
  }
  else
  {
    /*
      from_cs is 0 in the case where the result set may vary between calls,
      for example with dynamic columns.
    */
    CHARSET_INFO *cs= from_cs ? from_cs : res->charset();
    if (!charset_conversion)
    {
      // Try to reuse the original string (if well formed).
      Well_formed_prefix prefix(cs, res->ptr(), res->end(), cast_length);
      if (!prefix.well_formed_error_pos())
        res= reuse(res, prefix.length());
      goto end;
    }
    // Character set conversion, or bad bytes were found.
    if (!(res= copy(res, cs)))
      return 0;
  }

end:
  return ((null_value= (res->length() >
                        adjusted_length_with_warn(res->length())))) ? 0 : res;
}


String *Item_char_typecast::val_str_binary_from_native(String *str)
{
  DBUG_ASSERT(fixed());
  DBUG_ASSERT(cast_cs == &my_charset_bin);
  NativeBuffer<STRING_BUFFER_USUAL_SIZE> native;

  if (args[0]->val_native(current_thd, &native))
  {
    null_value= 1;
    return 0;
  }

  if (has_explicit_length())
  {
    cast_length= adjusted_length_with_warn(cast_length);
    if (cast_length > native.length())
    {
      // add trailing 0x00s
      DBUG_ASSERT(cast_length <= current_thd->variables.max_allowed_packet);
      str->alloc(cast_length);
      str->copy(native.ptr(), native.length(), &my_charset_bin);
      bzero((char*) str->end(), cast_length - str->length());
      str->length(cast_length);
    }
    else
      str->copy(native.ptr(), cast_length, &my_charset_bin);
  }
  else
    str->copy(native.ptr(), native.length(), &my_charset_bin);

  return ((null_value= (str->length() >
                        adjusted_length_with_warn(str->length())))) ? 0 : str;
}


class Item_char_typecast_func_handler: public Item_handled_func::Handler_str
{
public:
  const Type_handler *return_type_handler(const Item_handled_func *item) const override
  {
    return Type_handler::string_type_handler(item->max_length);
  }
  const Type_handler *
    type_handler_for_create_select(const Item_handled_func *item) const override
  {
    return return_type_handler(item)->type_handler_for_tmp_table(item);
  }

  bool fix_length_and_dec(Item_handled_func *item) const override
  {
    return false;
  }
  String *val_str(Item_handled_func *item, String *to) const override
  {
    DBUG_ASSERT(dynamic_cast<const Item_char_typecast*>(item));
    return static_cast<Item_char_typecast*>(item)->val_str_generic(to);
  }
};


static Item_char_typecast_func_handler item_char_typecast_func_handler;


void Item_char_typecast::fix_length_and_dec_numeric()
{
  fix_length_and_dec_internal(from_cs= cast_cs->mbminlen == 1 ?
                                       cast_cs :
                                       &my_charset_latin1);
  set_func_handler(&item_char_typecast_func_handler);
}


void Item_char_typecast::fix_length_and_dec_generic()
{
  fix_length_and_dec_internal(from_cs= args[0]->dynamic_result() ?
                                       0 :
                                       args[0]->collation.collation);
  set_func_handler(&item_char_typecast_func_handler);
}


void Item_char_typecast::fix_length_and_dec_str()
{
  fix_length_and_dec_generic();
  m_suppress_warning_to_error_escalation= true;
  set_func_handler(&item_char_typecast_func_handler);
}


void
Item_char_typecast::fix_length_and_dec_native_to_binary(uint32 octet_length)
{
  collation.set(&my_charset_bin, DERIVATION_IMPLICIT);
  max_length= has_explicit_length() ? (uint32) cast_length : octet_length;
  if (current_thd->is_strict_mode())
    set_maybe_null();
}


void Item_char_typecast::fix_length_and_dec_internal(CHARSET_INFO *from_cs)
{
  uint32 char_length;
  /* 
     We always force character set conversion if cast_cs
     is a multi-byte character set. It guarantees that the
     result of CAST is a well-formed string.
     For single-byte character sets we allow just to copy
     from the argument. A single-byte character sets string
     is always well-formed. 
     
     There is a special trick to convert form a number to ucs2.
     As numbers have my_charset_bin as their character set,
     it wouldn't do conversion to ucs2 without an additional action.
     To force conversion, we should pretend to be non-binary.
     Let's choose from_cs this way:
     - If the argument in a number and cast_cs is ucs2 (i.e. mbminlen > 1),
       then from_cs is set to latin1, to perform latin1 -> ucs2 conversion.
     - If the argument is a number and cast_cs is ASCII-compatible
       (i.e. mbminlen == 1), then from_cs is set to cast_cs,
       which allows just to take over the args[0]->val_str() result
       and thus avoid unnecessary character set conversion.
     - If the argument is not a number, then from_cs is set to
       the argument's charset.
     - If argument has a dynamic collation (can change from call to call)
       we set from_cs to 0 as a marker that we have to take the collation
       from the result string.

       Note (TODO): we could use repertoire technique here.
  */
  charset_conversion= !from_cs || (cast_cs->mbmaxlen > 1) ||
                      (!my_charset_same(from_cs, cast_cs) &&
                       from_cs != &my_charset_bin &&
                       cast_cs != &my_charset_bin);
  collation.set(cast_cs, DERIVATION_IMPLICIT);
  char_length= ((cast_length != ~0U) ? cast_length :
                args[0]->max_length /
                (cast_cs == &my_charset_bin ? 1 :
                 args[0]->collation.collation->mbmaxlen));
  max_length= char_length * cast_cs->mbmaxlen;
  // Add NULL-ability in strict mode. See Item_str_func::fix_fields()
  if (current_thd->is_strict_mode())
    set_maybe_null();
}


bool Item_time_typecast::get_date(THD *thd, MYSQL_TIME *to, date_mode_t mode)
{
  Time *tm= new(to) Time(thd, args[0], Time::Options_for_cast(mode, thd),
                         MY_MIN(decimals, TIME_SECOND_PART_DIGITS));
  return (null_value= !tm->is_valid_time());
}


Sql_mode_dependency Item_time_typecast::value_depends_on_sql_mode() const
{
  return Item_timefunc::value_depends_on_sql_mode() |
         Sql_mode_dependency(decimals < args[0]->decimals ?
                             MODE_TIME_ROUND_FRACTIONAL : 0, 0);
}


bool Item_date_typecast::get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate)
{
  date_mode_t tmp= (fuzzydate | sql_mode_for_dates(thd))
                    & ~TIME_TIME_ONLY & ~TIME_INTERVAL_DAY;
  // Force truncation
  Date *d= new(ltime) Date(thd, args[0], Date::Options(date_conv_mode_t(tmp)));
  return (null_value= !d->is_valid_date());
}


bool Item_datetime_typecast::get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate)
{
  date_mode_t tmp= (fuzzydate | sql_mode_for_dates(thd))
                    & ~TIME_TIME_ONLY & ~TIME_INTERVAL_DAY;
  // Force rounding if the current sql_mode says so
  Datetime::Options opt(date_conv_mode_t(tmp), thd);
  Datetime *dt= new(ltime) Datetime(thd, args[0], opt,
                                    MY_MIN(decimals, TIME_SECOND_PART_DIGITS));
  return (null_value= !dt->is_valid_datetime());
}


Sql_mode_dependency Item_datetime_typecast::value_depends_on_sql_mode() const
{
  return Item_datetimefunc::value_depends_on_sql_mode() |
         Sql_mode_dependency(decimals < args[0]->decimals ?
                             MODE_TIME_ROUND_FRACTIONAL : 0, 0);
}


/**
  MAKEDATE(a,b) is a date function that creates a date value 
  from a year and day value.

  NOTES:
    As arguments are integers, we can't know if the year is a 2 digit
    or 4 digit year.  In this case we treat all years < 100 as 2 digit
    years. Ie, this is not safe for dates between 0000-01-01 and
    0099-12-31
*/

bool Item_func_makedate::get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate)
{
  DBUG_ASSERT(fixed());
  long year, days, daynr=  (long) args[1]->val_int();

  VYear vyear(args[0]);
  if (vyear.is_null() || args[1]->null_value || vyear.truncated() || daynr <= 0)
    goto err;

  if ((year= (long) vyear.year()) < 100)
    year= year_2000_handling(year);
  days= calc_daynr(year,1,1) + daynr - 1;
  if (get_date_from_daynr(days, &ltime->year, &ltime->month, &ltime->day))
    goto err;
  ltime->time_type= MYSQL_TIMESTAMP_DATE;
  ltime->neg= 0;
  ltime->hour= ltime->minute= ltime->second= ltime->second_part= 0;
  return (null_value= 0);

err:
  return (null_value= 1);
}


bool Item_func_add_time::fix_length_and_dec(THD *thd)
{
  enum_field_types arg0_field_type;

  if (!args[0]->type_handler()->is_traditional_scalar_type() ||
      !args[1]->type_handler()->is_traditional_scalar_type())
  {
    my_error(ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION, MYF(0),
             args[0]->type_handler()->name().ptr(),
             args[1]->type_handler()->name().ptr(), func_name());
    return TRUE;
  }
  /*
    The field type for the result of an Item_func_add_time function is defined
    as follows:

    - If first arg is a MYSQL_TYPE_DATETIME or MYSQL_TYPE_TIMESTAMP 
      result is MYSQL_TYPE_DATETIME
    - If first arg is a MYSQL_TYPE_TIME result is MYSQL_TYPE_TIME
    - Otherwise the result is MYSQL_TYPE_STRING
  */

  arg0_field_type= args[0]->field_type();
  if (arg0_field_type == MYSQL_TYPE_DATE ||
      arg0_field_type == MYSQL_TYPE_DATETIME ||
      arg0_field_type == MYSQL_TYPE_TIMESTAMP)
  {
    set_func_handler(sign > 0 ? &func_handler_add_time_datetime_add :
                                &func_handler_add_time_datetime_sub);
  }
  else if (arg0_field_type == MYSQL_TYPE_TIME)
  {
    set_func_handler(sign > 0 ? &func_handler_add_time_time_add :
                                &func_handler_add_time_time_sub);
  }
  else
  {
    set_func_handler(sign > 0 ? &func_handler_add_time_string_add :
                                &func_handler_add_time_string_sub);
  }

  set_maybe_null();
  return m_func_handler->fix_length_and_dec(this);
}


/**
  TIMEDIFF(t,s) is a time function that calculates the 
  time value between a start and end time.

  t and s: time_or_datetime_expression
  Result: Time value
*/

bool Item_func_timediff::get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate)
{
  DBUG_ASSERT(fixed());
  int l_sign= 1;
  MYSQL_TIME l_time1,l_time2,l_time3;

  /* the following may be true in, for example, date_add(timediff(...), ... */
  if (fuzzydate & TIME_NO_ZERO_IN_DATE)
    return (null_value= 1);

  if (args[0]->get_time(thd, &l_time1) ||
      args[1]->get_time(thd, &l_time2) ||
      l_time1.time_type != l_time2.time_type)
    return (null_value= 1);

  if (l_time1.neg != l_time2.neg)
    l_sign= -l_sign;

  if (l_time1.time_type == MYSQL_TIMESTAMP_TIME)
  {
    /*
      In case of TIME-alike arguments:
        TIMEDIFF('38:59:59', '839:00:00')
      let's truncate extra fractional seconds that might appear if the argument
      values were out of the supported TIME range. For example, args[n]->get_time()
      for the string literal '839:00:00' returns TIME'838:59:59.999999'.
      The fractional part must be truncated according to this->decimals,
      to avoid returning more fractional seconds than it was detected
      during this->fix_length_and_dec().
      Note, the thd rounding mode should not be important here, as we're removing
      redundant digits from the maximum possible value: '838:59:59.999999'.
    */
    my_time_trunc(&l_time1, decimals);
    my_time_trunc(&l_time2, decimals);
  }

  if (calc_time_diff(&l_time1, &l_time2, l_sign, &l_time3, fuzzydate))
    return (null_value= 1);

  *ltime= l_time3;
  return (null_value= adjust_time_range_with_warn(thd, ltime, decimals));
}


/**
  MAKETIME(h,m,s) is a time function that calculates a time value 
  from the total number of hours, minutes, and seconds.
  Result: Time value
*/

bool Item_func_maketime::get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate)
{
  DBUG_ASSERT(fixed());
  Longlong_hybrid hour(args[0]->val_int(), args[0]->unsigned_flag);
  longlong minute= args[1]->val_int();
  VSec9 sec(thd, args[2], "seconds", 59);

  DBUG_ASSERT(sec.is_null() || sec.sec() <= 59);
  if (args[0]->null_value || args[1]->null_value || sec.is_null() ||
       minute < 0 || minute > 59 || sec.neg() || sec.truncated())
    return (null_value= 1);

  int warn;
  new(ltime) Time(&warn, hour.neg(), hour.abs(), (uint) minute,
                  sec.to_const_sec9(), thd->temporal_round_mode(), decimals);
  if (warn)
  {
    // use check_time_range() to set ltime to the max value depending on dec
    int unused;
    ltime->hour= TIME_MAX_HOUR + 1;
    check_time_range(ltime, decimals, &unused);
    char buf[28];
    char *ptr= longlong10_to_str(hour.value(), buf, hour.is_unsigned() ? 10 : -10);
    int len = (int)(ptr - buf) + sprintf(ptr, ":%02u:%02u",
                                         (uint) minute, (uint) sec.sec());
    ErrConvString err(buf, len, &my_charset_bin);
    thd->push_warning_truncated_wrong_value("time", err.ptr());
  }

  return (null_value= 0);
}


/**
  MICROSECOND(a) is a function ( extraction) that extracts the microseconds
  from a.

  a: Datetime or time value
  Result: int value
*/

longlong Item_func_microsecond::val_int()
{
  DBUG_ASSERT(fixed());
  THD *thd= current_thd;
  Time tm(thd, args[0], Time::Options_for_cast(thd));
  return ((null_value= !tm.is_valid_time())) ?
         0 : tm.get_mysql_time()->second_part;
}


longlong Item_func_timestamp_diff::val_int()
{
  MYSQL_TIME ltime1, ltime2;
  ulonglong seconds;
  ulong microseconds;
  long months= 0;
  int neg= 1;
  THD *thd= current_thd;
  Datetime::Options opt(TIME_NO_ZEROS, thd);

  null_value= 0;

  if (Datetime(thd, args[0], opt).copy_to_mysql_time(&ltime1) ||
      Datetime(thd, args[1], opt).copy_to_mysql_time(&ltime2))
    goto null_date;

  if (calc_time_diff(&ltime2,&ltime1, 1,
		     &seconds, &microseconds))
    neg= -1;

  if (int_type == INTERVAL_YEAR ||
      int_type == INTERVAL_QUARTER ||
      int_type == INTERVAL_MONTH)
  {
    uint year_beg, year_end, month_beg, month_end, day_beg, day_end;
    uint years= 0;
    uint second_beg, second_end, microsecond_beg, microsecond_end;

    if (neg == -1)
    {
      year_beg= ltime2.year;
      year_end= ltime1.year;
      month_beg= ltime2.month;
      month_end= ltime1.month;
      day_beg= ltime2.day;
      day_end= ltime1.day;
      second_beg= ltime2.hour * 3600 + ltime2.minute * 60 + ltime2.second;
      second_end= ltime1.hour * 3600 + ltime1.minute * 60 + ltime1.second;
      microsecond_beg= ltime2.second_part;
      microsecond_end= ltime1.second_part;
    }
    else
    {
      year_beg= ltime1.year;
      year_end= ltime2.year;
      month_beg= ltime1.month;
      month_end= ltime2.month;
      day_beg= ltime1.day;
      day_end= ltime2.day;
      second_beg= ltime1.hour * 3600 + ltime1.minute * 60 + ltime1.second;
      second_end= ltime2.hour * 3600 + ltime2.minute * 60 + ltime2.second;
      microsecond_beg= ltime1.second_part;
      microsecond_end= ltime2.second_part;
    }

    /* calc years */
    years= year_end - year_beg;
    if (month_end < month_beg || (month_end == month_beg && day_end < day_beg))
      years-= 1;

    /* calc months */
    months= 12*years;
    if (month_end < month_beg || (month_end == month_beg && day_end < day_beg))
      months+= 12 - (month_beg - month_end);
    else
      months+= (month_end - month_beg);

    if (day_end < day_beg)
      months-= 1;
    else if ((day_end == day_beg) &&
	     ((second_end < second_beg) ||
	      (second_end == second_beg && microsecond_end < microsecond_beg)))
      months-= 1;
  }

  switch (int_type) {
  case INTERVAL_YEAR:
    return months/12*neg;
  case INTERVAL_QUARTER:
    return months/3*neg;
  case INTERVAL_MONTH:
    return months*neg;
  case INTERVAL_WEEK:          
    return ((longlong) (seconds / SECONDS_IN_24H / 7L)) * neg;
  case INTERVAL_DAY:		
    return ((longlong) (seconds / SECONDS_IN_24H)) * neg;
  case INTERVAL_HOUR:		
    return ((longlong) (seconds / 3600L)) * neg;
  case INTERVAL_MINUTE:		
    return ((longlong) (seconds / 60L)) * neg;
  case INTERVAL_SECOND:		
    return ((longlong) seconds) * neg;
  case INTERVAL_MICROSECOND:
    /*
      In MySQL difference between any two valid datetime values
      in microseconds fits into longlong.
    */
    return ((longlong) ((ulonglong) seconds * 1000000L + microseconds)) * neg;
  default:
    break;
  }

null_date:
  null_value=1;
  return 0;
}


void Item_func_timestamp_diff::print(String *str, enum_query_type query_type)
{
  str->append(func_name_cstring());
  str->append('(');

  switch (int_type) {
  case INTERVAL_YEAR:
    str->append(STRING_WITH_LEN("YEAR"));
    break;
  case INTERVAL_QUARTER:
    str->append(STRING_WITH_LEN("QUARTER"));
    break;
  case INTERVAL_MONTH:
    str->append(STRING_WITH_LEN("MONTH"));
    break;
  case INTERVAL_WEEK:          
    str->append(STRING_WITH_LEN("WEEK"));
    break;
  case INTERVAL_DAY:		
    str->append(STRING_WITH_LEN("DAY"));
    break;
  case INTERVAL_HOUR:
    str->append(STRING_WITH_LEN("HOUR"));
    break;
  case INTERVAL_MINUTE:		
    str->append(STRING_WITH_LEN("MINUTE"));
    break;
  case INTERVAL_SECOND:
    str->append(STRING_WITH_LEN("SECOND"));
    break;		
  case INTERVAL_MICROSECOND:
    str->append(STRING_WITH_LEN("MICROSECOND"));
    break;
  default:
    break;
  }

  for (uint i=0 ; i < 2 ; i++)
  {
    str->append(',');
    args[i]->print(str, query_type);
  }
  str->append(')');
}


String *Item_func_get_format::val_str_ascii(String *str)
{
  DBUG_ASSERT(fixed());
  const char *format_name;
  KNOWN_DATE_TIME_FORMAT *format;
  String *val= args[0]->val_str_ascii(str);
  ulong val_len;

  if ((null_value= args[0]->null_value))
    return 0;    

  val_len= val->length();
  for (format= &known_date_time_formats[0];
       (format_name= format->format_name);
       format++)
  {
    uint format_name_len;
    format_name_len= (uint) strlen(format_name);
    if (val_len == format_name_len &&
	!my_charset_latin1.strnncoll(val->ptr(), val_len, 
		                     format_name, val_len))
    {
      const char *format_str= get_date_time_format_str(format, type);
      str->set(format_str, (uint) strlen(format_str), &my_charset_numeric);
      return str;
    }
  }

  null_value= 1;
  return 0;
}


void Item_func_get_format::print(String *str, enum_query_type query_type)
{
  str->append(func_name_cstring());
  str->append('(');

  switch (type) {
  case MYSQL_TIMESTAMP_DATE:
    str->append(STRING_WITH_LEN("DATE, "));
    break;
  case MYSQL_TIMESTAMP_DATETIME:
    str->append(STRING_WITH_LEN("DATETIME, "));
    break;
  case MYSQL_TIMESTAMP_TIME:
    str->append(STRING_WITH_LEN("TIME, "));
    break;
  default:
    DBUG_ASSERT(0);
  }
  args[0]->print(str, query_type);
  str->append(')');
}


/**
  Get type of datetime value (DATE/TIME/...) which will be produced
  according to format string.

  @param format   format string
  @param length   length of format string

  @note
    We don't process day format's characters('D', 'd', 'e') because day
    may be a member of all date/time types.

  @note
    Format specifiers supported by this function should be in sync with
    specifiers supported by extract_date_time() function.

  @return
    A function handler corresponding the given format
*/

static const Item_handled_func::Handler *
get_date_time_result_type(const char *format, uint length)
{
  const char *time_part_frms= "HISThiklrs";
  const char *date_part_frms= "MVUXYWabcjmvuxyw";
  bool date_part_used= 0, time_part_used= 0, frac_second_used= 0;
  
  const char *val= format;
  const char *end= format + length;

  for (; val != end; val++)
  {
    if (*val == '%' && val+1 != end)
    {
      val++;
      if (*val == 'f')
        frac_second_used= time_part_used= 1;
      else if (!time_part_used && strchr(time_part_frms, *val))
	time_part_used= 1;
      else if (!date_part_used && strchr(date_part_frms, *val))
	date_part_used= 1;
      if (date_part_used && frac_second_used)
      {
        /*
          frac_second_used implies time_part_used, and thus we already
          have all types of date-time components and can end our search.
        */
        return &func_handler_str_to_date_datetime_usec;
      }
    }
  }

  /* We don't have all three types of date-time components */
  if (frac_second_used)
    return &func_handler_str_to_date_time_usec;
  if (time_part_used)
  {
    if (date_part_used)
      return &func_handler_str_to_date_datetime_sec;
    return &func_handler_str_to_date_time_sec;
  }
  return &func_handler_str_to_date_date;
}


bool Item_func_str_to_date::fix_length_and_dec(THD *thd)
{
  if (!args[0]->type_handler()->is_traditional_scalar_type() ||
      !args[1]->type_handler()->is_traditional_scalar_type())
  {
    my_error(ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION, MYF(0),
             args[0]->type_handler()->name().ptr(),
             args[1]->type_handler()->name().ptr(), func_name());
    return TRUE;
  }
  if (agg_arg_charsets(collation, args, 2, MY_COLL_ALLOW_CONV, 1))
    return TRUE;
  if (collation.collation->mbminlen > 1)
    internal_charset= &my_charset_utf8mb4_general_ci;

  set_maybe_null();
  set_func_handler(&func_handler_str_to_date_datetime_usec);

  if ((const_item= args[1]->const_item()))
  {
    StringBuffer<64> format_str;
    String *format= args[1]->val_str(&format_str, &format_converter,
                                     internal_charset);
    if (!args[1]->null_value)
      set_func_handler(get_date_time_result_type(format->ptr(), format->length()));
  }
  return m_func_handler->fix_length_and_dec(this);
}


bool Item_func_str_to_date::get_date_common(THD *thd, MYSQL_TIME *ltime,
                                            date_mode_t fuzzydate,
                                            timestamp_type tstype)
{
  DATE_TIME_FORMAT date_time_format;
  StringBuffer<64> val_string, format_str;
  String *val, *format;

  val=    args[0]->val_str(&val_string, &subject_converter, internal_charset);
  format= args[1]->val_str(&format_str, &format_converter, internal_charset);
  if (args[0]->null_value || args[1]->null_value)
    return (null_value=1);

  date_time_format.format.str=    (char*) format->ptr();
  date_time_format.format.length= format->length();
  if (extract_date_time(thd, &date_time_format, val->ptr(), val->length(),
			ltime, tstype, 0, "datetime",
                        date_conv_mode_t(fuzzydate) |
                        sql_mode_for_dates(thd)))
    return (null_value=1);
  return (null_value= 0);
}


bool Item_func_last_day::get_date(THD *thd, MYSQL_TIME *ltime, date_mode_t fuzzydate)
{
  Datetime::Options opt(date_conv_mode_t(fuzzydate & ~TIME_TIME_ONLY),
                        time_round_mode_t(fuzzydate));
  Datetime *d= new(ltime) Datetime(thd, args[0], opt);
  if ((null_value= (!d->is_valid_datetime() || ltime->month == 0)))
    return true;
  uint month_idx= ltime->month-1;
  ltime->day= days_in_month[month_idx];
  if ( month_idx == 1 && calc_days_in_year(ltime->year) == 366)
    ltime->day= 29;
  ltime->hour= ltime->minute= ltime->second= 0;
  ltime->second_part= 0;
  ltime->time_type= MYSQL_TIMESTAMP_DATE;
  return (null_value= 0);
}
