/**
 * Copyright (c) 2021 OceanBase
 * OceanBase Database Proxy(ODP) is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#define USING_LOG_PREFIX LIB_TIME
#include <stdlib.h>
#include <stdint.h>
#include <limits.h>
#include "lib/time/ob_time_utility.h"
#include "lib/utility/utility.h"
#include "common/ob_obj_type.h"

#define IS_MINUS(str, is_minus) \
  if (OB_SUCC(ret)) \
  { \
    const char *ptr = str.ptr(); \
    const char *end_ptr = str.ptr() + str.length(); \
    while ((ptr < end_ptr) && (*ptr < '0' || *ptr > '9')) \
    { \
      if ('-' == *ptr) \
      { \
        is_minus = true; \
      } \
      ptr++; \
    } \
  }

namespace oceanbase
{
namespace common
{
const char *ObTimeUtility::mday_name_[31]             =
{
  "1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th",
  "11th", "12th", "13th", "14th", "15th", "16th", "17th", "18th", "19th", "20th",
  "21st", "22nd", "23rd", "24th", "25th", "26th", "27th", "28th", "29th", "30th",
  "31st"
};

const char *ObTimeUtility::weekday_name_[7]           =
{
  "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
};

const char *ObTimeUtility::weekday_abbr_name_[7]      =
{
  "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};

const char *ObTimeUtility::month_abbr_name_[12]       =
{
  "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};

const char *ObTimeUtility::month_name_[12]            =
{
  "January", "February", "March", "April", "May", "June", "July", "August", "September",
  "October", "November", "December"
};

const char *ObTimeUtility::STD_TS_FORMAT_WITH_USEC    = "%Y-%m-%d %H:%i:%s.%f";

const char *ObTimeUtility::STD_TS_FORMAT_WITHOUT_USEC = "%Y-%m-%d %H:%i:%s";

int64_t ObTimeUtility::current_time_coarse()
{
  struct timespec t;
#ifdef HAVE_REALTIME_COARSE
  const int tmp = clock_gettime(CLOCK_REALTIME_COARSE, &t);
#else
  const int tmp = clock_gettime(CLOCK_REALTIME, &t);
#endif
  if (OB_UNLIKELY(tmp != 0)) {
    LOG_WARN("failed to get time coarse", KERRMSGS);
  }
  return (static_cast<int64_t>(t.tv_sec) * static_cast<int64_t>(1000000) + static_cast<int64_t>
          (t.tv_nsec / 1000));
}

int ObTimeUtility::make_second(struct tm &t, time_t &second)
{
  int ret = OB_SUCCESS;
  time_t s = 0;

  t.tm_isdst = -1; //let system to determine whether DST is effect
  if (-1 == (s = mktime(&t))) {
    struct tm tm_cmp;
    time_t t_cmp = -1;

    if ((NULL != localtime_r(&t_cmp, &tm_cmp))
        && (t.tm_sec == tm_cmp.tm_sec && t.tm_min == tm_cmp.tm_min
            && t.tm_hour == tm_cmp.tm_hour && t.tm_mday == tm_cmp.tm_mday
            && t.tm_mon == tm_cmp.tm_mon && t.tm_year == tm_cmp.tm_year)) {
      //1970-01-01 07:59:59 in +08:00 timezone
      ret = OB_SUCCESS;
    } else {
      ret = OB_ERR_SYS;
      LOG_ERROR("make time failed", KERRMSGS,
                K(t.tm_sec), K(t.tm_min), K(t.tm_hour), K(t.tm_mday), K(t.tm_mon), K(t.tm_year));
    }
  }
  if (OB_SUCC(ret)) {
    second = s;
  }
  return ret;
}

time_t ObTimeUtility::extract_second(int64_t usec)
{
  return usec >= 0 ? static_cast<time_t>(usec / 1000000L) : static_cast<time_t>(usec / 1000000L - 1);
}

int ObTimeUtility::usec_format_to_str(int64_t usec, const ObString &format, char *buf,
                                      int64_t buf_len, int64_t &pos)
{
  int ret = OB_SUCCESS;
  struct tm t;
  int64_t second = 0;
  int64_t incre_usec = 0;

  second = usec / (1000L * 1000L);
  incre_usec = usec % (1000L * 1000L);
  if (incre_usec < 0) {
    incre_usec += 1000L * 1000L;
    second -= 1;
  }
  memset(&t, 0, sizeof(struct tm));
  t.tm_isdst = -1;
  if (NULL == localtime_r(&second, &t)) {
    ret = OB_ERR_UNEXPECTED;
    LOG_ERROR("convert second to struct tm failed", K(second));
  } else if (OB_FAIL(timestamp_format_to_str(t, incre_usec, format, buf, buf_len, pos))) {
    LOG_WARN("format date failed", K(ret));
  }
  return ret;
}

int ObTimeUtility::usec_to_str(int64_t usec, char *buf, int64_t buf_len, int64_t &pos)
{
  const char *format = NULL;
  if (usec % 1000000) {
    format = STD_TS_FORMAT_WITH_USEC;
  } else {
    format = STD_TS_FORMAT_WITHOUT_USEC;
  }
  return usec_format_to_str(usec, ObString(format), buf, buf_len, pos);
}

int ObTimeUtility::timestamp_format_to_str(const struct tm &t, int64_t usec, const ObString &format,
                                           char *buf, int64_t buf_len, int64_t &pos)
{
  int ret = OB_SUCCESS;

  if (OB_ISNULL(format.ptr())
      || OB_UNLIKELY(format.length() <= 0)
      || OB_ISNULL(buf)
      || OB_UNLIKELY(buf_len <= 0)
      || OB_UNLIKELY(!is_valid_date(t.tm_year + 1900, t.tm_mon + 1, t.tm_mday))
      || OB_UNLIKELY(!is_valid_time(t.tm_hour, t.tm_min, t.tm_sec, static_cast<int>(usec)))) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("invalid argument", K(format), K(buf), K(buf_len), K(t.tm_year), K(t.tm_mon),
             K(t.tm_mday), K(t.tm_hour), K(t.tm_min), K(t.tm_sec), K(usec));
  } else if (pos >= buf_len) {
    ret = OB_SIZE_OVERFLOW;
    //size overflow isn't an error, so don't print warnings
  } else {
    const char *format_ptr = format.ptr();
    const char *end_ptr = format.ptr() + format.length();
    while (format_ptr < end_ptr && OB_SUCC(ret)) {
      if ('%' == *format_ptr) {
        ++format_ptr;
        if (format_ptr >= end_ptr) {
          ret = OB_INVALID_ARGUMENT;
          LOG_WARN("format string is invalid", K(format));
          break;
        }
        switch (*format_ptr) {
          case 'a': { //Abbreviated weekday name (Sun..Sat)
            ret = databuff_printf(buf, buf_len, pos, "%s", weekday_abbr_name_[t.tm_wday]);
            break;
          }
          case 'b': { //Abbreviated month name (Jan..Dec)
            ret = databuff_printf(buf, buf_len, pos, "%s", month_abbr_name_[t.tm_mon]);
            break;
          }
          case 'c': { //Month, numeric (0..12)
            ret = databuff_printf(buf, buf_len, pos, "%d", t.tm_mon + 1);
            break;
          }
          case 'D': { //Day of the month with English suffix (0th, 1st, 2nd, 3rd, ...)
            ret = databuff_printf(buf, buf_len, pos, "%s", mday_name_[t.tm_mday - 1]);
            break;
          }
          case 'd': { //Day of the month, numeric (00..31)
            ret = databuff_printf(buf, buf_len, pos, "%02d", t.tm_mday);
            break;
          }
          case 'e': { //Day of the month, numeric (0..31)
            ret = databuff_printf(buf, buf_len, pos, "%d", t.tm_mday);
            break;
          }
          case 'f': { //Microseconds (000000..999999)
            ret = databuff_printf(buf, buf_len, pos, "%06ld", usec);
            break;
          }
          case 'H': { //Hour (00..23)
            ret = databuff_printf(buf, buf_len, pos, "%02d", t.tm_hour);
            break;
          }
          case 'h': //Hour (01..12)
          case 'I': { //Hour (01..12)
            int hour = 0;
            if (0 == t.tm_hour) {
              hour = 12; //AM 12 clock
            } else if (t.tm_hour > 12) {
              hour = t.tm_hour - 12;
            } else {
              hour = t.tm_hour;
            }
            ret = databuff_printf(buf, buf_len, pos, "%02d", hour);
            break;
          }
          case 'i': { //Minutes, numeric (00..59)
            ret = databuff_printf(buf, buf_len, pos, "%02d", t.tm_min);
            break;
          }
          case 'j': { //Day of year (001..366)
            ret = databuff_printf(buf, buf_len, pos, "%03d", t.tm_yday + 1);
            break;
          }
          case 'k': { //Hour (0..23)
            ret = databuff_printf(buf, buf_len, pos, "%d", t.tm_hour);
            break;
          }
          case 'l': { //Hour (1..12)
            int hour = 0;
            if (t.tm_hour == 0) {
              hour = 12; //AM 12 clock
            } else if (t.tm_hour > 12) {
              hour = t.tm_hour - 12; //PM
            } else {
              hour = t.tm_hour;
            }
            ret = databuff_printf(buf, buf_len, pos, "%d", hour);
            break;
          }
          case 'M': { //Month name (January..December)
            ret = databuff_printf(buf, buf_len, pos, "%s", month_name_[t.tm_mon]);
            break;
          }
          case 'm': { //Month, numeric (00..12)
            ret = databuff_printf(buf, buf_len, pos, "%02d", t.tm_mon + 1);
            break;
          }
          case 'p': { //AM or PM
            const char *ptr = t.tm_hour >= 12 ? "PM" : "AM";
            if (t.tm_hour >= 0 && t.tm_hour < 12) {
              ptr = "AM"; //AM 12 clock - 11 clock
            } else {
              ptr = "PM";
            }
            ret = databuff_printf(buf, buf_len, pos, "%s", ptr);
            break;
          }
          case 'r': { //Time, 12-hour (hh:mm:ss followed by AM or PM)
            int hour = t.tm_hour % 12;
            const char *ptr = t.tm_hour >= 12 ? "PM" : "AM";
            ret = databuff_printf(buf, buf_len, pos, "%02d:%02d:%02d %s", hour, t.tm_min, t.tm_sec, ptr);
            break;
          }
          case 'S': //Seconds (00..60)
          case 's': { //Seconds (00..60) (1 leap second)
            ret = databuff_printf(buf, buf_len, pos, "%02d", t.tm_sec);
            break;
          }
          case 'T': { //Time, 24-hour (hh:mm:ss)
            ret = databuff_printf(buf, buf_len, pos, "%02d:%02d:%02d", t.tm_hour, t.tm_min, t.tm_sec);
            break;
          }
          case 'U': { //Week (00..53), where Sunday is the first day of the week
            struct tm tmp = t;
            uint8_t flag_mask = START_WITH_SUNDAY | WEEK_FIRST_WEEKDAY;
            ret = databuff_printf(buf, buf_len, pos, "%02d", get_weeks_of_year(tmp, flag_mask));
            break;
          }
          case 'u': { //Week (00..53), where Monday is the first day of the week
            struct tm tmp = t;
            uint8_t flag_mask = 0;
            ret = databuff_printf(buf, buf_len, pos, "%02d", get_weeks_of_year(tmp, flag_mask));
            break;
          }
          case 'V': { //Week (01..53), where Sunday is the first day of the week; used with %X
            struct tm tmp = t;
            uint8_t flag_mask = START_WITH_SUNDAY | WEEK_FIRST_WEEKDAY | INCLUDE_CRITICAL_WEEK;
            ret = databuff_printf(buf, buf_len, pos, "%02d", get_weeks_of_year(tmp, flag_mask));
            break;
          }
          case 'v': { //Week (01..53), where Monday is the first day of the week; used with %x
            struct tm tmp = t;
            uint8_t flag_mask = INCLUDE_CRITICAL_WEEK;
            ret = databuff_printf(buf, buf_len, pos, "%02d", get_weeks_of_year(tmp, flag_mask));
            break;
          }
          case 'W': { //Weekday name (Sunday..Saturday)
            ret = databuff_printf(buf, buf_len, pos, "%s", weekday_name_[t.tm_wday]);
            break;
          }
          case 'w': { //Day of the week (0=Sunday..6=Saturday)
            ret = databuff_printf(buf, buf_len, pos, "%d", t.tm_wday);
            break;
          }
          case 'X': { //Year for the week where Sunday is the first day of the week, numeric, four digits; used with %V
            struct tm tmp = t;
            uint8_t flag_mask = START_WITH_SUNDAY | WEEK_FIRST_WEEKDAY | INCLUDE_CRITICAL_WEEK;
            get_weeks_of_year(tmp, flag_mask);
            ret = databuff_printf(buf, buf_len, pos, "%04d", tmp.tm_year + 1900);
            break;
          }
          case 'x': { //Year for the week, where Monday is the first day of the week, numeric, four digits; used with %v
            struct tm tmp = t;
            uint8_t flag_mask = INCLUDE_CRITICAL_WEEK;
            get_weeks_of_year(tmp, flag_mask);
            ret = databuff_printf(buf, buf_len, pos, "%04d", tmp.tm_year + 1900);
            break;
          }
          case 'Y': { //Year, numeric, four digits
            ret = databuff_printf(buf, buf_len, pos, "%04d", t.tm_year + 1900);
            break;
          }
          case 'y': { //Year, numeric (two digits)
            int year = (t.tm_year + 1900) % 100;
            ret = databuff_printf(buf, buf_len, pos, "%02d", year);
            break;
          }
          case '%': { //A literal "%" character
            if (pos >= buf_len) {
              ret = OB_SIZE_OVERFLOW;
              break;
            }
            buf[pos++] = '%';
            break;
          }
          default: {
            ret = OB_NOT_SUPPORTED;
            break;
          }
        }
        if (OB_SUCC(ret)) {
          format_ptr++;
        }
      } else if (pos >= buf_len) {
        ret = OB_SIZE_OVERFLOW;
        break;
      } else {
        buf[pos++] = *(format_ptr++);
      }
    }
  }
  return ret;
}

int ObTimeUtility::get_start_weekday(int year_day, int week_day, bool week_first_weekday)
{
  int start_weekday = 0;

  start_weekday = (year_day - week_day + 1) % 7;
  if (start_weekday <= 0) {
    start_weekday += 7;
  }

  //if use ISO 8601:1988 protocol, need to calculate the critical week belong to which year
  if (!week_first_weekday) {
    if (start_weekday >= 5 && start_weekday <= 7) {
      start_weekday = start_weekday - 7;
    }
  }
  return start_weekday;
}

bool ObTimeUtility::check_in_next_critical_week(int yday, int wday, int year, uint8_t flag_mask)
{
  bool bret = false;
  bool week_first_weekday = flag_mask & WEEK_FIRST_WEEKDAY;
  bool include_critical_week = flag_mask & INCLUDE_CRITICAL_WEEK;
  int max_day = is_leap_year(year) ? 366 : 365;

  if (include_critical_week && !week_first_weekday && yday > max_day - 3) {
    int week_first_day = yday - wday + 1;
    if (max_day - week_first_day + 1 <= 3) {
      bret = true;
    }
  }
  return bret;
}

int ObTimeUtility::get_weeks_of_year(struct tm &t, uint8_t flag_mask)
{
  bool start_with_sunday = false;
  bool week_first_weekday = false;
  bool include_critical_week = false;
  int start_day = 0;
  int today = 0;
  int wday = 0;
  int week_count = 0;

  start_with_sunday = flag_mask & START_WITH_SUNDAY;
  week_first_weekday = flag_mask & WEEK_FIRST_WEEKDAY;
  include_critical_week = flag_mask & INCLUDE_CRITICAL_WEEK;

  today = t.tm_yday + 1;
  //calculate the specified date in which day of the week
  if (start_with_sunday) {
    wday = t.tm_wday + 1;
  } else {
    if (0 == t.tm_wday) {
      //t.tm_wday means sunday, if the week doesn't start with sunday, sunday is the last day
      wday = 7;
    } else {
      wday = t.tm_wday;
    }
  }
  if (check_in_next_critical_week(today, wday, t.tm_year + 1900, flag_mask)) {
    t.tm_year += 1;
    week_count = 1;
  } else {
    start_day = get_start_weekday(today, wday, week_first_weekday);
    if (today < start_day && include_critical_week) {
      t.tm_year -= 1;
      today += is_leap_year(t.tm_year + 1900) ? 366 : 365;
      start_day = get_start_weekday(today, wday, week_first_weekday);
      week_count = (today - start_day) / 7 + 1;
    } else if (today < start_day && !include_critical_week) {
      week_count = 0;
    } else {
      week_count = (today - start_day) / 7 + 1;
    }
  }
  return week_count;
}

int ObTimeUtility::extract_usec(const ObString &str, int64_t &pos, int64_t &usec, DecimalDigts num_flag)
{
  int ret = OB_SUCCESS;

  if (OB_ISNULL(str.ptr()) || OB_UNLIKELY(str.length() <= 0)
      || OB_UNLIKELY(pos < 0) || OB_UNLIKELY(pos >= str.length())) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("invalid argument", K(str), K(pos));
  } else {
    const char *cur_ptr = str.ptr() + pos;
    const char *end_ptr = str.ptr() + str.length();
    int scanned = 0;
    int64_t result = 0;
    int64_t cur_value = 0;

    //skip non-numeric character
    while (cur_ptr < end_ptr && (*cur_ptr > '9' || *cur_ptr < '0')) {
      ++cur_ptr;
    }
    if (cur_ptr >= end_ptr) {
      ret = OB_ERR_UNEXPECTED;
      LOG_WARN("current ptr is invalid", K(cur_ptr), K(end_ptr));
    } else {
      while (cur_ptr < end_ptr && scanned < 6 && *cur_ptr <= '9' && *cur_ptr >= '0') {
        cur_value = *cur_ptr - '0';
        result = result * 10L + cur_value;
        ++scanned;
        ++cur_ptr;
      }
      if (num_flag == DIGTS_SENSITIVE && 6 == scanned && cur_ptr < end_ptr) {
         ret = OB_INVALID_ARGUMENT;
         LOG_USER_ERROR(ret, "more than 6 decimal places");
      } else if (scanned < 6) {
        for (int i = scanned; i < 6; i ++) {
          result *= 10L;
        }
      }
      pos = cur_ptr - str.ptr();
      usec = result;
    }
  }
  return ret;
}

int ObTimeUtility::extract_date(const ObString &str, int n, int64_t &pos, int64_t &value)
{
  return extract_int(str, n, pos, value);
}

int ObTimeUtility::str_to_time(const ObString &date, int64_t &usec, DecimalDigts num_flag)
{
  int ret = OB_SUCCESS;
  int64_t pos = 0;
  int64_t sec = 0;
  int64_t tmp_usec = 0;
  if (OB_FAIL(extract_int(date, 13, pos, sec))) {
    LOG_WARN("extract second failed", K(ret));
  } else if (OB_ISNULL(date.ptr()) || OB_UNLIKELY(date.length() <= 0) || OB_UNLIKELY(pos >= date.length())) {
    ret = OB_ERR_UNEXPECTED;
    LOG_WARN("date string is invalid", K(date), K(pos));
  } else {
    usec = sec * 1000000L;
    const char *ptr = date.ptr();
    //search for '.'
    while (pos < date.length() && ptr[pos] != '.') {
      ++pos;
    }
    if (pos < date.length()) {
      ++pos; //skip '.'
      if (OB_FAIL(extract_usec(date, pos, tmp_usec, num_flag))) {
        LOG_WARN("extract usec part failed", K(ret));
      } else {
        usec += tmp_usec;
      }
    }
  }
  return ret;
}

int ObTimeUtility::str_to_timestamp(const ObString &date, struct tm &t, int64_t &usec)
{
  int64_t dates[7] = {0};
  int matched = 0;
  int64_t pos = 0;
  bool is_minus = false;
  int ret = OB_SUCCESS;

  usec = 0;
  //matching relaxed date format and strict date format
  //such as 'ww 2013 ***11%%%^^^09>>>08:45&&03?678'
  //and '2013-11-09 08:45:03.678'
  //match year value
  if (OB_ISNULL(date.ptr()) || OB_UNLIKELY(date.length() <= 0)) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("date string is invalid", K(date));
  }
  IS_MINUS(date, is_minus);
  for (int i = 0; OB_SUCC(ret) && i < 7; i++) {
    if (OB_UNLIKELY(6 == matched)) {
      //to match usec
      if (OB_FAIL(ObTimeUtility::extract_usec(date, pos, dates[i], DIGTS_INSENSITIVE))) {
        LOG_WARN("extract usec with digts insensitive failed", K(ret));
      }
    } else {
      if (OB_FAIL(ObTimeUtility::extract_date(date, 0, pos, dates[i]))) {
        LOG_WARN("extract date failed", K(ret));
      }
    }
    ++matched;
  }

  if (OB_SUCC(ret)) {
    if (is_minus) {
      dates[0] = -dates[0];
    }
    if (OB_UNLIKELY(matched < 3)) {
      ret = OB_INVALID_DATE_FORMAT;
      LOG_WARN("year, month, day is necessary in date format", K(date));
    }
    //check the date value whether valid
    else if (!is_valid_date(static_cast<int>(dates[0]), static_cast<int>(dates[1]),
                            static_cast<int>(dates[2]))
             || !is_valid_time(static_cast<int>(dates[3]), static_cast<int>(dates[4]),
                               static_cast<int>(dates[5]),
                               static_cast<int>(dates[6]))) {
      ret = OB_INVALID_DATE_FORMAT;
      LOG_WARN("the date format is invalid");
    } else {
      //not use fuzzy year rule making by MySQL
      /*
      //if year value is fuzzy, we use this rule:
      //[00, 69] treat as [2000, 2069]
      //[70, 99] treat as [1970, 1999]
      if (dates[0] >= 0 && dates[0] <= 69)
      {
        dates[0] += 2000;
      }
      else if (dates[0] >= 70 && dates[0] <= 99)
      {
        dates[0] += 1900;
      }*/
      t.tm_sec = static_cast<int>(dates[5]);
      t.tm_min = static_cast<int>(dates[4]);
      t.tm_hour = static_cast<int>(dates[3]);
      t.tm_mday = static_cast<int>(dates[2]);
      t.tm_mon = static_cast<int>(dates[1]) - 1;
      t.tm_year = static_cast<int>(dates[0]) - 1900;
      usec = dates[6];
    }
  }
  return ret;
}

