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

#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, &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);
}


/*
  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.

  Let's reserve this rarely used Unicode block to encode format charactes:
    U+E000 - U+F8FF Private Use Area

  Other characters in the format array mean them literally.
  Supplementary characters (>= U+10000) are not supported yet.
  This would need to change the data type for format elements from uint16
  to uint32.
*/

#define TO_DATE_FORMAT_RANGE_FIRST 0xE000
#define TO_DATE_FORMAT_RANGE_LAST  0xF8FF

enum enum_tochar_formats
{
  FMT_BASE= TO_DATE_FORMAT_RANGE_FIRST,
  FMT_AD,       /* Handled: Anno Domini ("in the year of the Lord") */
  FMT_AD_DOT,   /* Handled: Anno Domini ("in the year of the Lord") */
  FMT_AM,       /* Handled: Meridian indicator (Before midday) */
  FMT_AM_DOT,   /* Handled  Meridian indicator (Before midday) */
  FMT_BC,       /* Handled: Before Christ */
  FMT_BC_DOT,   /* Handled: Before Christ */
  FMT_CC,
  FMT_SCC,
  FMT_D,
  FMT_DAY,      /* Handled: Name of day */
  FMT_DD,       /* Handled: Day (1-31) */
  FMT_DDD,      /* Handled: Day of year (1-336) */
  FMT_DL,
  FMT_DS,
  FMT_DY,       /* Handled: Abbreviated name of day */
  FMT_E,
  FMT_EE,
  FMT_FF,       /* Handled: Fractional seconds */
  FMT_FM,       /* Handled: Value with no leading or trailing blanks */
  FMT_FX,
  FMT_HH,       /* Handled: Hour (1-12) */
  FMT_HH12,     /* Handled: Hour (1-12) */
  FMT_HH24,     /* Handled: Hour (0-23) */
  FMT_IW,       /* Handled: Week of year (1-53). Used with FMT_I*. ISO 8601 */
  /* FMT_I, FMT_IY...FMT_IYYY must be in sequence */
  FMT_I,        /* Handled: Year, 1 digit. Used with IW. ISO 8601 */
  FMT_IY,       /* Handled: Year, 2 digits. Used with IW. ISO 8601 */
  FMT_IYY,      /* Handled: Year, 3 digits. Used with IW. ISO 8601 */
  FMT_IYYY,     /* Handled: Year, 4 digits. Used with IW. ISO 8601 */
  FMT_J,
  FMT_MI,       /* Handled: Minutes (0-59) */
  FMT_MM,       /* Handled: Month (1-12) */
  FMT_MON,      /* Handled: Abbreviated name of month */
  FMT_MONTH,    /* Handled: Name of Month */
  FMT_PM,       /* Handled: Handled: Meridian indicator (After midday) */
  FMT_PM_DOT,   /* Handled: Handled: Meridian indicator (After midday) */
  FMT_RM,
  FMT_RR,       /* Handled: 20th century dates in the 21st century. 2 digits */
  FMT_RRRR,     /* Handled: 20th century dates in the 21st century. 4 digits */
  FMT_SS,       /* Handled: Seconds */
  FMT_SSSSSS,
  FMT_TS,
  FMT_TZD,
  FMT_TZH,
  FMT_TZM,
  FMT_TZR,
  FMT_W,
  FMT_WW,
  FMT_X,
  FMT_Y,        /* Handled: 1 digit year */
  FMT_YY,       /* Handled: 2 digits year */
  FMT_YYY,      /* Handled: 3 digits year */
  FMT_YYYY,     /* Handled: 4 digits year */
  FMT_YYYY_COMMA,
  FMT_YEAR,
  FMT_SYYYY,    /* Handled as YYYY */
  FMT_SYEAR
};

static const char *ad_bc_names[]=
{
  "AD", "A.D.", "BC", "B.C.", NullS
};

static uint ad_bc_lengths[]= {
  2, 4, 2, 4
};

static TYPELIB ad_bc_typelib=
{
  array_elements(ad_bc_names)-1, "", ad_bc_names, ad_bc_lengths, NULL
};

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

/*
   A value between 0-49 will return a 20xx year.
   A value between 50-99 will return a 19xx year.
*/

uint oracle_year_2000_handling(uint year)
{
  DBUG_ASSERT(year < 100);
  if ((year=year+1900) < 1950)
    year+=100;
  return year;
}


/*
  Like check_word() in strfunc, with the following differnces for
  multi-byte characters:
  - Word can contain numbers (needed for matching Japanese months)
  - If multiple partial matches and next character is space, try
    matching with two words (needed for matching vietnamese months)
*/

uint check_word(CHARSET_INFO *cs, TYPELIB *lib, const char *val,
                const char *end, const char **end_of_word)
{
  int res, rc=0, round;
  const char *ptr, *val_start= val;
  my_bool part_match;
  my_wc_t wc= 0;

  if (cs->mbmaxlen == 1)
    return check_word(lib, val, end, end_of_word);

  /*
    Try to match one word or two words.
    Needed for matching vietnamese months which are two words
  */
  for (round= 0 ; round < 2 ; round++, val= ptr+1)
  {
    /*
      Find end of word. Words ends with single letter non alpha character or
      end of string.
    */
    for (ptr=val ; ptr < end ; ptr+= rc)
    {
      if ((rc= cs->mb_wc(&wc, (const uchar*) ptr,
                         (const uchar*) end)) <= 0 ||
          (rc == 1 && !my_isalnum(cs, wc)))
        break;
    }
    if ((res= find_type2(lib, val_start, (size_t) (ptr - val_start),
                         &part_match, cs)) > 0)
    {
      *end_of_word= ptr;
      return res;
    }
    if (!part_match || ptr == end || rc != 1 || !my_isspace(cs, wc))
      return res;
  }
  return 0;                                     // No match
}


uint check_word_sp(CHARSET_INFO *cs, TYPELIB *lib, const char *val,
                   const char *end, const char **end_of_word)
{
  int res;
  const char *ptr;

  /* Find end of word */
  ptr= val+ cs->scan(val, end, MY_SEQ_NONSPACES);
  if ((res= find_type2(lib, val, (size_t) (ptr - val), 0, cs)) > 0)
    *end_of_word= ptr;
  return res;
}


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

  @param format         date/time format specification
  @param val            String to decode
  @param length         Length of string
  @param val_cs         The character set of "val"
  @param orig_val_cs    The original character set of "val"
  @param l_time         Store result here.
                        This value should be prefilled with the
                        current date in case format does not
                        have all date parts.
  @parrm locale         Used to get day and month names
  @param data_time_type Type of string. Used for error messages
  @param fuzzy_date     If partial dates are allowed
  @param give_error     Generate a warning/error
  @par
  @retval
    0	ok
  @retval
    1	error
*/

