text: #pragma once
  
  #include <DataTypes/DataTypesNumber.h>
  #include <DataTypes/DataTypeDate.h>
  #include <DataTypes/DataTypeDateTime.h>
  #include <DataTypes/DataTypeArray.h>
  #include <DataTypes/DataTypeString.h>
  
  #include <Columns/ColumnsNumber.h>
  #include <Columns/ColumnConst.h>
  #include <Columns/ColumnArray.h>
  #include <Columns/ColumnString.h>
  #include <Columns/ColumnFixedString.h>
  
  #include <Common/typeid_cast.h>
  
  #include <IO/WriteHelpers.h>
  
  #include <Functions/IFunction.h>
  #include <Functions/FunctionHelpers.h>
  
  #include <common/DateLUT.h>
  
  #include <Poco/String.h>
  
  #include <type_traits>
  
  
  namespace DB
  {
  
  namespace ErrorCodes
  {
      extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH;
  }
  
  /** Functions for working with date and time.
    *
    * toYear, toMonth, toDayOfMonth, toDayOfWeek, toHour, toMinute, toSecond,
    * toMonday, toStartOfMonth, toStartOfYear, toStartOfMinute, toStartOfFiveMinute, toStartOfFifteenMinutes
    * toStartOfHour, toTime,
    * now, today, yesterday
    * TODO: makeDate, makeDateTime
    *
    * (toDate - located in FunctionConversion.h file)
    *
    * Return types:
    *  toYear -> UInt16
    *  toMonth, toDayOfMonth, toDayOfWeek, toHour, toMinute, toSecond -> UInt8
    *  toMonday, toStartOfMonth, toStartOfYear -> Date
    *  toStartOfMinute, toStartOfHour, toTime, now -> DateTime
    *
    * And also:
    *
    * timeSlot(EventTime)
    * - rounds the time to half an hour.
    *
    * timeSlots(StartTime, Duration)
    * - for the time interval beginning at `StartTime` and continuing `Duration` seconds,
    *   returns an array of time points, consisting of rounding down to half an hour of points from this interval.
    *  For example, timeSlots(toDateTime('2012-01-01 12:20:00'), 600) = [toDateTime('2012-01-01 12:00:00'), toDateTime('2012-01-01 12:30:00')].
    *  This is necessary to search for hits that are part of the corresponding visit.
    */
  
  
  /// Determine working timezone either from optional argument with time zone name or from time zone in DateTime type of argument.
  std::string extractTimeZoneNameFromFunctionArguments(const ColumnsWithTypeAndName & arguments, size_t time_zone_arg_num, size_t datetime_arg_num);
  const DateLUTImpl & extractTimeZoneFromFunctionArguments(Block & block, const ColumnNumbers & arguments, size_t time_zone_arg_num, size_t datetime_arg_num);
  
  
  
  #define TIME_SLOT_SIZE 1800
  
  /** Transformations.
    * Represents two functions - from datetime (UInt32) and from date (UInt16).
    *
    * Also, the "factor transformation" F is defined for the T transformation.
    * This is a transformation of F such that its value identifies the region of monotonicity for T
    *  (for a fixed value of F, the transformation T is monotonic).
    *
    * Or, figuratively, if T is similar to taking the remainder of division, then F is similar to division.
    *
    * Example: for transformation T "get the day number in the month" (2015-02-03 -> 3),
    *  factor-transformation F is "round to the nearest month" (2015-02-03 -> 2015-02-01).
    */
  
  /// This factor transformation will say that the function is monotone everywhere.
  struct ZeroTransform
  {
      static inline UInt16 execute(UInt32, const DateLUTImpl &) { return 0; }
      static inline UInt16 execute(UInt16, const DateLUTImpl &) { return 0; }
  };
  
  struct ToDateImpl
  {
      static constexpr auto name = "toDate";
  
      static inline UInt16 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return UInt16(time_zone.toDayNum(t));
      }
      static inline UInt16 execute(UInt16 d, const DateLUTImpl &)
      {
          return d;
      }
  
      using FactorTransform = ZeroTransform;
  };
  
  struct ToStartOfDayImpl
  {
      static constexpr auto name = "toStartOfDay";
  
      static inline UInt32 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toDate(t);
      }
      static inline UInt32 execute(UInt16, const DateLUTImpl &)
      {
          throw Exception("Illegal type Date of argument for function toStartOfDay", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
      }
  
      using FactorTransform = ZeroTransform;
  };
  
  struct ToMondayImpl
  {
      static constexpr auto name = "toMonday";
  
      static inline UInt16 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toFirstDayNumOfWeek(time_zone.toDayNum(t));
      }
      static inline UInt16 execute(UInt16 d, const DateLUTImpl & time_zone)
      {
          return time_zone.toFirstDayNumOfWeek(DayNum_t(d));
      }
  
      using FactorTransform = ZeroTransform;
  };
  
  struct ToStartOfMonthImpl
  {
      static constexpr auto name = "toStartOfMonth";
  
      static inline UInt16 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toFirstDayNumOfMonth(time_zone.toDayNum(t));
      }
      static inline UInt16 execute(UInt16 d, const DateLUTImpl & time_zone)
      {
          return time_zone.toFirstDayNumOfMonth(DayNum_t(d));
      }
  
      using FactorTransform = ZeroTransform;
  };
  
  struct ToStartOfQuarterImpl
  {
      static constexpr auto name = "toStartOfQuarter";
  
      static inline UInt16 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toFirstDayNumOfQuarter(time_zone.toDayNum(t));
      }
      static inline UInt16 execute(UInt16 d, const DateLUTImpl & time_zone)
      {
          return time_zone.toFirstDayNumOfQuarter(DayNum_t(d));
      }
  
      using FactorTransform = ZeroTransform;
  };
  
  struct ToStartOfYearImpl
  {
      static constexpr auto name = "toStartOfYear";
  
      static inline UInt16 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toFirstDayNumOfYear(time_zone.toDayNum(t));
      }
      static inline UInt16 execute(UInt16 d, const DateLUTImpl & time_zone)
      {
          return time_zone.toFirstDayNumOfYear(DayNum_t(d));
      }
  
      using FactorTransform = ZeroTransform;
  };
  
  
  struct ToTimeImpl
  {
      static constexpr auto name = "toTime";
  
      /// When transforming to time, the date will be equated to 1970-01-02.
      static inline UInt32 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toTime(t) + 86400;
      }
  
      static inline UInt32 execute(UInt16, const DateLUTImpl &)
      {
          throw Exception("Illegal type Date of argument for function toTime", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
      }
  
      using FactorTransform = ToDateImpl;
  };
  
  struct ToStartOfMinuteImpl
  {
      static constexpr auto name = "toStartOfMinute";
  
      static inline UInt32 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toStartOfMinute(t);
      }
      static inline UInt32 execute(UInt16, const DateLUTImpl &)
      {
          throw Exception("Illegal type Date of argument for function toStartOfMinute", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
      }
  
      using FactorTransform = ZeroTransform;
  };
  
  struct ToStartOfFiveMinuteImpl
  {
      static constexpr auto name = "toStartOfFiveMinute";
  
      static inline UInt32 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toStartOfFiveMinute(t);
      }
      static inline UInt32 execute(UInt16, const DateLUTImpl &)
      {
          throw Exception("Illegal type Date of argument for function toStartOfFiveMinute", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
      }
  
      using FactorTransform = ZeroTransform;
  };
  
  struct ToStartOfFifteenMinutesImpl
  {
      static constexpr auto name = "toStartOfFifteenMinutes";
  
      static inline UInt32 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toStartOfFifteenMinutes(t);
      }
      static inline UInt32 execute(UInt16, const DateLUTImpl &)
      {
          throw Exception("Illegal type Date of argument for function toStartOfFifteenMinutes", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
      }
  
      using FactorTransform = ZeroTransform;
  };
  
  struct ToStartOfHourImpl
  {
      static constexpr auto name = "toStartOfHour";
  
      static inline UInt32 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toStartOfHour(t);
      }
  
      static inline UInt32 execute(UInt16, const DateLUTImpl &)
      {
          throw Exception("Illegal type Date of argument for function toStartOfHour", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
      }
  
      using FactorTransform = ZeroTransform;
  };
  
  struct ToYearImpl
  {
      static constexpr auto name = "toYear";
  
      static inline UInt16 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toYear(t);
      }
      static inline UInt16 execute(UInt16 d, const DateLUTImpl & time_zone)
      {
          return time_zone.toYear(DayNum_t(d));
      }
  
      using FactorTransform = ZeroTransform;
  };
  
  struct ToQuarterImpl
  {
      static constexpr auto name = "toQuarter";
  
      static inline UInt8 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toQuarter(t);
      }
      static inline UInt8 execute(UInt16 d, const DateLUTImpl & time_zone)
      {
          return time_zone.toQuarter(DayNum_t(d));
      }
  
      using FactorTransform = ToStartOfYearImpl;
  };
  
  struct ToMonthImpl
  {
      static constexpr auto name = "toMonth";
  
      static inline UInt8 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toMonth(t);
      }
      static inline UInt8 execute(UInt16 d, const DateLUTImpl & time_zone)
      {
          return time_zone.toMonth(DayNum_t(d));
      }
  
      using FactorTransform = ToStartOfYearImpl;
  };
  
  struct ToDayOfMonthImpl
  {
      static constexpr auto name = "toDayOfMonth";
  
      static inline UInt8 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toDayOfMonth(t);
      }
      static inline UInt8 execute(UInt16 d, const DateLUTImpl & time_zone)
      {
          return time_zone.toDayOfMonth(DayNum_t(d));
      }
  
      using FactorTransform = ToStartOfMonthImpl;
  };
  
  struct ToDayOfWeekImpl
  {
      static constexpr auto name = "toDayOfWeek";
  
      static inline UInt8 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toDayOfWeek(t);
      }
      static inline UInt8 execute(UInt16 d, const DateLUTImpl & time_zone)
      {
          return time_zone.toDayOfWeek(DayNum_t(d));
      }
  
      using FactorTransform = ToMondayImpl;
  };
  
  struct ToHourImpl
  {
      static constexpr auto name = "toHour";
  
      static inline UInt8 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toHour(t);
      }
  
      static inline UInt8 execute(UInt16, const DateLUTImpl &)
      {
          throw Exception("Illegal type Date of argument for function toHour", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
      }
  
      using FactorTransform = ToDateImpl;
  };
  
  struct ToMinuteImpl
  {
      static constexpr auto name = "toMinute";
  
      static inline UInt8 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toMinute(t);
      }
      static inline UInt8 execute(UInt16, const DateLUTImpl &)
      {
          throw Exception("Illegal type Date of argument for function toMinute", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
      }
  
      using FactorTransform = ToStartOfHourImpl;
  };
  
  struct ToSecondImpl
  {
      static constexpr auto name = "toSecond";
  
      static inline UInt8 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toSecond(t);
      }
      static inline UInt8 execute(UInt16, const DateLUTImpl &)
      {
          throw Exception("Illegal type Date of argument for function toSecond", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
      }
  
      using FactorTransform = ToStartOfMinuteImpl;
  };
  
  struct ToRelativeYearNumImpl
  {
      static constexpr auto name = "toRelativeYearNum";
  
      static inline UInt16 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toYear(t);
      }
      static inline UInt16 execute(UInt16 d, const DateLUTImpl & time_zone)
      {
          return time_zone.toYear(DayNum_t(d));
      }
  
      using FactorTransform = ZeroTransform;
  };
  
  struct ToRelativeQuarterNumImpl
  {
      static constexpr auto name = "toRelativeQuarterNum";
  
      static inline UInt16 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toRelativeQuarterNum(t);
      }
      static inline UInt16 execute(UInt16 d, const DateLUTImpl & time_zone)
      {
          return time_zone.toRelativeQuarterNum(DayNum_t(d));
      }
  
      using FactorTransform = ZeroTransform;
  };
  
  struct ToRelativeMonthNumImpl
  {
      static constexpr auto name = "toRelativeMonthNum";
  
      static inline UInt16 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toRelativeMonthNum(t);
      }
      static inline UInt16 execute(UInt16 d, const DateLUTImpl & time_zone)
      {
          return time_zone.toRelativeMonthNum(DayNum_t(d));
      }
  
      using FactorTransform = ZeroTransform;
  };
  
  struct ToRelativeWeekNumImpl
  {
      static constexpr auto name = "toRelativeWeekNum";
  
      static inline UInt16 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toRelativeWeekNum(t);
      }
      static inline UInt16 execute(UInt16 d, const DateLUTImpl & time_zone)
      {
          return time_zone.toRelativeWeekNum(DayNum_t(d));
      }
  
      using FactorTransform = ZeroTransform;
  };
  
  struct ToRelativeDayNumImpl
  {
      static constexpr auto name = "toRelativeDayNum";
  
      static inline UInt16 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toDayNum(t);
      }
      static inline UInt16 execute(UInt16 d, const DateLUTImpl &)
      {
          return static_cast<DayNum_t>(d);
      }
  
      using FactorTransform = ZeroTransform;
  };
  
  
  struct ToRelativeHourNumImpl
  {
      static constexpr auto name = "toRelativeHourNum";
  
      static inline UInt32 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toRelativeHourNum(t);
      }
      static inline UInt32 execute(UInt16 d, const DateLUTImpl & time_zone)
      {
          return time_zone.toRelativeHourNum(DayNum_t(d));
      }
  
      using FactorTransform = ZeroTransform;
  };
  
  struct ToRelativeMinuteNumImpl
  {
      static constexpr auto name = "toRelativeMinuteNum";
  
      static inline UInt32 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toRelativeMinuteNum(t);
      }
      static inline UInt32 execute(UInt16 d, const DateLUTImpl & time_zone)
      {
          return time_zone.toRelativeMinuteNum(DayNum_t(d));
      }
  
      using FactorTransform = ZeroTransform;
  };
  
  struct ToRelativeSecondNumImpl
  {
      static constexpr auto name = "toRelativeSecondNum";
  
      static inline UInt32 execute(UInt32 t, const DateLUTImpl &)
      {
          return t;
      }
      static inline UInt32 execute(UInt16 d, const DateLUTImpl & time_zone)
      {
          return time_zone.fromDayNum(DayNum_t(d));
      }
  
      using FactorTransform = ZeroTransform;
  };
  
  struct ToYYYYMMImpl
  {
      static constexpr auto name = "toYYYYMM";
  
      static inline UInt32 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toNumYYYYMM(t);
      }
      static inline UInt32 execute(UInt16 d, const DateLUTImpl & time_zone)
      {
          return time_zone.toNumYYYYMM(static_cast<DayNum_t>(d));
      }
  
      using FactorTransform = ZeroTransform;
  };
  
  struct ToYYYYMMDDImpl
  {
      static constexpr auto name = "toYYYYMMDD";
  
      static inline UInt32 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toNumYYYYMMDD(t);
      }
      static inline UInt32 execute(UInt16 d, const DateLUTImpl & time_zone)
      {
          return time_zone.toNumYYYYMMDD(static_cast<DayNum_t>(d));
      }
  
      using FactorTransform = ZeroTransform;
  };
  
  struct ToYYYYMMDDhhmmssImpl
  {
      static constexpr auto name = "toYYYYMMDDhhmmss";
  
      static inline UInt64 execute(UInt32 t, const DateLUTImpl & time_zone)
      {
          return time_zone.toNumYYYYMMDDhhmmss(t);
      }
      static inline UInt64 execute(UInt16 d, const DateLUTImpl & time_zone)
      {
          return time_zone.toNumYYYYMMDDhhmmss(time_zone.toDate(static_cast<DayNum_t>(d)));
      }
  
      using FactorTransform = ZeroTransform;
  };
  
  
  template <typename FromType, typename ToType, typename Transform>
  struct Transformer
  {
      static void vector(const PaddedPODArray<FromType> & vec_from, PaddedPODArray<ToType> & vec_to, const DateLUTImpl & time_zone)
      {
          size_t size = vec_from.size();
          vec_to.resize(size);
  
          for (size_t i = 0; i < size; ++i)
              vec_to[i] = Transform::execute(vec_from[i], time_zone);
      }
  };
  
  
  template <typename FromType, typename ToType, typename Transform>
  struct DateTimeTransformImpl
  {
      static void execute(Block & block, const ColumnNumbers & arguments, size_t result)
      {
          using Op = Transformer<FromType, ToType, Transform>;
  
          const DateLUTImpl & time_zone = extractTimeZoneFromFunctionArguments(block, arguments, 1, 0);
  
          const ColumnPtr source_col = block.getByPosition(arguments[0]).column;
          if (const auto * sources = checkAndGetColumn<ColumnVector<FromType>>(source_col.get()))
          {
              auto col_to = ColumnVector<ToType>::create();
              Op::vector(sources->getData(), col_to->getData(), time_zone);
              block.getByPosition(result).column = std::move(col_to);
          }
          else
          {
              throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName()
                  + " of first argument of function " + Transform::name,
                  ErrorCodes::ILLEGAL_COLUMN);
          }
      }
  };
  
  
  template <typename ToDataType, typename Transform>
  class FunctionDateOrDateTimeToSomething : public IFunction
  {
  public:
      static constexpr auto name = Transform::name;
      static FunctionPtr create(const Context &) { return std::make_shared<FunctionDateOrDateTimeToSomething>(); };
  
      String getName() const override
      {
          return name;
      }
  
      bool isVariadic() const override { return true; }
      size_t getNumberOfArguments() const override { return 0; }
  
      void getReturnTypeAndPrerequisitesImpl(
          const ColumnsWithTypeAndName & arguments,
          DataTypePtr & out_return_type,
          std::vector<ExpressionAction> &) override
      {
          if (arguments.size() == 1)
          {
              if (!arguments[0].type->isDateOrDateTime())
                  throw Exception{
                      "Illegal type " + arguments[0].type->getName() + " of argument of function " + getName() +
                      ". Should be a date or a date with time", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT};
          }
          else if (arguments.size() == 2)
          {
              if (!checkDataType<DataTypeDateTime>(arguments[0].type.get())
                  || !checkDataType<DataTypeString>(arguments[1].type.get()))
                  throw Exception{
                      "Function " + getName() + " supports 1 or 2 arguments. The 1st argument "
                      "must be of type Date or DateTime. The 2nd argument (optional) must be "
                      "a constant string with timezone name. The timezone argument is allowed "
                      "only when the 1st argument has the type DateTime",
                      ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT};
          }
          else
              throw Exception("Number of arguments for function " + getName() + " doesn't match: passed "
                  + toString(arguments.size()) + ", should be 1 or 2",
                  ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
  
          /// For DateTime, if time zone is specified, attach it to type.
          if (std::is_same_v<ToDataType, DataTypeDateTime>)
              out_return_type = std::make_shared<DataTypeDateTime>(extractTimeZoneNameFromFunctionArguments(arguments, 1, 0));
          else
              out_return_type = std::make_shared<ToDataType>();
      }
  
      bool useDefaultImplementationForConstants() const override { return true; }
      ColumnNumbers getArgumentsThatAreAlwaysConstant() const override { return {1}; }
  
      void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result) override
      {
          const IDataType * from_type = block.getByPosition(arguments[0]).type.get();
  
          if (checkDataType<DataTypeDate>(from_type))
              DateTimeTransformImpl<DataTypeDate::FieldType, typename ToDataType::FieldType, Transform>::execute(block, arguments, result);
          else if (checkDataType<DataTypeDateTime>(from_type))
              DateTimeTransformImpl<DataTypeDateTime::FieldType, typename ToDataType::FieldType, Transform>::execute(block, arguments, result);
          else
              throw Exception("Illegal type " + block.getByPosition(arguments[0]).type->getName() + " of argument of function " + getName(),
                  ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
      }
  
  
      bool hasInformationAboutMonotonicity() const override
      {
          return true;
      }
  
      Monotonicity getMonotonicityForRange(const IDataType & type, const Field & left, const Field & right) const override
      {
          IFunction::Monotonicity is_monotonic { true };
          IFunction::Monotonicity is_not_monotonic;
  
          if (std::is_same_v<typename Transform::FactorTransform, ZeroTransform>)
          {
              is_monotonic.is_always_monotonic = true;
              return is_monotonic;
          }
  
          /// This method is called only if the function has one argument. Therefore, we do not care about the non-local time zone.
          const DateLUTImpl & date_lut = DateLUT::instance();
  
          if (left.isNull() || right.isNull())
              return is_not_monotonic;
  
          /// The function is monotonous on the [left, right] segment, if the factor transformation returns the same values for them.
  
          if (checkAndGetDataType<DataTypeDate>(&type))
          {
              return Transform::FactorTransform::execute(UInt16(left.get<UInt64>()), date_lut)
                  == Transform::FactorTransform::execute(UInt16(right.get<UInt64>()), date_lut)
                  ? is_monotonic : is_not_monotonic;
          }
          else
          {
              return Transform::FactorTransform::execute(UInt32(left.get<UInt64>()), date_lut)
                  == Transform::FactorTransform::execute(UInt32(right.get<UInt64>()), date_lut)
                  ? is_monotonic : is_not_monotonic;
          }
      }
  };
  
  
  struct AddSecondsImpl
  {
      static constexpr auto name = "addSeconds";
  
      static inline UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl &)
      {
          return t + delta;
      }
  
      static inline UInt32 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone)
      {
          return time_zone.fromDayNum(DayNum_t(d)) + delta;
      }
  };
  
  struct AddMinutesImpl
  {
      static constexpr auto name = "addMinutes";
  
      static inline UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl &)
      {
          return t + delta * 60;
      }
  
      static inline UInt32 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone)
      {
          return time_zone.fromDayNum(DayNum_t(d)) + delta * 60;
      }
  };
  
  struct AddHoursImpl
  {
      static constexpr auto name = "addHours";
  
      static inline UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl &)
      {
          return t + delta * 3600;
      }
  
      static inline UInt32 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone)
      {
          return time_zone.fromDayNum(DayNum_t(d)) + delta * 3600;
      }
  };
  
  struct AddDaysImpl
  {
      static constexpr auto name = "addDays";
  
      static inline UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl & time_zone)
      {
          return time_zone.addDays(t, delta);
      }
  
      static inline UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl &)
      {
          return d + delta;
      }
  };
  
  struct AddWeeksImpl
  {
      static constexpr auto name = "addWeeks";
  
      static inline UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl & time_zone)
      {
          return time_zone.addWeeks(t, delta);
      }
  
      static inline UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl &)
      {
          return d + delta * 7;
      }
  };
  
  struct AddMonthsImpl
  {
      static constexpr auto name = "addMonths";
  
      static inline UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl & time_zone)
      {
          return time_zone.addMonths(t, delta);
      }
  
      static inline UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone)
      {
          return time_zone.addMonths(DayNum_t(d), delta);
      }
  };
  
  struct AddYearsImpl
  {
      static constexpr auto name = "addYears";
  
      static inline UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl & time_zone)
      {
          return time_zone.addYears(t, delta);
      }
  
      static inline UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone)
      {
          return time_zone.addYears(DayNum_t(d), delta);
      }
  };
  
  
  template <typename Transform>
  struct SubtractIntervalImpl
  {
      static inline UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl & time_zone)
      {
          return Transform::execute(t, -delta, time_zone);
      }
  
      static inline UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone)
      {
          return Transform::execute(d, -delta, time_zone);
      }
  };
  
  struct SubtractSecondsImpl : SubtractIntervalImpl<AddSecondsImpl> { static constexpr auto name = "subtractSeconds"; };
  struct SubtractMinutesImpl : SubtractIntervalImpl<AddMinutesImpl> { static constexpr auto name = "subtractMinutes"; };
  struct SubtractHoursImpl : SubtractIntervalImpl<AddHoursImpl> { static constexpr auto name = "subtractHours"; };
  struct SubtractDaysImpl : SubtractIntervalImpl<AddDaysImpl> { static constexpr auto name = "subtractDays"; };
  struct SubtractWeeksImpl : SubtractIntervalImpl<AddWeeksImpl> { static constexpr auto name = "subtractWeeks"; };
  struct SubtractMonthsImpl : SubtractIntervalImpl<AddMonthsImpl> { static constexpr auto name = "subtractMonths"; };
  struct SubtractYearsImpl : SubtractIntervalImpl<AddYearsImpl> { static constexpr auto name = "subtractYears"; };
  
  
  template <typename FromType, typename ToType, typename Transform>
  struct Adder
  {
      static void vector_vector(const PaddedPODArray<FromType> & vec_from, PaddedPODArray<ToType> & vec_to, const IColumn & delta, const DateLUTImpl & time_zone)
      {
          size_t size = vec_from.size();
          vec_to.resize(size);
  
          for (size_t i = 0; i < size; ++i)
              vec_to[i] = Transform::execute(vec_from[i], delta.getInt(i), time_zone);
      }
  
      static void vector_constant(const PaddedPODArray<FromType> & vec_from, PaddedPODArray<ToType> & vec_to, Int64 delta, const DateLUTImpl & time_zone)
      {
          size_t size = vec_from.size();
          vec_to.resize(size);
  
          for (size_t i = 0; i < size; ++i)
              vec_to[i] = Transform::execute(vec_from[i], delta, time_zone);
      }
  
      static void constant_vector(const FromType & from, PaddedPODArray<ToType> & vec_to, const IColumn & delta, const DateLUTImpl & time_zone)
      {
          size_t size = delta.size();
          vec_to.resize(size);
  
          for (size_t i = 0; i < size; ++i)
              vec_to[i] = Transform::execute(from, delta.getInt(i), time_zone);
      }
  };
  
  
  template <typename FromType, typename Transform>
  struct DateTimeAddIntervalImpl
  {
      static void execute(Block & block, const ColumnNumbers & arguments, size_t result)
      {
          using ToType = decltype(Transform::execute(FromType(), 0, std::declval<DateLUTImpl>()));
          using Op = Adder<FromType, ToType, Transform>;
  
          const DateLUTImpl & time_zone = extractTimeZoneFromFunctionArguments(block, arguments, 2, 0);
  
          const ColumnPtr source_col = block.getByPosition(arguments[0]).column;
  
          if (const auto * sources = checkAndGetColumn<ColumnVector<FromType>>(source_col.get()))
          {
              auto col_to = ColumnVector<ToType>::create();
  
              const IColumn & delta_column = *block.getByPosition(arguments[1]).column;
  
              if (const auto * delta_const_column = typeid_cast<const ColumnConst *>(&delta_column))
                  Op::vector_constant(sources->getData(), col_to->getData(), delta_const_column->getField().get<Int64>(), time_zone);
              else
                  Op::vector_vector(sources->getData(), col_to->getData(), delta_column, time_zone);
  
              block.getByPosition(result).column = std::move(col_to);
          }
          else if (const auto * sources = checkAndGetColumnConst<ColumnVector<FromType>>(source_col.get()))
          {
              auto col_to = ColumnVector<ToType>::create();
              Op::constant_vector(sources->template getValue<FromType>(), col_to->getData(), *block.getByPosition(arguments[1]).column, time_zone);
              block.getByPosition(result).column = std::move(col_to);
          }
          else
          {
              throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName()
                  + " of first argument of function " + Transform::name,
                  ErrorCodes::ILLEGAL_COLUMN);
          }
      }
  };
  
  
  template <typename Transform>
  class FunctionDateOrDateTimeAddInterval : public IFunction
  {
  public:
      static constexpr auto name = Transform::name;
      static FunctionPtr create(const Context &) { return std::make_shared<FunctionDateOrDateTimeAddInterval>(); };
  
      String getName() const override
      {
          return name;
      }
  
      bool isVariadic() const override { return true; }
      size_t getNumberOfArguments() const override { return 0; }
  
      void getReturnTypeAndPrerequisitesImpl(
          const ColumnsWithTypeAndName & arguments,
          DataTypePtr & out_return_type,
          std::vector<ExpressionAction> &) override
      {
          if (arguments.size() != 2 && arguments.size() != 3)
              throw Exception("Number of arguments for function " + getName() + " doesn't match: passed "
                  + toString(arguments.size()) + ", should be 2 or 3",
                  ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
  
          if (!arguments[1].type->isNumber())
              throw Exception("Second argument for function " + getName() + " (delta) must be number",
                  ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
  
          if (arguments.size() == 2)
          {
              if (!arguments[0].type->isDateOrDateTime())
                  throw Exception{
                      "Illegal type " + arguments[0].type->getName() + " of argument of function " + getName() +
                      ". Should be a date or a date with time", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT};
          }
          else
          {
              if (!checkDataType<DataTypeDateTime>(arguments[0].type.get())
                  || !checkDataType<DataTypeString>(arguments[2].type.get()))
                  throw Exception{
                      "Function " + getName() + " supports 2 or 3 arguments. The 1st argument "
                      "must be of type Date or DateTime. The 2nd argument must be number. "
                      "The 3rd argument (optional) must be "
                      "a constant string with timezone name. The timezone argument is allowed "
                      "only when the 1st argument has the type DateTime",
                      ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT};
          }
  
          if (checkDataType<DataTypeDate>(arguments[0].type.get()))
          {
              if (std::is_same_v<decltype(Transform::execute(DataTypeDate::FieldType(), 0, std::declval<DateLUTImpl>())), UInt16>)
                  out_return_type = std::make_shared<DataTypeDate>();
              else
                  out_return_type = std::make_shared<DataTypeDateTime>(extractTimeZoneNameFromFunctionArguments(arguments, 2, 0));
          }
          else
          {
              if (std::is_same_v<decltype(Transform::execute(DataTypeDateTime::FieldType(), 0, std::declval<DateLUTImpl>())), UInt16>)
                  out_return_type = std::make_shared<DataTypeDate>();
              else
                  out_return_type = std::make_shared<DataTypeDateTime>(extractTimeZoneNameFromFunctionArguments(arguments, 2, 0));
          }
      }
  
      bool useDefaultImplementationForConstants() const override { return true; }
      ColumnNumbers getArgumentsThatAreAlwaysConstant() const override { return {2}; }
  
      void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result) override
      {
          const IDataType * from_type = block.getByPosition(arguments[0]).type.get();
  
          if (checkDataType<DataTypeDate>(from_type))
              DateTimeAddIntervalImpl<DataTypeDate::FieldType, Transform>::execute(block, arguments, result);
          else if (checkDataType<DataTypeDateTime>(from_type))
              DateTimeAddIntervalImpl<DataTypeDateTime::FieldType, Transform>::execute(block, arguments, result);
          else
              throw Exception("Illegal type " + block.getByPosition(arguments[0]).type->getName() + " of argument of function " + getName(),
                  ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
      }
  };
  
  
  /** dateDiff('unit', t1, t2, [timezone])
    * t1 and t2 can be Date or DateTime
    *
    * If timezone is specified, it applied to both arguments.
    * If not, timezones from datatypes t1 and t2 are used.
    * If that timezones are not the same, the result is unspecified.
    *
    * Timezone matters because days can have different length.
    */
  class FunctionDateDiff : public IFunction
  {
  public:
      static constexpr auto name = "dateDiff";
      static FunctionPtr create(const Context &) { return std::make_shared<FunctionDateDiff>(); };
  
      String getName() const override
      {
          return name;
      }
  
      bool isVariadic() const override { return true; }
      size_t getNumberOfArguments() const override { return 0; }
  
      DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override
      {
          if (arguments.size() != 3 && arguments.size() != 4)
              throw Exception("Number of arguments for function " + getName() + " doesn't match: passed "
                  + toString(arguments.size()) + ", should be 3 or 4",
                  ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
  
          if (!arguments[0]->isString())
              throw Exception("First argument for function " + getName() + " (unit) must be String",
                  ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
  
          if (!arguments[1]->isDateOrDateTime())
              throw Exception("Second argument for function " + getName() + " must be Date or DateTime",
                  ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
  
          if (!arguments[2]->isDateOrDateTime())
              throw Exception("Third argument for function " + getName() + " must be Date or DateTime",
                  ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
  
          if (arguments.size() == 4 && !arguments[3]->isString())
              throw Exception("Fourth argument for function " + getName() + " (timezone) must be String",
                  ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
  
          return std::make_shared<DataTypeInt64>();
      }
  
      bool useDefaultImplementationForConstants() const override { return true; }
      ColumnNumbers getArgumentsThatAreAlwaysConstant() const override { return {0, 3}; }
  
      void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result) override
      {
          auto * unit_column = checkAndGetColumnConst<ColumnString>(block.getByPosition(arguments[0]).column.get());
          if (!unit_column)
              throw Exception("First argument for function " + getName() + " must be constant String", ErrorCodes::ILLEGAL_COLUMN);
  
          String unit = Poco::toLower(unit_column->getValue<String>());
  
          const IColumn & x = *block.getByPosition(arguments[1]).column;
          const IColumn & y = *block.getByPosition(arguments[2]).column;
  
          size_t rows = block.rows();
          auto res = ColumnInt64::create(rows);
  
          const DateLUTImpl & timezone_x = extractTimeZoneFromFunctionArguments(block, arguments, 3, 1);
          const DateLUTImpl & timezone_y = extractTimeZoneFromFunctionArguments(block, arguments, 3, 2);
  
          if (unit == "year" || unit == "yy" || unit == "yyyy")
              dispatchForColumns<ToRelativeYearNumImpl>(x, y, timezone_x, timezone_y, res->getData());
          else if (unit == "quarter" || unit == "qq" || unit == "q")
              dispatchForColumns<ToRelativeQuarterNumImpl>(x, y, timezone_x, timezone_y, res->getData());
          else if (unit == "month" || unit == "mm" || unit == "m")
              dispatchForColumns<ToRelativeMonthNumImpl>(x, y, timezone_x, timezone_y, res->getData());
          else if (unit == "week" || unit == "wk" || unit == "ww")
              dispatchForColumns<ToRelativeWeekNumImpl>(x, y, timezone_x, timezone_y, res->getData());
          else if (unit == "day" || unit == "dd" || unit == "d")
              dispatchForColumns<ToRelativeDayNumImpl>(x, y, timezone_x, timezone_y, res->getData());
          else if (unit == "hour" || unit == "hh")
              dispatchForColumns<ToRelativeHourNumImpl>(x, y, timezone_x, timezone_y, res->getData());
          else if (unit == "minute" || unit == "mi" || unit == "n")
              dispatchForColumns<ToRelativeMinuteNumImpl>(x, y, timezone_x, timezone_y, res->getData());
          else if (unit == "second" || unit == "ss" || unit == "s")
              dispatchForColumns<ToRelativeSecondNumImpl>(x, y, timezone_x, timezone_y, res->getData());
          else
              throw Exception("Function " + getName() + " does not support '" + unit + "' unit", ErrorCodes::BAD_ARGUMENTS);
  
          block.getByPosition(result).column = std::move(res);
      }
  
  private:
      template <typename Transform>
      void dispatchForColumns(
          const IColumn & x, const IColumn & y,
          const DateLUTImpl & timezone_x, const DateLUTImpl & timezone_y,
          ColumnInt64::Container & result)
      {
          if (auto * x_vec = checkAndGetColumn<ColumnUInt16>(&x))
              dispatchForSecondColumn<Transform>(*x_vec, y, timezone_x, timezone_y, result);
          else if (auto * x_vec = checkAndGetColumn<ColumnUInt32>(&x))
              dispatchForSecondColumn<Transform>(*x_vec, y, timezone_x, timezone_y, result);
          else if (auto * x_const = checkAndGetColumnConst<ColumnUInt16>(&x))
              dispatchConstForSecondColumn<Transform>(x_const->getValue<UInt16>(), y, timezone_x, timezone_y, result);
          else if (auto * x_const = checkAndGetColumnConst<ColumnUInt32>(&x))
              dispatchConstForSecondColumn<Transform>(x_const->getValue<UInt32>(), y, timezone_x, timezone_y, result);
          else
              throw Exception("Illegal column for first argument of function " + getName() + ", must be Date or DateTime", ErrorCodes::ILLEGAL_COLUMN);
      }
  
      template <typename Transform, typename T1>
      void dispatchForSecondColumn(
          const ColumnVector<T1> & x, const IColumn & y,
          const DateLUTImpl & timezone_x, const DateLUTImpl & timezone_y,
          ColumnInt64::Container & result)
      {
          if (auto * y_vec = checkAndGetColumn<ColumnUInt16>(&y))
              vector_vector<Transform>(x, *y_vec, timezone_x, timezone_y, result);
          else if (auto * y_vec = checkAndGetColumn<ColumnUInt32>(&y))
              vector_vector<Transform>(x, *y_vec, timezone_x, timezone_y, result);
          else if (auto * y_const = checkAndGetColumnConst<ColumnUInt16>(&y))
              vector_constant<Transform>(x, y_const->getValue<UInt16>(), timezone_x, timezone_y, result);
          else if (auto * y_const = checkAndGetColumnConst<ColumnUInt32>(&y))
              vector_constant<Transform>(x, y_const->getValue<UInt32>(), timezone_x, timezone_y, result);
          else
              throw Exception("Illegal column for second argument of function " + getName() + ", must be Date or DateTime", ErrorCodes::ILLEGAL_COLUMN);
      }
  
      template <typename Transform, typename T1>
      void dispatchConstForSecondColumn(
          T1 x, const IColumn & y,
          const DateLUTImpl & timezone_x, const DateLUTImpl & timezone_y,
          ColumnInt64::Container & result)
      {
          if (auto * y_vec = checkAndGetColumn<ColumnUInt16>(&y))
              constant_vector<Transform>(x, *y_vec, timezone_x, timezone_y, result);
          else if (auto * y_vec = checkAndGetColumn<ColumnUInt32>(&y))
              constant_vector<Transform>(x, *y_vec, timezone_x, timezone_y, result);
          else
              throw Exception("Illegal column for second argument of function " + getName() + ", must be Date or DateTime", ErrorCodes::ILLEGAL_COLUMN);
      }
  
      template <typename Transform, typename T1, typename T2>
      void vector_vector(
          const ColumnVector<T1> & x, const ColumnVector<T2> & y,
          const DateLUTImpl & timezone_x, const DateLUTImpl & timezone_y,
          ColumnInt64::Container & result)
      {
          const auto & x_data = x.getData();
          const auto & y_data = y.getData();
          for (size_t i = 0, size = x.size(); i < size; ++i)
              result[i] = calculate<Transform>(x_data[i], y_data[i], timezone_x, timezone_y);
      }
  
      template <typename Transform, typename T1, typename T2>
      void vector_constant(
          const ColumnVector<T1> & x, T2 y,
          const DateLUTImpl & timezone_x, const DateLUTImpl & timezone_y,
          ColumnInt64::Container & result)
      {
          const auto & x_data = x.getData();
          for (size_t i = 0, size = x.size(); i < size; ++i)
              result[i] = calculate<Transform>(x_data[i], y, timezone_x, timezone_y);
      }
  
      template <typename Transform, typename T1, typename T2>
      void constant_vector(
          T1 x, const ColumnVector<T2> & y,
          const DateLUTImpl & timezone_x, const DateLUTImpl & timezone_y,
          ColumnInt64::Container & result)
      {
          const auto & y_data = y.getData();
          for (size_t i = 0, size = y.size(); i < size; ++i)
              result[i] = calculate<Transform>(x, y_data[i], timezone_x, timezone_y);
      }
  
      template <typename Transform, typename T1, typename T2>
      Int64 calculate(T1 x, T2 y, const DateLUTImpl & timezone_x, const DateLUTImpl & timezone_y)
      {
          return Int64(Transform::execute(y, timezone_y))
               - Int64(Transform::execute(x, timezone_x));
      }
  };
  
  
  /// Get the current time. (It is a constant, it is evaluated once for the entire query.)
  class FunctionNow : public IFunction
  {
  public:
      static constexpr auto name = "now";
      static FunctionPtr create(const Context &) { return std::make_shared<FunctionNow>(); };
  
      String getName() const override
      {
          return name;
      }
  
      size_t getNumberOfArguments() const override { return 0; }
  
      DataTypePtr getReturnTypeImpl(const DataTypes & /*arguments*/) const override
      {
          return std::make_shared<DataTypeDateTime>();
      }
  
      void executeImpl(Block & block, const ColumnNumbers & /*arguments*/, size_t result) override
      {
          block.getByPosition(result).column = DataTypeUInt32().createColumnConst(
              block.rows(),
              static_cast<UInt64>(time(nullptr)));
      }
  };
  
  
  class FunctionToday : public IFunction
  {
  public:
      static constexpr auto name = "today";
      static FunctionPtr create(const Context &) { return std::make_shared<FunctionToday>(); };
  
      String getName() const override
      {
          return name;
      }
  
      size_t getNumberOfArguments() const override { return 0; }
  
      DataTypePtr getReturnTypeImpl(const DataTypes & /*arguments*/) const override
      {
          return std::make_shared<DataTypeDate>();
      }
  
      void executeImpl(Block & block, const ColumnNumbers & /*arguments*/, size_t result) override
      {
          block.getByPosition(result).column = DataTypeUInt16().createColumnConst(
              block.rows(),
              UInt64(DateLUT::instance().toDayNum(time(nullptr))));
      }
  };
  
  
  class FunctionYesterday : public IFunction
  {
  public:
      static constexpr auto name = "yesterday";
      static FunctionPtr create(const Context &) { return std::make_shared<FunctionYesterday>(); };
  
      String getName() const override
      {
          return name;
      }
  
      size_t getNumberOfArguments() const override { return 0; }
  
      DataTypePtr getReturnTypeImpl(const DataTypes & /*arguments*/) const override
      {
          return std::make_shared<DataTypeDate>();
      }
  
      void executeImpl(Block & block, const ColumnNumbers & /*arguments*/, size_t result) override
      {
          block.getByPosition(result).column = DataTypeUInt16().createColumnConst(
              block.rows(),
              UInt64(DateLUT::instance().toDayNum(time(nullptr)) - 1));
      }
  };
  
  
  /// Just changes time zone information for data type. The calculation is free.
  class FunctionToTimeZone : public IFunction
  {
  public:
      static constexpr auto name = "toTimeZone";
      static FunctionPtr create(const Context &) { return std::make_shared<FunctionToTimeZone>(); };
  
      String getName() const override
      {
          return name;
      }
  
      size_t getNumberOfArguments() const override { return 2; }
  
      void getReturnTypeAndPrerequisitesImpl(
          const ColumnsWithTypeAndName & arguments,
          DataTypePtr & out_return_type,
          std::vector<ExpressionAction> &) override
      {
          if (arguments.size() != 2)
              throw Exception("Number of arguments for function " + getName() + " doesn't match: passed "
                  + toString(arguments.size()) + ", should be 2",
                  ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
  
          if (!checkDataType<DataTypeDateTime>(arguments[0].type.get()))
              throw Exception{
                  "Illegal type " + arguments[0].type->getName() + " of argument of function " + getName() +
                  ". Should be DateTime", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT};
  
          String time_zone_name = extractTimeZoneNameFromFunctionArguments(arguments, 1, 0);
          out_return_type = std::make_shared<DataTypeDateTime>(time_zone_name);
      }
  
      void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result) override
      {
          block.getByPosition(result).column = block.getByPosition(arguments[0]).column;
      }
  };
  
  
  class FunctionTimeSlot : public IFunction
  {
  public:
      static constexpr auto name = "timeSlot";
      static FunctionPtr create(const Context &) { return std::make_shared<FunctionTimeSlot>(); };
  
      String getName() const override
      {
          return name;
      }
  
      size_t getNumberOfArguments() const override { return 1; }
  
      DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override
      {
          if (!checkDataType<DataTypeDateTime>(arguments[0].get()))
              throw Exception("Illegal type " + arguments[0]->getName() + " of first argument of function " + getName() + ". Must be DateTime.",
                  ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
  
          return std::make_shared<DataTypeDateTime>();
      }
  
      bool useDefaultImplementationForConstants() const override { return true; }
  
      void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result) override
      {
          if (const ColumnUInt32 * times = typeid_cast<const ColumnUInt32 *>(block.getByPosition(arguments[0]).column.get()))
          {
              auto res = ColumnUInt32::create();
              ColumnUInt32::Container & res_vec = res->getData();
              const ColumnUInt32::Container & vec = times->getData();
  
              size_t size = vec.size();
              res_vec.resize(size);
  
              for (size_t i = 0; i < size; ++i)
                  res_vec[i] = vec[i] / TIME_SLOT_SIZE * TIME_SLOT_SIZE;
  
              block.getByPosition(result).column = std::move(res);
          }
          else
              throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName()
                      + " of argument of function " + getName(),
                  ErrorCodes::ILLEGAL_COLUMN);
      }
  };
  
  
  template <typename DurationType>
  struct TimeSlotsImpl
  {
      static void vector_vector(
          const PaddedPODArray<UInt32> & starts, const PaddedPODArray<DurationType> & durations,
          PaddedPODArray<UInt32> & result_values, ColumnArray::Offsets & result_offsets)
      {
          size_t size = starts.size();
  
          result_offsets.resize(size);
          result_values.reserve(size);
  
          ColumnArray::Offset current_offset = 0;
          for (size_t i = 0; i < size; ++i)
          {
              for (UInt32 value = starts[i] / TIME_SLOT_SIZE; value <= (starts[i] + durations[i]) / TIME_SLOT_SIZE; ++value)
              {
                  result_values.push_back(value * TIME_SLOT_SIZE);
                  ++current_offset;
              }
  
              result_offsets[i] = current_offset;
          }
      }
  
      static void vector_constant(
          const PaddedPODArray<UInt32> & starts, DurationType duration,
          PaddedPODArray<UInt32> & result_values, ColumnArray::Offsets & result_offsets)
      {
          size_t size = starts.size();
  
          result_offsets.resize(size);
          result_values.reserve(size);
  
          ColumnArray::Offset current_offset = 0;
          for (size_t i = 0; i < size; ++i)
          {
              for (UInt32 value = starts[i] / TIME_SLOT_SIZE; value <= (starts[i] + duration) / TIME_SLOT_SIZE; ++value)
              {
                  result_values.push_back(value * TIME_SLOT_SIZE);
                  ++current_offset;
              }
  
              result_offsets[i] = current_offset;
          }
      }
  
      static void constant_vector(
          UInt32 start, const PaddedPODArray<DurationType> & durations,
          PaddedPODArray<UInt32> & result_values, ColumnArray::Offsets & result_offsets)
      {
          size_t size = durations.size();
  
          result_offsets.resize(size);
          result_values.reserve(size);
  
          ColumnArray::Offset current_offset = 0;
          for (size_t i = 0; i < size; ++i)
          {
              for (UInt32 value = start / TIME_SLOT_SIZE; value <= (start + durations[i]) / TIME_SLOT_SIZE; ++value)
              {
                  result_values.push_back(value * TIME_SLOT_SIZE);
                  ++current_offset;
              }
  
              result_offsets[i] = current_offset;
          }
      }
  
      static void constant_constant(
          UInt32 start, DurationType duration,
          Array & result)
      {
          for (UInt32 value = start / TIME_SLOT_SIZE; value <= (start + duration) / TIME_SLOT_SIZE; ++value)
              result.push_back(static_cast<UInt64>(value * TIME_SLOT_SIZE));
      }
  };
  
  
  class FunctionTimeSlots : public IFunction
  {
  public:
      static constexpr auto name = "timeSlots";
      static FunctionPtr create(const Context &) { return std::make_shared<FunctionTimeSlots>(); };
  
      String getName() const override
      {
          return name;
      }
  
      size_t getNumberOfArguments() const override { return 2; }
  
      DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override
      {
          if (!checkDataType<DataTypeDateTime>(arguments[0].get()))
              throw Exception("Illegal type " + arguments[0]->getName() + " of first argument of function " + getName() + ". Must be DateTime.",
                  ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
  
          if (!checkDataType<DataTypeUInt32>(arguments[1].get()))
              throw Exception("Illegal type " + arguments[1]->getName() + " of second argument of function " + getName() + ". Must be UInt32.",
                  ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
  
          return std::make_shared<DataTypeArray>(std::make_shared<DataTypeDateTime>());
      }
  
      void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result) override
      {
          auto starts = checkAndGetColumn<ColumnUInt32>(block.getByPosition(arguments[0]).column.get());
          auto const_starts = checkAndGetColumnConst<ColumnUInt32>(block.getByPosition(arguments[0]).column.get());
  
          auto durations = checkAndGetColumn<ColumnUInt32>(block.getByPosition(arguments[1]).column.get());
          auto const_durations = checkAndGetColumnConst<ColumnUInt32>(block.getByPosition(arguments[1]).column.get());
  
          auto res = ColumnArray::create(ColumnUInt32::create());
          ColumnUInt32::Container & res_values = typeid_cast<ColumnUInt32 &>(res->getData()).getData();
  
          if (starts && durations)
          {
              TimeSlotsImpl<UInt32>::vector_vector(starts->getData(), durations->getData(), res_values, res->getOffsets());
              block.getByPosition(result).column = std::move(res);
          }
          else if (starts && const_durations)
          {
              TimeSlotsImpl<UInt32>::vector_constant(starts->getData(), const_durations->getValue<UInt32>(), res_values, res->getOffsets());
              block.getByPosition(result).column = std::move(res);
          }
          else if (const_starts && durations)
          {
              TimeSlotsImpl<UInt32>::constant_vector(const_starts->getValue<UInt32>(), durations->getData(), res_values, res->getOffsets());
              block.getByPosition(result).column = std::move(res);
          }
          else if (const_starts && const_durations)
          {
              Array const_res;
              TimeSlotsImpl<UInt32>::constant_constant(const_starts->getValue<UInt32>(), const_durations->getValue<UInt32>(), const_res);
              block.getByPosition(result).column = block.getByPosition(result).type->createColumnConst(block.rows(), const_res);
          }
          else
              throw Exception("Illegal columns " + block.getByPosition(arguments[0]).column->getName()
                      + ", " + block.getByPosition(arguments[1]).column->getName()
                      + " of arguments of function " + getName(),
                  ErrorCodes::ILLEGAL_COLUMN);
      }
  };
  
  
  using FunctionToYear = FunctionDateOrDateTimeToSomething<DataTypeUInt16, ToYearImpl>;
  using FunctionToQuarter = FunctionDateOrDateTimeToSomething<DataTypeUInt8, ToQuarterImpl>;
  using FunctionToMonth = FunctionDateOrDateTimeToSomething<DataTypeUInt8, ToMonthImpl>;
  using FunctionToDayOfMonth = FunctionDateOrDateTimeToSomething<DataTypeUInt8, ToDayOfMonthImpl>;
  using FunctionToDayOfWeek = FunctionDateOrDateTimeToSomething<DataTypeUInt8, ToDayOfWeekImpl>;
  using FunctionToHour = FunctionDateOrDateTimeToSomething<DataTypeUInt8, ToHourImpl>;
  using FunctionToMinute = FunctionDateOrDateTimeToSomething<DataTypeUInt8, ToMinuteImpl>;
  using FunctionToSecond = FunctionDateOrDateTimeToSomething<DataTypeUInt8, ToSecondImpl>;
  using FunctionToStartOfDay = FunctionDateOrDateTimeToSomething<DataTypeDateTime, ToStartOfDayImpl>;
  using FunctionToMonday = FunctionDateOrDateTimeToSomething<DataTypeDate, ToMondayImpl>;
  using FunctionToStartOfMonth = FunctionDateOrDateTimeToSomething<DataTypeDate, ToStartOfMonthImpl>;
  using FunctionToStartOfQuarter = FunctionDateOrDateTimeToSomething<DataTypeDate, ToStartOfQuarterImpl>;
  using FunctionToStartOfYear = FunctionDateOrDateTimeToSomething<DataTypeDate, ToStartOfYearImpl>;
  using FunctionToStartOfMinute = FunctionDateOrDateTimeToSomething<DataTypeDateTime, ToStartOfMinuteImpl>;
  using FunctionToStartOfFiveMinute = FunctionDateOrDateTimeToSomething<DataTypeDateTime, ToStartOfFiveMinuteImpl>;
  using FunctionToStartOfFifteenMinutes = FunctionDateOrDateTimeToSomething<DataTypeDateTime, ToStartOfFifteenMinutesImpl>;
  using FunctionToStartOfHour = FunctionDateOrDateTimeToSomething<DataTypeDateTime, ToStartOfHourImpl>;
  using FunctionToTime = FunctionDateOrDateTimeToSomething<DataTypeDateTime, ToTimeImpl>;
  
  using FunctionToRelativeYearNum = FunctionDateOrDateTimeToSomething<DataTypeUInt16, ToRelativeYearNumImpl>;
  using FunctionToRelativeQuarterNum = FunctionDateOrDateTimeToSomething<DataTypeUInt32, ToRelativeQuarterNumImpl>;
  using FunctionToRelativeMonthNum = FunctionDateOrDateTimeToSomething<DataTypeUInt32, ToRelativeMonthNumImpl>;
  using FunctionToRelativeWeekNum = FunctionDateOrDateTimeToSomething<DataTypeUInt32, ToRelativeWeekNumImpl>;
  using FunctionToRelativeDayNum = FunctionDateOrDateTimeToSomething<DataTypeUInt32, ToRelativeDayNumImpl>;
  using FunctionToRelativeHourNum = FunctionDateOrDateTimeToSomething<DataTypeUInt32, ToRelativeHourNumImpl>;
  using FunctionToRelativeMinuteNum = FunctionDateOrDateTimeToSomething<DataTypeUInt32, ToRelativeMinuteNumImpl>;
  using FunctionToRelativeSecondNum = FunctionDateOrDateTimeToSomething<DataTypeUInt32, ToRelativeSecondNumImpl>;
  
  using FunctionToYYYYMM = FunctionDateOrDateTimeToSomething<DataTypeUInt32, ToYYYYMMImpl>;
  using FunctionToYYYYMMDD = FunctionDateOrDateTimeToSomething<DataTypeUInt32, ToYYYYMMDDImpl>;
  using FunctionToYYYYMMDDhhmmss = FunctionDateOrDateTimeToSomething<DataTypeUInt64, ToYYYYMMDDhhmmssImpl>;
  
  using FunctionAddSeconds = FunctionDateOrDateTimeAddInterval<AddSecondsImpl>;
  using FunctionAddMinutes = FunctionDateOrDateTimeAddInterval<AddMinutesImpl>;
  using FunctionAddHours = FunctionDateOrDateTimeAddInterval<AddHoursImpl>;
  using FunctionAddDays = FunctionDateOrDateTimeAddInterval<AddDaysImpl>;
  using FunctionAddWeeks = FunctionDateOrDateTimeAddInterval<AddWeeksImpl>;
  using FunctionAddMonths = FunctionDateOrDateTimeAddInterval<AddMonthsImpl>;
  using FunctionAddYears = FunctionDateOrDateTimeAddInterval<AddYearsImpl>;
  
  using FunctionSubtractSeconds = FunctionDateOrDateTimeAddInterval<SubtractSecondsImpl>;
  using FunctionSubtractMinutes = FunctionDateOrDateTimeAddInterval<SubtractMinutesImpl>;
  using FunctionSubtractHours = FunctionDateOrDateTimeAddInterval<SubtractHoursImpl>;
  using FunctionSubtractDays = FunctionDateOrDateTimeAddInterval<SubtractDaysImpl>;
  using FunctionSubtractWeeks = FunctionDateOrDateTimeAddInterval<SubtractWeeksImpl>;
  using FunctionSubtractMonths = FunctionDateOrDateTimeAddInterval<SubtractMonthsImpl>;
  using FunctionSubtractYears = FunctionDateOrDateTimeAddInterval<SubtractYearsImpl>;
  
  }
  