int ObTimeUtility::str_to_usec(const ObString &date, int64_t &usec)
{
  int ret = OB_SUCCESS;
  struct tm t;
  int64_t sec = 0;
  int64_t tmp_usec = 0;

  memset(&t, 0, sizeof(struct tm));
  t.tm_isdst = -1;
  if (OB_FAIL(str_to_timestamp(date, t, tmp_usec))) {
    LOG_WARN("parse string to date failed", K(ret));
  } else if (OB_FAIL(make_second(t, sec))) {
    LOG_WARN("parse time to usec failed", K(ret));
  } else {
    usec = sec * 1000L * 1000L + tmp_usec;
  }
  return ret;
}

bool ObTimeUtility::is_valid_year(int year)
{
  //(0, 9999]
  bool ret = true;
  if (year <= 0 || year > 9999) {
    ret = false;
    //let user catch the error
    LOG_USER_ERROR(OB_INVALID_DATE_FORMAT, "year", year, 1, 9999);
  }
  return ret;
}

bool ObTimeUtility::is_valid_month(int month)
{
  bool ret = true;
  if (month < 1 || month > 12) {
    ret = false;
    LOG_USER_ERROR(OB_INVALID_DATE_FORMAT, "month", month, 1, 12);
  }
  return ret;
}

bool ObTimeUtility::is_leap_year(int year)
{
  return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}