static bool
extract_oracle_date_time(THD *thd, uint16 *format_ptr,
                         const char *val, uint length, CHARSET_INFO *val_cs,
                         CHARSET_INFO *orig_val_cs,
                         MYSQL_TIME *l_time,
                         const MY_LOCALE *locale,
                         const char *date_time_type,
                         date_conv_mode_t fuzzydate,
                         bool give_error)
{
  int weekday= 0, daypart= 0;
  int frac_part, rc;
  uint yearday= 0;
  bool usa_time= 0, before_christ= 0, part_of_digits= 0;
  const char *val_begin= val;
  const char *val_end= val + length;
  char *tmp;
  CHARSET_INFO *cs= &my_charset_bin;
  my_wc_t wc, format;
  DBUG_ENTER("extract_oracle_date_time");

  for ( ; (format= *format_ptr) ; format_ptr++)
  {
    uint val_len;
    int error;

    tmp= (char*) val;
    val+= val_cs->cset->scan(val_cs, val, val_end, MY_SEQ_SPACES);
    if (tmp != val)
      part_of_digits= 0;

    if (format < TO_DATE_FORMAT_RANGE_FIRST ||
        format > TO_DATE_FORMAT_RANGE_LAST)
    {
      /* A control character or text string found in the format */

      if (format < 256 && my_isspace(cs, (uchar) format))
        continue;

      if (format == '"')
      {
        /* Handle quoted strings. Should match */
        while ((format= *++format_ptr) != '"')
        {
          int rc1, rc2;
          uchar buf1[MY_CS_MBMAXLEN], buf2[MY_CS_MBMAXLEN];
          if (!format || val == val_end)
            goto error;

          if ((rc= val_cs->mb_wc(&wc, (const uchar*) val,
                                 (const uchar*) val_end)) <= 0)
            goto error;

          if ((rc1= orig_val_cs->wc_mb(wc, buf1, buf1+sizeof(buf1))) <= 0)
            goto error;

          if ((rc2= orig_val_cs->wc_mb(format, buf2, buf2+sizeof(buf2))) <= 0)
            goto error;

          if (orig_val_cs->strnncoll(buf1, (size_t) rc1, buf2, (size_t) rc2))
            goto error;

          val+= rc;
        }
        part_of_digits= 0;
        continue;
      }

      if ((rc= val_cs->mb_wc(&wc,
                             (const uchar*) val,
                             (const uchar*) val_end)) <= 0)
        goto error;

      if (!INVALID_CHARACTER(format))
      {
        if (rc == 1)
        {
          if (my_isdigit(val_cs, (char) wc))
          {
            /*
              Oracle tochar allows values without punctuation characters for
              numercial values
            */
            continue;
          }
          part_of_digits= 0;
          if (format == wc)
          {
            val++;
            if (format_ptr[0] != format_ptr[1])
            {
              /*
                Oracle allow skipping of multiple format_characters
                Needed for things like TO_DATE('2001 -- 10','YYYY - MM')
              */
              while (val != val_end &&
                     (rc= val_cs->mb_wc(&wc,
                                        (const uchar*) val,
                                        (const uchar*) val_end)) == 1 &&
                     wc == format)
                val++;
            }
            continue;
          }
          if (!INVALID_CHARACTER(wc))
          {
            val+= rc;
            continue;
          }
        }
      }
      continue;
    }

    error= 0;
    if (!(val_len= (uint) (val_end - val)))
      goto error;

    switch (format) {
      /* Year */
    case FMT_YYYY:
    case FMT_SYYYY:
      tmp= (char*) val + MY_MIN(4, val_len);
      l_time->year= (int) val_cs->cset->strtoll10(val_cs, val, &tmp, &error);
      if (part_of_digits && (tmp-val) != 4)
        goto error;
      val= tmp;
      part_of_digits= 1;
      break;
    case FMT_YYY:
      tmp= (char*) val + MY_MIN(3, val_len);
      l_time->year= ((int) val_cs->cset->strtoll10(val_cs, val, &tmp, &error) +
                     l_time->year/1000*1000);
      if (part_of_digits && (tmp-val) != 3)
        goto error;
      val= tmp;
      part_of_digits= 1;
      break;
    case FMT_YY:
      tmp= (char*) val + MY_MIN(2, val_len);
      l_time->year= ((int) val_cs->cset->strtoll10(val_cs, val, &tmp, &error) +
                     l_time->year/100*100);
      if (part_of_digits && (tmp-val) != 2)
        goto error;
      val= tmp;
      part_of_digits= 1;
      break;
    case FMT_Y:
      tmp= (char*) val + 1;
      l_time->year= ((int) val_cs->cset->strtoll10(val_cs, val, &tmp, &error) +
                     l_time->year/10*10);
      val= tmp;
      part_of_digits= 1;
      break;
    case FMT_RR:
    {
      uint year;
      tmp= (char*) val + MY_MIN(2, val_len);
      year= (uint)val_cs->cset->strtoll10(val_cs, val, &tmp, &error);
      if (part_of_digits && (tmp-val) != 2)
        goto error;
      l_time->year= year >= 100 ? year : oracle_year_2000_handling(year);
      val= tmp;
      part_of_digits= 1;
      break;
    }
    case FMT_RRRR:
    {
      /* Accept 2 or 4 digits years. If 2 digits, works like RR */
      uint year;
      tmp= (char*) val + MY_MIN(4, val_len);
      year= (uint)val_cs->cset->strtoll10(val_cs, val, &tmp, &error);
      if (part_of_digits && (tmp-val) != 2 && (tmp-val != 4))
        goto error;
      l_time->year= year >= 100 ? year : oracle_year_2000_handling(year);
      val= tmp;
      part_of_digits= 1;
      break;
    }

    case FMT_AD:
    case FMT_AD_DOT:
    case FMT_BC:
    case FMT_BC_DOT:
    {
      int period;
      if ((period= check_word_sp(val_cs, &ad_bc_typelib, val, val_end,
                                 &val)) <= 0)
        goto error;
      before_christ= period > 2;
      break;
    }

    /* Month */
    case FMT_MM:
      tmp= (char*) val + MY_MIN(2, val_len);
      l_time->month= (int) val_cs->cset->strtoll10(val_cs, val, &tmp, &error);
      if (part_of_digits && (tmp-val) != 2)
        goto error;
      val= tmp;
      part_of_digits= 1;
      break;
    case FMT_MONTH:
      if ((l_time->month= check_word(val_cs, locale->month_names,
                                     val, val_end, &val)) <= 0)
        goto error;
      break;
    case FMT_MON:
      if ((l_time->month= check_word(val_cs, locale->ab_month_names,
                                     val, val_end, &val)) <= 0)
        goto error;
      break;

      /* Day */
    case FMT_DD:
      tmp= (char*) val + MY_MIN(2, val_len);
      l_time->day= (int) val_cs->cset->strtoll10(val_cs, val, &tmp, &error);
      if (part_of_digits && (tmp-val) != 2)
        goto error;
      val= tmp;
      part_of_digits= 1;
      break;

      /* Hour */
    case FMT_HH:
    case FMT_HH12:
      usa_time= 1;
      /* fall through */
    case FMT_HH24:
      tmp= (char*) val + MY_MIN(2, val_len);
      l_time->hour= (int) val_cs->cset->strtoll10(val_cs, val, &tmp, &error);
      if (part_of_digits && (tmp-val) != 2)
        goto error;
      val= tmp;
      part_of_digits= 1;
      break;

      /* Minute */
    case FMT_MI:
      tmp= (char*) val + MY_MIN(2, val_len);
      l_time->minute= (int) val_cs->cset->strtoll10(val_cs, val, &tmp, &error);
      if (part_of_digits && (tmp-val) != 2)
        goto error;
      val= tmp;
      part_of_digits= 1;
      break;

      /* Second */
    case FMT_SS:
      tmp= (char*) val + MY_MIN(2, val_len);
      l_time->second= (int) val_cs->cset->strtoll10(val_cs, val, &tmp, &error);
      if (part_of_digits && (tmp-val) != 2)
        goto error;
      val= tmp;
      part_of_digits= 1;
      break;

      /* Second part */
    case FMT_FF:
    {
      uint length= 6;
      if (format_ptr[1] >= '1' && format_ptr[1] <= '6')
        length= *++format_ptr - (uint) '0';
      tmp= (char*) val + MY_MIN(length, val_len);
      l_time->second_part= (int) val_cs->cset->strtoll10(val_cs, 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;
      part_of_digits= 0;                        // Ignore end digits
      break;
    }
      /* AM / PM */
    case FMT_AM:
    case FMT_AM_DOT:
    case FMT_PM:
    case FMT_PM_DOT:
      if (val_len < 2 || ! usa_time)
        goto error;
      if (!val_cs->strnncoll(val, 2, "PM", 2))
      {
        daypart= 12;
        val+= 2;
      }
      else if (!val_cs->strnncoll(val, 2, "AM", 2))
        val+= 2;
      else if (val_len >= 4 && !val_cs->strnncoll(val, 4, "P.M.", 4))
      {
        daypart= 12;
        val+= 4;
      }
      else if (val_len >= 4 && !val_cs->strnncoll(val, 4, "A.M.", 4))
        val+= 4;
      else
        goto error;
      break;

      /* Exotic things. Weekdays are only use validation of date */
    case FMT_DAY:
      if ((weekday= check_word(val_cs, locale->day_names, val, val_end,
                               &val)) <= 0)
        goto error;
      break;
    case FMT_DY:
      if ((weekday= check_word(val_cs, locale->ab_day_names, val, val_end,
                               &val)) <= 0)
        goto error;
      break;

    case FMT_DDD:	 // Day of year
      tmp= (char*) val + MY_MIN(val_len, 3);
      yearday= (uint) val_cs->cset->strtoll10(val_cs, val, &tmp, &error);
      if (yearday == 0)                     // End range checked later
        goto error;
      if (part_of_digits && (tmp-val) != 3)
        goto error;
      val= tmp;
      part_of_digits= 1;
      break;

    default:
      goto error;
    }
    if (unlikely(error))                  // Error from my_strtoll10
      goto error;
  }

  /* Generate the datetime */
  if (usa_time)
  {
    if (l_time->hour > 12 || l_time->hour < 1)
      goto error;
    l_time->hour= l_time->hour%12+daypart;
  }

  if (yearday > 0)
  {
    uint days;
    if (yearday > calc_days_in_year(l_time->year))
      goto error;
    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 error;
  }

  if (before_christ)                            // Cannot handle negative dates
    goto error;

  if (weekday && calc_weekday(calc_daynr(l_time->year,l_time->month,
                                         l_time->day),0) != weekday -1)
    goto error;

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

  int was_cut;
  if (check_date(l_time, fuzzydate, &was_cut))
    goto error;

  if (val != val_end)
  {
    /* There was more data than the format allowed.
       End space is ok.
       Don't allow ending with unfinished digits stream except with
       fractional digits
       Give warning for cut date in other cases.
    */
    if (part_of_digits && my_isdigit(&my_charset_latin1, *val))
      goto error;
    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, l_time->time_type,
                                     nullptr, nullptr, nullptr);
	break;
      }
    } while (++val != val_end);
  }
  DBUG_RETURN(0);

error:
  if (give_error)
  {
    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, "to_date");
  }
  DBUG_RETURN(1);
}


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

static bool make_date_time(THD *thd, 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;
  struct my_tz curr_tz;
  Time_zone* curr_timezone= 0;

  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;

      case 'z':
      {
        if (!curr_timezone)
        {
          curr_timezone= thd->variables.time_zone;
          curr_timezone->get_timezone_information(&curr_tz, l_time);
        }
        long minutes= labs(curr_tz.seconds_offset)/60, diff_hr, diff_min;
        diff_hr= minutes/60;
        diff_min= minutes%60;

        str->append(curr_tz.seconds_offset < 0 ? '-' : '+');
        str->append(static_cast<char>('0' + diff_hr/10));
        str->append(static_cast<char>('0' + diff_hr%10));
        str->append(static_cast<char>('0' + diff_min/10));
        str->append(static_cast<char>('0' + diff_min%10));
        break;
      }
      case 'Z':
        if (!curr_timezone)
        {
          curr_timezone= thd->variables.time_zone;
          curr_timezone->get_timezone_information(&curr_tz, l_time);
        }
        str->append(curr_tz.abbreviation, strlen(curr_tz.abbreviation));
        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= (my_time_t) tm.tv_sec;
  *second_part= tm.tv_usec;
  if ((null_value= (tm.tv_sec == 0 && tm.tv_usec == 0)))
  {
    /*
      The value {0,0}='1970-01-01 00:00:00.000000 GMT' cannot be
      stored in a TIMESTAMP field. Return SQL NULL.
      Simmetrically, UNIX_TIMESTAMP(0) also returns SQL NULL.
    */
    return true;
  }
  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(0, 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;
}


bool Item_func_current_timestamp::val_native(THD *thd, Native *to)
{
  Timestamp ts(Timeval(thd->query_start(), thd->query_start_sec_part()));
  /*
    to_native() can fail in case of EOM. Don't set null_value on EOM,
    because CURRENT_TIMESTAMP is NOT NULL. The statement will fail anyway.
  */
  return ts.trunc(decimals).to_native(to, decimals);
}


/**
    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)
{
  return check_fsp_or_error() ||
         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)
{
  return check_fsp_or_error() ||
         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(')');
}


/**
    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.
*/
bool Item_func_sysdate_local::val_native(THD *thd, Native *to)
{
  my_hrtime_t now= my_hrtime();
  Timestamp ts(hrtime_to_my_time(now), hrtime_sec_part(now));
  /*
    to_native() can fail on EOM. Don't set null_value here,
    because SYSDATE is NOT NULL. The statement will fail anyway.
  */
  return ts.trunc(decimals).to_native(to, decimals);
}


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 'Z': /* time zone abbreviation */
      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 'z': /* time zone offset */
        size += 5;
        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(thd, format, &l_time,
                      is_time_format ? MYSQL_TIMESTAMP_TIME :
                                       MYSQL_TIMESTAMP_DATE,
                      lc, str))
    return str;

null_date:
  null_value=1;
  return 0;
}


/* A class to print TO_CHAR(date_time, format) */
class Date_time_format_oracle
{
  // m_fm is true if "FM" was found in the format string odd number of times
  bool m_fm;
public:
  Date_time_format_oracle()
   :m_fm(false)
  { }