bool ObTimeUtility::is_valid_mday(int year, int month, int mday)
{
  bool ret = true;

  switch (month) {
    case 1:
    case 3:
    case 5:
    case 7:
    case 8:
    case 10:
    case 12: {
      if (mday > 31 || mday < 1) {
        ret = false;
        LOG_USER_ERROR(OB_INVALID_DATE_FORMAT, "mday", mday, 1, 31);
      }
      break;
    }
    case 4:
    case 6:
    case 9:
    case 11: {
      if (mday > 30 || mday < 1) {
        ret = false;
        LOG_USER_ERROR(OB_INVALID_DATE_FORMAT, "mday", mday, 1, 30);
      }
      break;
    }
    case 2: {
      if (is_leap_year(year)) {
        if (mday > 29 || mday < 1) {
          ret = false;
          LOG_USER_ERROR(OB_INVALID_DATE_FORMAT, "mday", mday, 1, 29);
        }
      } else {
        if (mday > 28 || mday < 1) {
          ret = false;
          LOG_USER_ERROR(OB_INVALID_DATE_FORMAT, "mday", mday, 1, 28);
        }
      }
      break;
    }
    default: {
      ret = false;
      LOG_WARN("month must between 1 and 12", K(month));
      break;
    }
  }
  return ret;
}