  /*
    Append a numeric value to a String.
    If m_fm is false, then left-pad the numeric value with '0'.

    @param val       - the numeric value to be appended to str
    @param size      - the maximum number of digits possible in val
    @param [OUT] str - the result String (val will be appended to it)

    @retval false    - on success
    @retval true     - on error (e.g. EOM)
  */
  bool append_val(int val, uint size, String *str) const
  {
    if (m_fm)
      return str->append_longlong(val);
    return str->append_zerofill(val, size);
  }

  /*
    Append a LEX_CSTRING value to a String.
    If m_fm is false, then right-pad the appended value with spaces.

    @param ls              - the LEX_CSTRING to be append to str
    @param max_char_length - the maximum possible length of ls, in characters
    @param [OUT] str       - the result String (ls will be appended to it)

    @retval false          - on success
    @retval true           - on error (e.g. EOM)
 */
  bool append_lex_cstring(const LEX_CSTRING ls, uint max_char_length,
                          String *str) const
  {
    // Locale data uses utf8mb3
    static constexpr CHARSET_INFO *cs= &my_charset_utf8mb3_general_ci;
    str->append(ls.str, ls.length, cs);
    if (!m_fm)
    {
      size_t char_length= cs->numchars(ls.str, ls.str + ls.length);
      if (char_length < max_char_length)
        return str->fill(str->length() + max_char_length - char_length, ' ');
    }
    return false;
  }

  /*
    Print a date/time value to a String according to the given format
    @param fmt_array - the format array
    @param l_time    - the date/time value
    @param locale    - the locale to use for textual components
                       (MONTH and DAY)
    @param [OUT] str - the string to print into.

    @retval false    - on success
    @retval true     - on error (e.g. EOM)
  */
  bool format(const uint16 *fmt_array,
              const MYSQL_TIME *l_time,
              const MY_LOCALE *locale,
              String *str);
};


/**
  Special characters are directly output in the result

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

static uint parse_special(uchar 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 characters 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;
}


static inline bool formats_used(uint64 *used, int src)
{
  uint64 bit= 1ULL << (src-TO_DATE_FORMAT_RANGE_FIRST);
  if (*used & bit)
    return 1;                                 // Duplicate item
  (*used)|= bit;
  return 0;
}


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

  @param format          Format string
  @param fmt_array       Packed format
  @param fmt_len         Max length of formatted date string is stored here
  @param locale          Locale
  @param for_to_date     If the format is for the to_date() oracle function
  @param warning_message Error message is stored here.
  @param flags           Bits of PARSE_TYPE_FLAGS of formats used

  @return 0 ok.     fmt_len and flags are updated
  @return 1 error   warnings_massage set and flags=0
*/


static bool parse_format_string(const String *format, uint16 *fmt_array,
                                uint *fmt_len, const MY_LOCALE *locale,
                                bool for_to_date,
                                String *warning_message,
                                PARSE_TYPE_FLAGS *flags)
{
  int mblen;
  const char *ptr, *end;
  uint16 *tmp_fmt= fmt_array;
  uint tmp_len= 0;
  int offset= 0;
  bool quotation_flag= false;
  uint64 used= 0;                               // Formats used
  uint type_flags= PARSE_TYPE_NONE;

  *flags= PARSE_TYPE_NONE;
  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;
  }
  if (format->length() == 0)
  {
    *flags= PARSE_TYPE_NULL;         // Empty string in oracle is same as null
    *fmt_len= 0;
    *tmp_fmt= 0;
    return 0;
  }

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

    mblen= format->charset()->mb_wc(&cfmt, (uchar*) ptr, (uchar*) end);
    if (mblen < 1 || cfmt > 0xFFFF)
      goto error;

    if (cfmt == '"')
    {
      quotation_flag= !quotation_flag;
      *tmp_fmt= (uint16) cfmt;
      tmp_len++;                                // Count '"' (why)
      continue;
    }
    if (quotation_flag)
    {
      *tmp_fmt= (uint16) cfmt;
      tmp_len++;
      continue;
    }

    if (cfmt >= 128)
      goto error; // Only ASCII characters are allowed outside of quotes

    cfmt= my_toupper(system_charset_info, (uchar) cfmt);

    /*
      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
      Date_time_format_oracle::format().

      NOTE: the quotation mark is not print in return value. for example:
      select TO_CHAR(sysdate, 'YYYY"abc"MM"xyzDD"') will return 2021abc01xyz11
     */

    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')
      {
        if (for_to_date && formats_used(&used, FMT_AD))
          goto error;
        *tmp_fmt= FMT_AD;
        ptr+= 1;
        tmp_len+= 2;
      }
      else if (next_char == 'M')
      {
        if (for_to_date && formats_used(&used, FMT_AM))
          goto error;
        *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')
        {
          if (for_to_date && formats_used(&used, FMT_AD))
            goto error;
          *tmp_fmt= FMT_AD_DOT;
          ptr+= 3;
          tmp_len+= 4;
        }
        else if (my_toupper(system_charset_info, *(ptr+2)) == 'M')
        {
          if (for_to_date && formats_used(&used, FMT_AM))
            goto error;
          *tmp_fmt= FMT_AM_DOT;
          ptr+= 3;
          tmp_len+= 4;
        }
        else
          goto error;
      }
      else
        goto error;
      type_flags|= PARSE_TYPE_NON_DETERMINISTIC;
      break;
    case 'B':                                     // BC and B.C
      if (ptr+1 >= end)
        goto error;
      if (for_to_date && formats_used(&used, FMT_BC))
        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 'I':
      if (for_to_date)
        goto error;
      if (ptr+1 >= end)
        goto found_I;
      next_char= my_toupper(system_charset_info, *(ptr+1));
      if (next_char == 'W')
      {
        *tmp_fmt= FMT_IW;
        tmp_len+=2;
        ptr++;
      }
      else if (next_char == 'Y')
      {
        uint i;
        for (i= 0 ; i < 3 && ++ptr < end; i++)
          if (my_toupper(system_charset_info, *(ptr+1)) != 'Y')
            break;

        *tmp_fmt= FMT_IY+i;
        tmp_len+= 2 + i;
      }
      else
      {
    found_I:
        *tmp_fmt= FMT_I;
        tmp_len+= 1;
      }
      break;
    case 'P':                                   // PM or P.M.
      if (ptr + 1 == end)
        goto error;

      if (for_to_date && formats_used(&used, FMT_AM))
        goto error;

      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 == '.' && ptr + 3 < end &&
               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 or YYYYY
      if (for_to_date && formats_used(&used, FMT_Y))
        goto error;

      if (ptr + 1 == end || my_toupper(system_charset_info, *(ptr+1)) != 'Y')
      {
        *tmp_fmt= FMT_Y;
        tmp_len+= 1;
        type_flags|= PARSE_TYPE_PART_YEAR;
        break;
      }
      if (ptr + 2 == end ||
          my_toupper(system_charset_info, *(ptr+2)) != 'Y') /* YY */
      {
        *tmp_fmt= FMT_YY;
        type_flags|= PARSE_TYPE_PART_YEAR;
        ulen= 2;
      }
      else
      {
        if (ptr + 3 < end && my_toupper(system_charset_info, *(ptr+3)) == 'Y')
        {
          *tmp_fmt= FMT_YYYY;
          ulen= 4;
          type_flags|= PARSE_TYPE_YEAR;
        }
        else
        {
          *tmp_fmt= FMT_YYY;
          type_flags|= PARSE_TYPE_PART_YEAR;
          ulen= 3;
        }
      }
      ptr+= ulen-1;
      tmp_len+= ulen;
      break;
    case 'R':                                   // RR or RRRR
      if (for_to_date && formats_used(&used, FMT_Y))
        goto error;

      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;
      type_flags|= PARSE_TYPE_YEAR;
      break;
    case 'M':
    {
      char tmp1;
      if (ptr + 1 >= end)
        goto error;

      tmp1= my_toupper(system_charset_info, *(ptr+1));
      if (tmp1 == 'M')
      {
        if (for_to_date && formats_used(&used, FMT_MM))
          goto error;
        *tmp_fmt= FMT_MM;
        tmp_len+= 2;
        type_flags|= PARSE_TYPE_MONTH;
        ptr+= 1;
      }
      else if (tmp1 == 'I')
      {
        if (for_to_date && formats_used(&used, FMT_MI))
          goto error;
        *tmp_fmt= FMT_MI;
        tmp_len+= 2;
        type_flags|= PARSE_TYPE_MM;
        ptr+= 1;
      }
      else if (tmp1 == 'O')
      {
        if (ptr + 2 >= end)
          goto error;
        if (for_to_date && formats_used(&used, FMT_MM))
          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;
        }
        type_flags|= PARSE_TYPE_MONTH | PARSE_TYPE_NON_DETERMINISTIC;
      }
      else
        goto error;
    }
    break;
    case 'D':                                   // D, DD, DY, or DAY
    {
      char tmp1;
      if (ptr + 1 >= end)
        goto error;

      tmp1= my_toupper(system_charset_info, *(ptr+1));
      if (tmp1 == 'D')
      {
        if (ptr + 2 != end &&
            my_toupper(system_charset_info, *(ptr+2)) == 'D')
        {
          if (for_to_date &&
              (formats_used(&used, FMT_DDD) ||
               formats_used(&used, FMT_DD) ||
               formats_used(&used, FMT_MM)))
            goto error;
          *tmp_fmt= FMT_DDD;                    // Day of year
          tmp_len+= 3;
          ptr+= 2;
          break;
        }
        if (for_to_date && formats_used(&used, FMT_DD))
          goto error;
        *tmp_fmt= FMT_DD;                       // Day of month 1-31
        tmp_len+= 2;
        type_flags|= PARSE_TYPE_DAY | PARSE_TYPE_NON_DETERMINISTIC;
      }
      else if (tmp1 == 'Y')
      {
        *tmp_fmt= FMT_DY;                       // Day name
        tmp_len+= 3;
        type_flags|= PARSE_TYPE_WEEKDAY | PARSE_TYPE_NON_DETERMINISTIC;
      }
      else if (tmp1 == 'A')
      {
        if (ptr + 2 == end || my_toupper(system_charset_info, *(ptr+2)) != 'Y')
          goto error;
        *tmp_fmt= FMT_DAY;                      // Day name
        tmp_len+= locale->max_day_name_length * my_charset_utf8mb3_bin.mbmaxlen;
        type_flags|= PARSE_TYPE_WEEKDAY | PARSE_TYPE_NON_DETERMINISTIC;
        ptr+= 1;
      }
      else
        goto error;                             // 'D', weekday 1-7, territory
      ptr+= 1;
    }
    break;
    case 'H':                                   // HH, HH12 or HH23
    {
      char tmp1, tmp2, tmp3;
      if (ptr + 1 >= end)
        goto error;
      if (for_to_date && formats_used(&used, FMT_HH))
        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;
      type_flags|= PARSE_TYPE_HH;
      break;
    }
    case 'S':                                   // SS
      if (ptr + 1 == end)
        goto error;
      if (my_toupper(system_charset_info, *(ptr+1)) == 'S')
      {
        if (for_to_date && formats_used(&used, FMT_SS))
          goto error;
        *tmp_fmt= FMT_SS;
        tmp_len+= 2;
        type_flags|= PARSE_TYPE_SS;
        ptr+= 1;
      }
      else if (ptr + 5 <= end &&
               !my_charset_latin1.strnncoll(ptr+1, 4, "YYYY", 4))
      {
        *tmp_fmt= FMT_SYYYY;
        tmp_len+= 4;
        type_flags|= PARSE_TYPE_YEAR;
        ptr+= 4;
      }
      else
        goto error;
      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;
    case 'F':
      if (ptr + 1 == end)
        goto error;
      if (my_toupper(system_charset_info, ptr[1]) == 'M')
      {
        if (for_to_date)
          goto error;
        *tmp_fmt= FMT_FM;
        ptr+= 1;
      }
      else if (my_toupper(system_charset_info, ptr[1]) == 'F')
      {
        if (for_to_date && formats_used(&used, FMT_FF))
          goto error;
        *tmp_fmt= FMT_FF;
        type_flags|= PARSE_TYPE_SUBSECONDS;
        ptr+= 1;
        if (ptr + 1 <= end && ptr[1] >= '1' && ptr[1] <= '6')
          *++tmp_fmt= *++ptr;                   // Copy format length
        tmp_len+= 6;
      }
      else
        goto error;
      break;

    default:
      DBUG_ASSERT(cfmt < 256);
      offset= parse_special((uchar) 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;
    }
  }
  if (quotation_flag)
  {
    warning_message->append(STRING_WITH_LEN("date_format is missing end '\"'"));
    return 1;
  }

  *fmt_len= tmp_len;
  *tmp_fmt= 0;
  *flags= (PARSE_TYPE_FLAGS) type_flags;
  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;
}

/**
  Get the result datetime type for parsed string

  Same logic used as in get_date_time_result_type()
*/

static const Item_handled_func::Handler *
get_parsed_result_type(PARSE_TYPE_FLAGS type)
{
  if (type & PARSE_TYPE_SUBSECONDS)
    return &func_handler_str_to_date_datetime_usec;
  return &func_handler_str_to_date_datetime_sec;
}