bool ObTimeUtility::is_valid_hour(int hour)
{
  bool ret = true;
  if (hour > 23 || hour < 0) {
    ret = false;
    LOG_USER_ERROR(OB_INVALID_DATE_FORMAT, "hour", hour, 0, 23);
  }
  return ret;
}

bool ObTimeUtility::is_valid_minute(int minute)
{
  bool ret = true;
  if (minute > 59 || minute < 0) {
    ret = false;
    LOG_USER_ERROR(OB_INVALID_DATE_FORMAT, "minute", minute, 0, 59);
  }
  return ret;
}

bool ObTimeUtility::is_valid_second(int second)
{
  bool ret = true;
  if (second > 59 || second < 0) {
    ret = false;
    LOG_USER_ERROR(OB_INVALID_DATE_FORMAT, "second", second, 0, 59);
  }
  return ret;
}

bool ObTimeUtility::is_valid_usec(int usec)
{
  bool ret = true;
  if (usec > 999999 || usec < 0) {
    ret = false;
    LOG_USER_ERROR(OB_INVALID_DATE_FORMAT, "usec", usec, 0, 999999);
  }
  return ret;
}

bool ObTimeUtility::is_valid_date(int year, int month, int mday)
{
  return is_valid_year(year) && is_valid_month(month) && is_valid_mday(year, month, mday);
}

bool ObTimeUtility::is_valid_time(int hour, int minute, int second, int usec)
{
  return is_valid_hour(hour) && is_valid_minute(minute) && is_valid_second(second) &&
         is_valid_usec(usec);
}
}  // namespace common
}  // namespace oceanbase