bool Date_time_format_oracle::format(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;
  uint year= 0, week= 0;

  str->length(0);

  while (*ptr)
  {
    if (*ptr == '"')
    {
      ptr++;
      quotation_flag= !quotation_flag;
      continue;
    }
    if (quotation_flag)
    {
      DBUG_ASSERT(*ptr <= 255);
      str->append((char) *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_SYYYY:
    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
        {
          if (append_lex_cstring(locale->month_name(l_time->month - 1),
                                 locale->max_month_name_length, str))
            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
        {
          weekday=calc_weekday(calc_daynr(l_time->year,l_time->month,
                                          l_time->day), 0);
          if (append_lex_cstring(locale->day_name(weekday),
                                 locale->max_day_name_length, str))
            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;

    case FMT_FF:
    {
      uint length= 6;
      if (ptr[1] >= '1' && ptr[1] <= '6')
        length= *++ptr - (uint) '0';
      if (append_val((int)(l_time->second_part / log_10_int[6-length]), length, str))
        goto err_exit;
      break;
    }
    case FMT_FM:
      m_fm= !m_fm;
      break;

    case FMT_DDD:
    {
      uint day= (calc_daynr(l_time->year, l_time->month, l_time->day) -
                 calc_daynr(l_time->year, 1, 1) + 1);
      if (append_val(day, 3, str))
        goto err_exit;
      break;
    }
    case FMT_IW:
    case FMT_I:
    case FMT_IY:
    case FMT_IYY:
    case FMT_IYYY:
      if (!week)
        week= calc_week(l_time, WEEK_MONDAY_FIRST | WEEK_YEAR, &year);
      if (*ptr == FMT_IW)
      {
        if (str->append_longlong(week))
          goto err_exit;
      }
      else
      {
        /* Handling FMT_I, FMT_IY, FMT_IYY and FMT_IYYY */
        int length= (*ptr - FMT_I) + 1;
        if (append_val(year % log_10_int[length], length, str))
          goto err_exit;
      }
      break;
    default:
      if (*ptr > 127)
        goto err_exit;
      str->append((char) *ptr);
    }

    ptr++;
  };
  DBUG_ASSERT(!quotation_flag);
  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;
    PARSE_TYPE_FLAGS flags;
    fixed_length= 1;
    if (parse_format_string(str, fmt_array, &ulen, locale, 0, &warning_message,
                            &flags))
    {
      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;
    PARSE_TYPE_FLAGS flags;
    if (!(format= args[1]->val_str(&format_buffer)) || !format->length() ||
        parse_format_string(format, fmt_array, &ulen, locale, 0,
                            &warning_message, &flags))
      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 (!Date_time_format_oracle().format(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)
{
  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::val_native(THD *thd, Native *to)
{
  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);

  // decimals can be NOT_FIXED_DEC
  decimal_digits_t fixed_decimals= MY_MIN(decimals, TIME_SECOND_PART_DIGITS);

  sec.round(fixed_decimals, thd->temporal_round_mode());

  if (sec.sec() == 0 && sec.usec() == 0)
  {
    /*
      The value {0,0}='1970-01-01 00:00:00.000000 GMT' cannot be
      stored in a TIMESTAMP field. Return SQL NULL.
      Simmetrically, UNIX_TIMESTAMP('1970-01-01 00:00:00')
      also returns SQL NULL (assuming time_zone='+00:00').
    */
    thd->push_warning_truncated_wrong_value("unixtime", "0.0");
    return (null_value= true); // 0.0 after rounding
  }

  if (sec.sec() > TIMESTAMP_MAX_VALUE)
    return (null_value= true); // Went out of range after rounding

  const Timestamp ts(Timeval(sec.sec(), sec.usec()));
  return null_value= ts.to_native(to, fixed_decimals);
}


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 (typeid(this) != typeid(item))
    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_charset(String *str)
{
  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"));
  }
}


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(')');
  }
  print_charset(str);
  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 (!(res= args[0]->val_str(str)))
  {
    null_value= 1;
    return 0;
  }
  return val_str_generic_finalize(res, str);
}


/*
  Adjust the result of: res= args[0]->val_str(str);
  according to the cast length.
  @param res - the value returned from val_str()
  @param str - the value passed to val_str() as a buffer.
*/
String *Item_char_typecast::val_str_generic_finalize(String *res, String *str)
{
  if (has_explicit_length())
    cast_length= adjusted_length_with_warn(cast_length);

  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= DTCollation::string_typecast(cast_cs);
  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);
}


/**
  Compile the format string to fmt_array

  @return The type flags found. PARSE_TYPE_NONE in case of error
*/

PARSE_TYPE_FLAGS Item_func_to_date::get_format()
{
  StringBuffer<128> format_str;
  String *format= args[1]->val_str(&format_str, &format_converter,
                                   (args[1]->collation.collation->state &
                                    MY_CS_NONASCII) ?
                                   &my_charset_utf8mb3_general_ci : nullptr);
  PARSE_TYPE_FLAGS result_type= PARSE_TYPE_NONE;

  if (!args[1]->null_value)
  {
    uint ulen;
    warning_message.length(0);
    if (parse_format_string(format, fmt_array, &ulen, locale, 1,
                            &warning_message, &result_type))
    {
      my_printf_error(ER_STD_INVALID_ARGUMENT,
                      ER(ER_STD_INVALID_ARGUMENT),
                      MYF(0),
                      warning_message.c_ptr(),
                      func_name());
      return PARSE_TYPE_NONE;
    }
    if (result_type == PARSE_TYPE_NONE)      // If no format modifiers
      result_type= PARSE_TYPE_NO_FORMAT;
  }
  else
    result_type= PARSE_TYPE_NULL;
  return result_type;
}


/**
  Read arguments from a string of value pair in the format of 'variable=value'

  @param pos       Parsing point. Will be updated to end of value pare
  @param variable  Will contain the variable name
  @param value     Will contain the value

  @return 0     value pair found
  @return 1     Error

  The caller has ensured that there is no prespace in the string and
  the string is not empty.
  Value pairs are separated by space or tab. There may be space before
  and after '='.
  The value may be surrounded by quotes ' or ".
*/

bool get_next_argument(const char **pos_arg, const char *end,
                       LEX_CSTRING *variable, LEX_CSTRING *value)
{
  const char *pos= *pos_arg;
  DBUG_ASSERT(pos < end);
  DBUG_ASSERT(!my_isspace(&my_charset_latin1, *pos));

  variable->str= pos;

  for (;;)
  {
    if (*pos == '=')
    {
      variable->length= (size_t) (pos - variable->str);
      break;
    }
    if (++pos == end)
      return true;
    if (my_isspace(&my_charset_latin1, *pos))
    {
      variable->length= (pos - variable->str);
      do
      {
        if (++pos == end)
          return true;
      } while (my_isspace(&my_charset_latin1, *pos));
      if (likely(*pos == '='))
        break;
      return true;
    }
  }
  if (variable->length == 0)
    return true;                    // Empty variables are not allowed

  pos++;                            // Skip '='
  if (pos == end)
    return true;                    // Empty values are not allowed

  while (my_isspace(&my_charset_latin1, *pos))
  {
    if (++pos == end)
      return true;                  // Empty values are not allowed
  }
  /* To make it easier for MariaDB users, we support both " and ' for quting */
  if (*pos == '\'' || *pos == '"')
  {
    char quote= *pos;
    value->str= pos+1;
    do
    {
      pos++;
      if (pos == end)
        return 1;
    } while (*pos != quote);
    value->length= (size_t) (pos - value->str);
    pos++;                          // Skip end quote
  }
  else
  {
    value->str= pos;
    do
    {
      pos++;
    } while (pos != end && !my_isspace(&my_charset_latin1, *pos));
    value->length= (size_t) (pos - value->str);
  }
  *pos_arg= pos;
  return 0;
}


/* Parse Oracle NLS_PARAM and set locale if used */

void Item_func_to_date::parse_nls_param(LEX_CSTRING *nls_param)
{
  LEX_CSTRING param, value;
  const char *pos, *end, *start;

  if (!nls_param || nls_param->length == 0)
    return;
  pos= nls_param->str;
  end= pos+ nls_param->length;

  for (start= pos ;start != end ; start= pos)
  {
    while (my_isspace(&my_charset_latin1, *start))
    {
      if (++start == end)
        return;
    }
    pos= start;
    if (get_next_argument(&pos, end, &param, &value))
    {
      my_printf_error(ER_STD_INVALID_ARGUMENT,
                      ER(ER_STD_INVALID_ARGUMENT),
                      MYF(ME_WARNING),
                      start,
                      func_name());
      return;
    }

    if (!my_charset_latin1.strnncoll(param.str, param.length,
                                     STRING_WITH_LEN("NLS_CALENDAR")))
    {
      /* Skip default calendar */
      if (!my_charset_latin1.strnncoll(value.str, value.length,
                                       STRING_WITH_LEN("GREGORIAN")))
        continue;
      goto warning;
    }

    if (!my_charset_latin1.strnncoll(param.str, param.length,
                                     STRING_WITH_LEN("NLS_DATE_LANGUAGE")))
    {
      /* Check first Oracle name, then MariaDB name */
      if (!(locale= my_locale_by_oracle_name((LEX_CSTRING) value)) &&
          !(locale= my_locale_by_name((LEX_CSTRING) value)))
      {
        my_error(ER_UNKNOWN_LOCALE, MYF(0), value.str);
        /* return error in fix_fields/Item_func_to_date::fix_length_and_dec */
        nls_param_error= 1;
      }
      return;
    }

    /* Give warning about unknown NSL parameter */
  warning:
    warning_message.length(0);
    warning_message.append(param.str, param.length);
    warning_message.append_char('=');
    warning_message.append(value.str, value.length);
    my_printf_error(ER_STD_INVALID_ARGUMENT,
                    ER(ER_STD_INVALID_ARGUMENT),
                    MYF(ME_WARNING),
                    warning_message.c_ptr(),
                    func_name());
    /* Reset warning as it is used by other functions */
    warning_message.length(0);
  }
}


void Item_func_to_date::print(String *str, enum_query_type query_type)
{
  str->append(func_name_cstring());
  str->append('(');
  args[0]->print(str, query_type);
  if (arg_count == 3)
  {
    str->append(STRING_WITH_LEN(" DEFAULT "));
    args[2]->print(str, query_type);
    str->append(STRING_WITH_LEN(" ON CONVERSION ERROR"));
  }
  str->append(',');
  args[1]->print(str, query_type);
  if (nls_param.length)
  {
    str->append(",'",2);
    str->append_for_single_quote_opt_convert(nls_param.str, nls_param.length,
                                             system_charset_info);
    str->append('\'');
  }
  str->append(')');
}


bool Item_func_to_date::fix_length_and_dec(THD *thd)
{
  int id;
  if (nls_param_error)
    return true;

  if (!locale)                        // If not set by parse nls_param
    locale= thd->variables.lc_time_names;

  if (!args[id=0]->type_handler()->is_traditional_scalar_type() ||
      !args[id=1]->type_handler()->is_traditional_scalar_type() ||
      (arg_count == 3 &&
       !args[id=2]->type_handler()->is_traditional_scalar_type()))
  {
    my_error(ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION, MYF(0),
             args[id]->type_handler()->name().ptr(), func_name());
    return TRUE;
  }
  /* Used for args[0] & args[2] (the date strings) */
  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()))
  {
    if (!(format_flags= get_format()))
      return true;
    set_func_handler(get_parsed_result_type(format_flags));
  }
  if (!test_all_bits(format_flags, PARSE_TYPE_DATE))
  {
    /* Some time part is missing, get current time to fill them in */
    thd->variables.time_zone->gmt_sec_to_TIME(&now_time, thd->query_start());
    now_time.hour= now_time.minute= now_time.second= 0;
    now_time.second_part= 0;
    now_time.neg= 0;
  }
  else
    bzero(&now_time, sizeof(now_time));
  now_time.time_type= MYSQL_TIMESTAMP_NONE;

  return m_func_handler->fix_length_and_dec(this);
}


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

  val= args[0]->val_str(&val_string, &subject_converter, internal_charset);
  if (args[1]->null_value)
    goto error;
  if (args[0]->null_value)
  {
    if (arg_count == 2)
      goto error;
    val= args[2]->val_str(&val_string, &subject_converter, internal_charset);
    if (args[2]->null_value)
      goto error;
  }

  if (!const_item && (format_flags= get_format()) == PARSE_TYPE_NONE)
    goto error;

  /* Set default year, month and day */
  memcpy(ltime, &now_time, sizeof(*ltime));
  ltime->time_type= tstype;

  if (format_flags == PARSE_TYPE_NULL)
    goto error;

  if (!extract_oracle_date_time(thd, fmt_array,
                                val->ptr(), val->length(), val->charset(),
                                args[0]->collation.collation,
                                ltime, locale, "datetime",
                                (date_conv_mode_t(fuzzydate) |
                                 sql_mode_for_dates(thd)),
                                arg_count == 2))
    return (null_value= 0);

  if (arg_count == 3)
  {
    /* Try to use default value */
    val= args[2]->val_str(&val_string, &subject_converter, internal_charset);
    if (args[2]->null_value)
      goto error;
    if (!extract_oracle_date_time(thd, fmt_array,
                                  val->ptr(), val->length(), val->charset(),
                                  args[0]->collation.collation,
                                  ltime, locale, "datetime",
                                  (date_conv_mode_t(fuzzydate) |
                                   sql_mode_for_dates(thd)), 1))
      return (null_value= 0);
  }
  error:
    return (null_value=1);
}


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;
  ltime->day= calc_days_in_month(ltime->year, ltime->month);
  ltime->hour= ltime->minute= ltime->second= 0;
  ltime->second_part= 0;
  ltime->time_type= MYSQL_TIMESTAMP_DATE;
  return (null_value= 0);
}


/* enum for Oracle TRUNC function */

struct TRUNC_FORMAT
{
  const LEX_CSTRING name;
  Item_func_trunc::enum_trunc format;
};


/*
  Formats in sorted order. Only the Oracle formats that truncates to year,
  month or day are supported
*/
static struct TRUNC_FORMAT trunc_options[]=
{
  {{STRING_WITH_LEN("DD")}, Item_func_trunc::TRUNC_DAY },
  {{STRING_WITH_LEN("DDD")}, Item_func_trunc::TRUNC_DAY },
  {{STRING_WITH_LEN("J")}, Item_func_trunc::TRUNC_DAY },
  {{STRING_WITH_LEN("MM")}, Item_func_trunc::TRUNC_MONTH },
  {{STRING_WITH_LEN("MON")}, Item_func_trunc::TRUNC_MONTH },
  {{STRING_WITH_LEN("MONTH")}, Item_func_trunc::TRUNC_MONTH },
  {{STRING_WITH_LEN("RM")}, Item_func_trunc::TRUNC_MONTH },
  {{STRING_WITH_LEN("SYEAR")}, Item_func_trunc::TRUNC_YEAR },
  {{STRING_WITH_LEN("SYYYY")}, Item_func_trunc::TRUNC_YEAR },
  {{STRING_WITH_LEN("Y")}, Item_func_trunc::TRUNC_YEAR },
  {{STRING_WITH_LEN("YEAR")}, Item_func_trunc::TRUNC_YEAR },
  {{STRING_WITH_LEN("YY")}, Item_func_trunc::TRUNC_YEAR },
  {{STRING_WITH_LEN("YYY")}, Item_func_trunc::TRUNC_YEAR },
  {{STRING_WITH_LEN("YYYY")}, Item_func_trunc::TRUNC_YEAR },
};


Item_func_trunc::enum_trunc
Item_func_trunc::get_trunc_option(const LEX_CSTRING format)
{
  uint low=0, high= array_elements(trunc_options) - 1;

  /* Use binary search to find the format */
  do
  {
    uint mid= (low+high)/2;
    int cmp= my_charset_latin1.strnncoll(format, trunc_options[mid].name);
    if (!cmp)
      return trunc_options[mid].format;
    if (cmp > 0)
      low= mid+1;
    else
      high= mid-1;
  } while ((int) low <= (int) high);
  return TRUNC_IMPOSSIBLE;
}


bool Item_func_trunc::fix_length_and_dec(THD *thd)
{
  fix_attributes_datetime(args[0]->datetime_precision(thd));
  set_maybe_null();
  if (args[1]->can_eval_in_optimize())
  {
    String tmp, *res;
    if ((res= args[1]->val_str_ascii(&tmp)))
    {
      const_format= get_trunc_option(res->to_lex_cstring());
      if (const_format == TRUNC_IMPOSSIBLE)
        const_format= TRUNC_UNINIT;             // Error handling in get_date()
    }
  }
  return false;
}


bool Item_func_trunc::get_date(THD *thd, MYSQL_TIME *ltime,
                               date_mode_t fuzzydate)
{
  Datetime::Options opt(TIME_NO_ZEROS, TIME_FRAC_TRUNCATE);
  enum_trunc format= TRUNC_IMPOSSIBLE;
  Datetime *dt= new(ltime) Datetime(thd, args[0], opt);
  if ((null_value= !dt->is_valid_datetime()))
    return true;
  if (const_format != TRUNC_UNINIT)
    format= const_format;
  else
  {
    String tmp, *res;
    if ((res= args[1]->val_str_ascii(&tmp)))
      format= get_trunc_option(res->to_lex_cstring());
    if (format == TRUNC_IMPOSSIBLE)
    {
      thd->push_warning_wrong_value(Sql_condition::WARN_LEVEL_WARN, "TRUNC",
                                    res ? res->c_ptr() : "<NULL>");
      goto error;
    }
  }

  null_value= 0;
  switch (format)
  {
  case TRUNC_UNINIT:
  case TRUNC_IMPOSSIBLE:
    DBUG_ASSERT(0);
    goto error;
  case TRUNC_YEAR:
    ltime->month= 1;
    /* fall through */
  case TRUNC_MONTH:
    ltime->day= 1;
    /* fall through */
  case TRUNC_DAY:
    ltime->hour= ltime->minute= ltime->second= 0;
    ltime->second_part= 0;
    break;
  }
  return false;

  error:
  null_value= 1;
  return true;
}


/*
  Help functions for months_between()
  Note that by compiling with EXTENDED_MONTHS_BETWEEN MariaDB would
  take into account alse the time part when comparing dates.
*/

static ulonglong months_between_rank(const MYSQL_TIME *t)
{
  ulonglong days= (((t->year * 366LL) + t->month) * 31 + t->day);
#ifndef EXTENDED_MONTHS_BETWEEN
  return days;
#else
  ulonglong rank= (((((days*24 + t->hour) * 60) + t->minute) * 60 +
                    t->second)*1000000LL + t->second_part);
  return rank;
#endif /* EXTENDED_MONTHS_BETWEEN */
}


/*
  Get the fractional day based on hour, minute, second and
  fractional second. The return value is in milliseconds.
*/

static double fractional_day(const MYSQL_TIME *t)
{
  /* Normalize all components to the fractional part of the day */
  ulonglong milliseconds= (t->hour * 3600LL + t->minute * 60LL +
                           t->second)*1000;

#ifdef EXTENDED_MONTHS_BETWEEN
  milliseconds+= t->second_part / 1000;
#endif

  /*
    Normalize by 86400000
    (which is 24 hours * 60 minutes * 60 seconds * 1000 milliseconds)
  */
  return milliseconds / 86400000.0; // fractional day
}


/* Check if it's the last day of the month */
static inline int is_last_day(const MYSQL_TIME *ltime)
{
  uint last_day= calc_days_in_month(ltime->year, ltime->month);
  return ltime->day == last_day;
}

/*
  Calculate months_between() according to how Oracle does it.

  "If date1 is earlier than date2, then the result is negative.  If
  date1 and date2 are either the same days of the month or both last
  days of months, then the result is always an integer.  If not, then
  a fractional portion is added based on a 31-day month.

  One difference between the MariaDB and Oracle implementation is that
  MariaDB takes hours, minutes, seconds and fractional seconds into
  account when comparing dates when computing the fractional months.
*/

double Item_func_months_between::val_real()
{
  double frac;
  int invert = 1, months;
  ulonglong dt1, dt2;
  MYSQL_TIME ltime1, ltime2, *d1, *d2;
  THD *thd= current_thd;
  Datetime::Options opt(TIME_NO_ZEROS, thd);

  if (Datetime(thd, args[0], opt).copy_to_mysql_time(&ltime1) ||
      Datetime(thd, args[1], opt).copy_to_mysql_time(&ltime2))
  {
    null_value= 1;
    return 0.0;
  }
  null_value= 0;

  /* Get earlier time in d1 */
  d1= &ltime1;
  d2= &ltime2;

  dt1= months_between_rank(d1);
  dt2= months_between_rank(d2);

  if (dt1 < dt2)
  {
    invert= -1;
    swap_variables(MYSQL_TIME *, d1, d2);
  }

  /* Calculate months */
  months= (d1->year - d2->year) * 12 + (d1->month - d2->month);

  /*
    If days are the same or day is last day of the month they are
    regarded as equal
  */
  if (d1->day == d2->day || (is_last_day(d1) && is_last_day(d2)))
    return months * invert;

  double frac_d1, frac_d2;
  frac_d1= fractional_day(d1);
  frac_d2= fractional_day(d2);

  /* Compute fractional month using 31-day assumption */
  if (d1->day > d2->day)
    frac= (d1->day + frac_d1 - d2->day - frac_d2) / 31.0;
  else
  {
    months--;
    frac= (31 - d2->day - frac_d2 + d1->day + frac_d1) / 31.0;
  }

  return invert * (months + frac);
}
