/*
 * Copyright (C) 2016-2022 ActionTech.
 * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher.
 */

package com.actiontech.dble.plan.common.time;

import com.actiontech.dble.plan.common.Ctype;
import com.actiontech.dble.plan.common.MySQLcom;
import com.actiontech.dble.plan.common.item.FieldTypes;
import com.actiontech.dble.plan.common.item.Item;
import com.actiontech.dble.plan.common.locale.MyLocale;
import com.actiontech.dble.plan.common.locale.MyLocales;
import com.actiontech.dble.plan.common.ptr.BoolPtr;
import com.actiontech.dble.plan.common.ptr.LongPtr;
import com.actiontech.dble.plan.common.ptr.StringPtr;
import com.alibaba.druid.sql.ast.expr.SQLIntervalUnit;

import java.math.BigDecimal;
import java.math.BigInteger;

public final class MyTime {
    private MyTime() {
    }

    private static final DateTimeFormat TIME_AMPM_FORMAT = new DateTimeFormat("%I:%i:%S %p");
    private static final DateTimeFormat TIME_24_HRS_FORMAT = new DateTimeFormat("%H:%i:%S");

    /* Flags to strToDatetime and numberToDatetime */
    public static final int TIME_FUZZY_DATE = 1;
    public static final int TIME_DATETIME_ONLY = 2;
    public static final int TIME_NO_NSEC_ROUNDING = 4;
    public static final int TIME_NO_DATE_FRAC_WARN = 8;

    public static final int DATETIME_MAX_DECIMALS = 6;

    public static final int MAX_DATE_PARTS = 8;
    public static final int MAX_DATE_WIDTH = 10;
    public static final int MAX_TIME_WIDTH = 10;
    public static final int MAX_DATETIME_WIDTH = 19;
    /*
     * -DDDDDD
     * HH:MM:SS.######
     */
    public static final int MAX_TIME_FULL_WIDTH = 23;
    /*
     * YYYY-MM-DD
     * HH:MM:SS.######
     * AM
     */
    public static final int MAX_DATETIME_FULL_WIDTH = 29;
    public static final int MAX_DATE_STRING_REP_LENGTH = 30;

    public static final int TIME_MAX_HOUR = 838;
    public static final int TIME_MAX_MINUTE = 59;
    public static final int TIME_MAX_SECOND = 59;

    public static final int MAX_DAY_NUMBER = 3652424;

    public static final int SECONDS_IN_24H = 86400;

    public static final int TIME_MAX_VALUE = (TIME_MAX_HOUR * 10000 + TIME_MAX_MINUTE * 100 + TIME_MAX_SECOND);

    public static final long TIME_MAX_VALUE_SECONDS = (TIME_MAX_HOUR * 3600L + TIME_MAX_MINUTE * 60L + TIME_MAX_SECOND);

    /* Must be same as MODE_NO_ZERO_IN_DATE */
    public static final long TIME_NO_ZERO_IN_DATE = (65536L * 2 * 2 * 2 * 2 * 2 * 2 * 2);
    /* Must be same as MODE_NO_ZERO_DATE */
    public static final long TIME_NO_ZERO_DATE = (TIME_NO_ZERO_IN_DATE * 2);
    public static final long TIME_INVALID_DATES = (TIME_NO_ZERO_DATE * 2);

    /* Conversion warnings */
    public static final int MYSQL_TIME_WARN_TRUNCATED = 1;
    public static final int MYSQL_TIME_WARN_OUT_OF_RANGE = 2;
    public static final int MYSQL_TIME_WARN_INVALID_TIMESTAMP = 4;
    public static final int MYSQL_TIME_WARN_ZERO_DATE = 8;
    public static final int MYSQL_TIME_NOTE_TRUNCATED = 16;
    public static final int MYSQL_TIME_WARN_ZERO_IN_DATE = 32;

    public static final int YY_PART_YEAR = 70;

    public static final long LONG_MAX = MySQLcom.getUnsignedLong(Integer.MAX_VALUE).longValue();

    static final char TIME_SEPARATOR = ':';

    /* Position for YYYY-DD-MM HH-MM-DD.FFFFFF AM in default format */

    private static final int[] INTERNAL_FORMAT_POSITIONS = {0, 1, 2, 3, 4, 5, 6, 255};

    public static final int[] DAYS_IN_MONTH = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0};

    public static final String[] MONTH_NAMES = {"January", "February", "March", "April", "May", "June", "July",
            "August", "September", "October", "November", "December"};

    public static final String[] AB_MONTH_NAMES = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep",
            "Oct", "Nov", "Dec"};

    public static final String[] DAY_NAMES = {"Monday", "Tuesday", "Thursday", "Friday", "Saturday", "Sunday"};

    public static final String[] AB_DAY_NAMES = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};

    /* Flags for calc_week() function. */
    public static final int WEEK_MONDAY_FIRST = 1;
    public static final int WEEK_YEAR = 2;
    public static final int WEEK_FIRST_WEEKDAY = 4;

    /*-------------------------My_time.h  start------------------------------
     *-------------------------change time type to other type--------------- */

    /*
     * Handle 2 digit year conversions
     *
     * SYNOPSIS year2000Handling() year 2 digit year
     *
     * RETURN Year between 1970-2069
     */

    public static long year2000Handling(long year) {
        if ((year = year + 1900) < 1900 + YY_PART_YEAR)
            year += 100;
        return year;
    }

    /**
     * @return 0 OK 1 error
     * @brief Check datetime value for validity according to flags.
     * @param[in] ltime Date to check.
     * @param[in] not_zero_date ltime is not the zero date
     * @param[in] flags flags to check (see strToDatetime() flags in
     * my_time.h)
     * @param[out] was_cut set to 2 if value was invalid according to flags.
     * (Feb 29 in non-leap etc.) This remains unchanged if value is
     * not invalid.
     * @details Here we assume that year and month is ok! If month is 0 we allow
     * any date. (This only happens if we allow zero date parts in
     * strToDatetime()) Disallow dates with zero year and non-zero
     * month and/or day.
     */

    public static boolean checkDate(final MySQLTime ltime, boolean notZeroDate, long flags, LongPtr wasCut) {
        if (notZeroDate) {
            if (((flags & TIME_NO_ZERO_IN_DATE) != 0 || (flags & TIME_FUZZY_DATE) == 0) &&
                    (ltime.getMonth() == 0 || ltime.getDay() == 0)) {
                wasCut.set(MYSQL_TIME_WARN_ZERO_IN_DATE);
                return true;
            } else if (((flags & TIME_INVALID_DATES) == 0 && ltime.getMonth() != 0 &&
                    ltime.getDay() > DAYS_IN_MONTH[(int) ltime.getMonth() - 1] &&
                    (ltime.getMonth() != 2 || calcDaysInYear(ltime.getYear()) != 366 || ltime.getDay() != 29))) {
                wasCut.set(MYSQL_TIME_WARN_OUT_OF_RANGE);
                return true;
            }
        } else if ((flags & TIME_NO_ZERO_DATE) != 0) {
            wasCut.set(MYSQL_TIME_WARN_ZERO_DATE);
            return true;
        }
        return false;
    }

    /*
     * Convert a timestamp string to a MYSQL_TIME value.
     *
     * SYNOPSIS strToDatetime() str String to parse length Length of string
     * l_time Date is stored here flags Bitmap of following items
     * TIME_FUZZY_DATE Set if we should allow partial dates TIME_DATETIME_ONLY
     * Set if we only allow full datetimes. TIME_NO_ZERO_IN_DATE Don't allow
     * partial dates TIME_NO_ZERO_DATE Don't allow 0000-00-00 date
     * TIME_INVALID_DATES Allow 2000-02-31 status Conversion status
     *
     *
     * DESCRIPTION At least the following formats are recogniced (based on
     * number of digits) YYMMDD, YYYYMMDD, YYMMDDHHMMSS, YYYYMMDDHHMMSS
     * YY-MM-DD, YYYY-MM-DD, YY-MM-DD HH.MM.SS YYYYMMDDTHHMMSS where T is a the
     * character T (ISO8601) Also dates where all parts are zero are allowed
     *
     * The second part may have an optional .###### fraction part.
     *
     * NOTES This function should work with a format position vector as long as
     * the following things holds: - All date are kept together and all time
     * parts are kept together - Date and time parts must be separated by blank
     * - Second fractions must come after second part and be separated by a '.'.
     * (The second fractions are optional) - AM/PM must come after second
     * fractions (or after seconds if no fractions) - Year must always been
     * specified. - If time is before date, then we will use datetime format
     * only if the argument consist of two parts, separated by space. Otherwise
     * we will assume the argument is a date. - The hour part must be specified
     * in hour-minute-second order.
     *
     * status.warnings is set to: 0 Value OK MYSQL_TIME_WARN_TRUNCATED If value
     * was cut during conversion MYSQL_TIME_WARN_OUT_OF_RANGE
     * checkDate(date,flags) considers date invalid
     *
     * l_time.time_type is set as follows: MYSQL_TIMESTAMP_NONE String wasn't a
     * timestamp, like [DD [HH:[MM:[SS]]]].fraction. l_time is not changed.
     * MYSQL_TIMESTAMP_DATE DATE string (YY MM and DD parts ok)
     * MYSQL_TIMESTAMP_DATETIME Full timestamp MYSQL_TIMESTAMP_ERROR Timestamp
     * with wrong values. All elements in l_time is set to 0 RETURN VALUES 0 -
     * Ok 1 - Error
     */
    public static boolean strToDatetime(String strori, int length, MySQLTime lTime, long flags,
                                        MySQLTimeStatus status) {
        /* Skip space at start */
        String str = strori.trim();
        long fieldLength, yearLength = 0;
        long[] date = new long[MAX_DATE_PARTS];
        long addHours = 0;
        final char[] chars = str.toCharArray();
        int lastFieldPos = 0;
        int[] formatPosition;
        boolean foundDelimiter = false, foundSpace = false;
        int fracPos, fracLen;

        myTimeStatusInit(status);

        if (str.isEmpty() || !Ctype.isDigit(str.charAt(0))) {
            status.setWarnings(MYSQL_TIME_WARN_TRUNCATED);
            lTime.setTimeType(MySQLTimestampType.MYSQL_TIMESTAMP_NONE);
            return true;
        }

        boolean isInternalFormat = false;
        /*
         * This has to be changed if want to activate different timestamp
     * formats
     */
        formatPosition = INTERNAL_FORMAT_POSITIONS;

    /*
     * Calculate number of digits in first part. If length= 8 or >= 14 then
     * year is of format YYYY. (YYYY-MM-DD, YYYYMMDD, YYYYYMMDDHHMMSS)
     */
        int pos;
        int end = str.length();
        for (pos = 0; pos != end && (Ctype.isDigit(chars[pos]) || chars[pos] == 'T'); pos++) {
            //do nothing
        }

        long digits = (long) pos;
        long startLoop = 0; /* Start of scan loop */

        int[] dateLen = new int[MAX_DATE_PARTS];
        dateLen[formatPosition[0]] = 0; /* Length of year field */
        if (pos == end || chars[pos] == '.') {
    /* Found date in internal format (only numbers like YYYYMMDD) */
            yearLength = (digits == 4 || digits == 8 || digits >= 14) ? 4 : 2;
            fieldLength = yearLength;
            isInternalFormat = true;
            formatPosition = INTERNAL_FORMAT_POSITIONS;
        } else {
            if (formatPosition[0] >= 3) /* If year is after HHMMDD */ {
    /*
     * If year is not in first part then we have to determinate if
     * we got a date field or a datetime field. We do this by
     * checking if there is two numbers separated by space in the
     * input.
     */
                while (pos < end && !Ctype.spaceChar(chars[pos]))
                    pos++;
                while (pos < end && !Ctype.isDigit(chars[pos]))
                    pos++;
                if (pos == end) {
                    if ((flags & TIME_DATETIME_ONLY) != 0) {
                        status.setWarnings(MYSQL_TIME_WARN_TRUNCATED);
                        lTime.setTimeType(MySQLTimestampType.MYSQL_TIMESTAMP_NONE);
                        return true; /* Can't be a full datetime */
                    }
    /* Date field. Set hour, minutes and seconds to 0 */
                    date[0] = date[1] = date[2] = date[3] = date[4] = 0;
                    startLoop = 5; /* Start with first date part */
                }
            }

            fieldLength = formatPosition[0] == 0 ? 4 : 2;
        }

    /*
     * Only allow space in the first "part" of the datetime field and: -
     * after days, part seconds - before and after AM/PM (handled by code
     * later)
     *
     * 2003-03-03 20:00:20 AM 20:00:20.000000 AM 03-03-2000
     */
        long i = Math.max((long) formatPosition[0], (long) formatPosition[1]);
        if (i < (long) formatPosition[2])
            i = (long) formatPosition[2];
        long allowSpace = ((1 << i) | (1 << formatPosition[6]));
        allowSpace &= (1 | 2 | 4 | 8 | 64);

        long notZeroDate = 0;
        int strindex = 0;
        for (i = startLoop; i < MAX_DATE_PARTS - 1 && strindex != end && Ctype.isDigit(chars[strindex]); i++) {
            final int start = strindex;
            int tmpValue = chars[strindex++] - '0';

    /*
     * Internal format means no delimiters; every field has a fixed
     * width. Otherwise, we scan until we find a delimiter and discard
     * leading zeroes -- except for the microsecond part, where leading
     * zeroes are significant, and where we never process more than six
     * digits.
     */
            boolean scanUntilDelim = !isInternalFormat && ((i != formatPosition[6]));

            while (strindex != end && Ctype.isDigit(chars[strindex]) && (scanUntilDelim || (--fieldLength != 0))) {
                tmpValue = tmpValue * 10 + (chars[strindex] - '0');
                strindex++;
            }
            dateLen[(int) i] = (strindex - start);
            if (tmpValue > 999999) /* Impossible date part */ {
                status.setWarnings(MYSQL_TIME_WARN_TRUNCATED);
                lTime.setTimeType(MySQLTimestampType.MYSQL_TIMESTAMP_NONE);
                return true;
            }
            date[(int) i] = tmpValue;
            notZeroDate |= tmpValue;

    /* Length of next field */
            fieldLength = formatPosition[(int) i + 1] == 0 ? 4 : 2;

            if ((lastFieldPos = strindex) == end) {
                i++; /* Register last found part */
                break;
            }
    /* Allow a 'T' after day to allow CCYYMMDDT type of fields */
            if (i == formatPosition[2] && chars[strindex] == 'T') {
                strindex++; /* ISO8601: CCYYMMDDThhmmss */
                continue;
            }
            if (i == formatPosition[5]) /* Seconds */ {
                if (chars[strindex] == '.') /* Followed by part seconds */ {
                    strindex++;
    /*
     * Shift last_field_pos, so '2001-01-01 00:00:00.' is
     * treated as a valid value
     */
                    lastFieldPos = strindex;
                    fieldLength = 6; /* 6 digits */
                }
                continue;
            }
            while (strindex != end && (Ctype.isPunct(chars[strindex]) || Ctype.spaceChar(chars[strindex]))) {
                if (Ctype.spaceChar(chars[strindex])) {
                    if ((allowSpace & (1 << i)) == 0) {
                        status.setWarnings(MYSQL_TIME_WARN_TRUNCATED);
                        lTime.setTimeType(MySQLTimestampType.MYSQL_TIMESTAMP_NONE);
                        return true;
                    }
                    foundSpace = true;
                }
                strindex++;
                foundDelimiter = true; /* Should be a 'normal' date */
            }
    /* Check if next position is AM/PM */
            if (i == formatPosition[6]) /* Seconds, time for AM/PM */ {
                i++; /* Skip AM/PM part */
                if (formatPosition[7] != 255) /* If using AM/PM */ {
                    if (strindex + 2 <= end && (chars[strindex + 1] == 'M' || chars[strindex + 1] == 'm')) {
                        if (chars[strindex] == 'p' || chars[strindex] == 'P')
                            addHours = 12;
                        else if (chars[strindex] != 'a' || chars[strindex] != 'A')
                            continue; /* Not AM/PM */
                        strindex += 2; /* Skip AM/PM */
    /* Skip space after AM/PM */
                        while (strindex != end && Ctype.spaceChar(chars[strindex]))
                            strindex++;
                    }
                }
            }
            lastFieldPos = strindex;
        }
        if (foundDelimiter && !foundSpace && (flags & TIME_DATETIME_ONLY) != 0) {
            status.setWarnings(MYSQL_TIME_WARN_TRUNCATED);
            lTime.setTimeType(MySQLTimestampType.MYSQL_TIMESTAMP_NONE);
            return true; /* Can't be a datetime */
        }

        strindex = lastFieldPos;

        final long numberOfFields = i - startLoop;
        while (i < MAX_DATE_PARTS) {
            dateLen[(int) i] = 0;
            date[(int) i++] = 0;
        }

        if (!isInternalFormat) {
            yearLength = dateLen[formatPosition[0]];
            if (yearLength == 0) /* Year must be specified */ {
                status.setWarnings(MYSQL_TIME_WARN_TRUNCATED);
                lTime.setTimeType(MySQLTimestampType.MYSQL_TIMESTAMP_NONE);
                return true;
            }

            lTime.setYear(date[formatPosition[0]]);
            lTime.setMonth(date[formatPosition[1]]);
            lTime.setDay(date[formatPosition[2]]);
            lTime.setHour(date[formatPosition[3]]);
            lTime.setMinute(date[formatPosition[4]]);
            lTime.setSecond(date[formatPosition[5]]);

            fracPos = formatPosition[6];
            fracLen = dateLen[fracPos];
            status.setFractionalDigits(fracLen);
            if (fracLen < 6)
                date[fracPos] *= MySQLcom.LOG_10_INT[DATETIME_MAX_DECIMALS - fracLen];
            lTime.setSecondPart(date[fracPos]);

            if (formatPosition[7] != 255) {
                if (lTime.getHour() > 12) {
                    status.setWarnings(MYSQL_TIME_WARN_TRUNCATED);
                    lTime.setZeroTime(MySQLTimestampType.MYSQL_TIMESTAMP_ERROR);
                    return true;
                }
                lTime.setHour(lTime.getHour() % 12 + addHours);
            }
        } else {
            lTime.setYear(date[0]);
            lTime.setMonth(date[1]);
            lTime.setDay(date[2]);
            lTime.setHour(date[3]);
            lTime.setMinute(date[4]);
            lTime.setSecond(date[5]);
            if (dateLen[6] < 6)
                date[6] *= MySQLcom.LOG_10_INT[DATETIME_MAX_DECIMALS - dateLen[6]];
            lTime.setSecondPart(date[6]);
            status.setFractionalDigits(dateLen[6]);
        }
        lTime.setNeg(false);

        if (yearLength == 2 && notZeroDate != 0)
            lTime.setYear(lTime.getYear() + (lTime.getYear() < YY_PART_YEAR ? 2000 : 1900));

    /*
     * Set time_type before check_datetime_range(), as the latter relies on
     * initialized time_type value.
     */
        lTime.setTimeType((numberOfFields <= 3 ?
                MySQLTimestampType.MYSQL_TIMESTAMP_DATE : MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME));

        if (numberOfFields < 3 || checkDatetimeRange(lTime)) {
    /*
     * Only give warning for a zero date if there is some garbage after
     */
            if (notZeroDate == 0) /* If zero date */ {
                for (; strindex != end; strindex++) {
                    if (!Ctype.spaceChar(chars[strindex])) {
                        notZeroDate = 1; /* Give warning */
                        break;
                    }
                }
            }
            status.setWarnings(status.getWarnings() | (notZeroDate != 0 ? MYSQL_TIME_WARN_TRUNCATED : MYSQL_TIME_WARN_ZERO_DATE));
            lTime.setZeroTime(MySQLTimestampType.MYSQL_TIMESTAMP_ERROR);
            return true;
        }

        LongPtr lptmp = new LongPtr(0);
        boolean bcheckdate = checkDate(lTime, notZeroDate != 0, flags, lptmp);
        status.setWarnings((int) lptmp.get());
        if (bcheckdate) {
            lTime.setZeroTime(MySQLTimestampType.MYSQL_TIMESTAMP_ERROR);
            return true;
        }

    /* Scan all digits left after microseconds */
        if (status.getFractionalDigits() == 6 && strindex != end) {
            if (Ctype.isDigit(chars[strindex])) {
    /*
     * We don't need the exact nanoseconds value. Knowing the first
     * digit is enough for rounding.
     */
                status.setNanoseconds(100L * (chars[strindex++] - '0'));
                for (; strindex != end && Ctype.isDigit(chars[strindex]); strindex++) {
                    //block
                }
            }
        }

        for (; strindex != end; strindex++) {
            if (!Ctype.spaceChar(chars[strindex])) {
                status.setWarnings(MYSQL_TIME_WARN_TRUNCATED);
                break;
            }
        }

        return false;
    }

    /*
     * Convert a time string to a MYSQL_TIME struct.
     *
     * SYNOPSIS strToTime() str A string in full TIMESTAMP format or [-] DAYS
     * [H]H:MM:SS, [H]H:MM:SS, [M]M:SS, [H]HMMSS, [M]MSS or [S]S There may be an
     * optional [.second_part] after seconds length Length of str l_time Store
     * result here status Conversion status
     *
     * status.warning is set to: MYSQL_TIME_WARN_TRUNCATED flag if the input
     * string was cut during conversion, and/or MYSQL_TIME_WARN_OUT_OF_RANGE
     * flag, if the value is out of range.
     *
     * NOTES Because of the extra days argument, this function can only work
     * with times where the time arguments are in the above order.
     *
     * RETURN 0 ok 1 error
     */
    public static boolean strToTime(String str, int length, MySQLTime lTime, MySQLTimeStatus status) {
        long[] date = new long[5];
        int pos = 0, end = length;
        final char[] chars = str.toCharArray();
        myTimeStatusInit(status);
        lTime.setNeg(false);
        for (; pos != end && Ctype.spaceChar(chars[pos]); pos++)
            length--;
        if (pos != end && chars[pos] == '-') {
            lTime.setNeg(true);
            pos++;
            length--;
        }
        if (pos == end)
            return true;

    /* Check first if this is a full TIMESTAMP */
        if (length >= 12) { /* Probably full timestamp */
            strToDatetime(str.substring(pos), length, lTime, (TIME_FUZZY_DATE | TIME_DATETIME_ONLY), status);
            if (lTime.getTimeType().compareTo(MySQLTimestampType.MYSQL_TIMESTAMP_ERROR) >= 0)
                return lTime.getTimeType() == MySQLTimestampType.MYSQL_TIMESTAMP_ERROR;
            myTimeStatusInit(status);
        }

    /* Not a timestamp. Try to get this as a DAYS_TO_SECOND string */
        long value;
        for (value = 0; pos != end && Ctype.isDigit(chars[pos]); pos++)
            value = value * 10L + (long) (chars[pos] - '0');

        if (value > LONG_MAX)
            return true;

    /* Skip all space after 'days' */
        int endOfDays = pos;
        for (; pos != end && Ctype.spaceChar(chars[pos]); pos++) {
            //do nothing
        }

        int state = 0;
        boolean gotofractional = false;
        if (end - pos > 1 && pos != endOfDays && Ctype.isDigit(chars[pos])) { /* Found days part */
            date[0] = value;
            state = 1; /* Assume next is hours */
        } else if ((end - pos) > 1 && chars[pos] == TIME_SEPARATOR && Ctype.isDigit(chars[pos + 1])) {
            date[0] = 0; /* Assume we found hours */
            date[1] = value;
            state = 2;
            pos++; /* skip ':' */
        } else {
    /* String given as one number; assume HHMMSS format */
            date[0] = 0;
            date[1] = (value / 10000);
            date[2] = (value / 100 % 100);
            date[3] = (value % 100);
            state = 4;
            gotofractional = true;
        }
        if (!gotofractional) {
    /* Read hours, minutes and seconds */
            for (; ; ) {
                for (value = 0; pos != end && Ctype.isDigit(chars[pos]); pos++)
                    value = value * 10L + (long) (chars[pos] - '0');
                date[state++] = value;
                if (state == 4 || (end - pos) < 2 || chars[pos] != TIME_SEPARATOR || !Ctype.isDigit(chars[pos + 1]))
                    break;
                pos++; /* Skip time_separator (':') */
            }

            if (state != 4) { /* Not HH:MM:SS */
                // TODO
    /*
     * Fix the date to assume that seconds was given if
     * (!found_hours && !found_days) { size_t len= sizeof(long) *
     * (state - 1); memmove((uchar*) (date+4) - len, (uchar*)
     * (date+state) - len, len); memset(date, 0,
     * sizeof(long)*(4-state)); } else memset((date+state), 0,
     * sizeof(long)*(4-state));
     */
            }
        }

    /* Get fractional second part */
        if ((end - pos) >= 2 && chars[pos] == '.' && Ctype.isDigit(chars[pos + 1])) {
            int fieldLength = 5;
            pos++;
            value = (chars[pos] - '0');
            while (++pos != end && Ctype.isDigit(chars[pos])) {
                if (fieldLength-- > 0)
                    value = value * 10 + (chars[pos] - '0');
            }
            if (fieldLength >= 0) {
                status.setFractionalDigits(DATETIME_MAX_DECIMALS - fieldLength);
                if (fieldLength > 0)
                    value *= MySQLcom.LOG_10_INT[fieldLength];
            } else {
    /* Scan digits left after microseconds */
                status.setFractionalDigits(6);
                status.setNanoseconds(100L * (chars[pos - 1] - '0'));
                for (; pos != end && Ctype.isDigit(chars[pos]); pos++) {
                    //block
                }
            }
            date[4] = value;
        } else if ((end - pos) == 1 && chars[pos] == '.') {
            pos++;
            date[4] = 0;
        } else
            date[4] = 0;

    /* Check for exponent part: E<gigit> | E<sign><digit> */
    /* (may occur as result of %g formatting of time value) */
        if ((end - pos) > 1 && (chars[pos] == 'e' || chars[pos] == 'E') &&
                (Ctype.isDigit(chars[pos + 1]) || ((chars[pos + 1] == '-' || chars[pos + 1] == '+') &&
                        (end - pos) > 2 && Ctype.isDigit(chars[pos + 2]))))
            return true;

        if (INTERNAL_FORMAT_POSITIONS[7] != 255) {
    /* Read a possible AM/PM */
            while (pos != end && Ctype.spaceChar(chars[pos]))
                pos++;
            if (pos + 2 <= end && (chars[pos + 1] == 'M' || chars[pos + 1] == 'm')) {
                if (chars[pos] == 'p' || chars[pos] == 'P') {
                    str += 2;
                    date[1] = date[1] % 12 + 12;
                } else if (chars[pos] == 'a' || chars[pos] == 'A')
                    str += 2;
            }
        }

    /* Integer overflow checks */
        if (date[0] > LONG_MAX || date[1] > LONG_MAX || date[2] > LONG_MAX || date[3] > LONG_MAX || date[4] > LONG_MAX)
            return true;

        lTime.setYear(0); /* For protocol::store_time */
        lTime.setMonth(0);

        lTime.setDay(0);
        lTime.setHour(date[1] + date[0] * 24); /* Mix days and hours */

        lTime.setMinute(date[2]);
        lTime.setSecond(date[3]);
        lTime.setSecondPart(date[4]);
        lTime.setTimeType(MySQLTimestampType.MYSQL_TIMESTAMP_TIME);

        if (checkTimeMmssffRange(lTime)) {
            status.setWarnings(status.getWarnings() | MYSQL_TIME_WARN_OUT_OF_RANGE);
            return true;
        }

    /* Adjust the value into supported MYSQL_TIME range */
        // adjust_time_range(l_time, &status.warnings);

    /* Check if there is garbage at end of the MYSQL_TIME specification */
        if (pos != end) {
            do {
                if (!Ctype.spaceChar(chars[pos])) {
                    status.setWarnings(status.getWarnings() | MYSQL_TIME_WARN_TRUNCATED);
                    break;
                }
            } while (++pos != end);
        }
        return false;
    }

    /*
     * Convert datetime value specified as number to broken-down TIME
     * representation and form value of DATETIME type as side-effect.
     *
     * SYNOPSIS numberToDatetime() nr - datetime value as number time_res -
     * pointer for structure for broken-down representation flags - flags to use
     * in validating date, as in strToDatetime() was_cut 0 Value ok 1 If value
     * was cut during conversion 2 checkDate(date,flags) considers date invalid
     *
     * DESCRIPTION Convert a datetime value of formats YYMMDD, YYYYMMDD,
     * YYMMDDHHMSS, YYYYMMDDHHMMSS to broken-down MYSQL_TIME representation.
     * Return value in YYYYMMDDHHMMSS format as side-effect.
     *
     * This function also checks if datetime value fits in DATETIME range.
     *
     * RETURN VALUE -1 Timestamp with wrong values anything else DATETIME as
     * integer in YYYYMMDDHHMMSS format Datetime value in YYYYMMDDHHMMSS format.
     *
     * was_cut if return value -1: one of - MYSQL_TIME_WARN_OUT_OF_RANGE -
     * MYSQL_TIME_WARN_ZERO_DATE - MYSQL_TIME_WARN_TRUNCATED otherwise 0.
     */
    public static long numberToDatetime(long nr, MySQLTime timeRes, long flags, LongPtr wasCut) {
        wasCut.set(0);
        timeRes.setZeroTime(MySQLTimestampType.MYSQL_TIMESTAMP_DATE);

        if (nr == 0 || nr >= 10000101000000L) {
            timeRes.setTimeType(MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME);
            if (nr > 99999999999999L) /* 9999-99-99 99:99:99 */ {
                wasCut.set(MYSQL_TIME_WARN_OUT_OF_RANGE);
                return -1;
            }
            return numberToDatetimeOk(nr, timeRes, flags, wasCut);
        }
        if (nr < 101) {
            wasCut.set(MYSQL_TIME_WARN_TRUNCATED);
            return -1;
        }
        if (nr <= (YY_PART_YEAR - 1) * 10000L + 1231L) {
            nr = (nr + 20000000L) * 1000000L; /* YYMMDD, year: 2000-2069 */
            return numberToDatetimeOk(nr, timeRes, flags, wasCut);
        }
        if (nr < (YY_PART_YEAR) * 10000L + 101L) {
            wasCut.set(MYSQL_TIME_WARN_TRUNCATED);
            return -1;
        }
        if (nr <= 991231L) {
            nr = (nr + 19000000L) * 1000000L; /* YYMMDD, year: 1970-1999 */
            return numberToDatetimeOk(nr, timeRes, flags, wasCut);
        }
    /*
     * Though officially we support DATE values from 1000-01-01 only, one
     * can easily insert a value like 1-1-1. So, for consistency reasons
     * such dates are allowed when TIME_FUZZY_DATE is set.
     */
        if (nr < 10000101L && (flags & TIME_FUZZY_DATE) == 0) {
            wasCut.set(MYSQL_TIME_WARN_TRUNCATED);
            return -1;
        }
        if (nr <= 99991231L) {
            nr = nr * 1000000L;
            return numberToDatetimeOk(nr, timeRes, flags, wasCut);
        }
        if (nr < 101000000L) {
            wasCut.set(MYSQL_TIME_WARN_TRUNCATED);
            return -1;
        }

        timeRes.setTimeType(MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME);

        if (nr <= (YY_PART_YEAR - 1) * (10000000000L) + (1231235959L)) {
            nr = nr + (20000000000000L); /* YYMMDDHHMMSS, 2000-2069 */
            return numberToDatetimeOk(nr, timeRes, flags, wasCut);
        }
        if (nr < YY_PART_YEAR * (10000000000L) + 101000000L) {
            wasCut.set(MYSQL_TIME_WARN_TRUNCATED);
            return -1;
        }
        if (nr <= 991231235959L)
            nr = nr + 19000000000000L; /* YYMMDDHHMMSS, 1970-1999 */
        return numberToDatetimeOk(nr, timeRes, flags, wasCut);
    }

    /**
     * helper goto ok
     *
     * @param nr
     * @param timeRes
     * @param flags
     * @param wasCut
     * @return
     */
    private static long numberToDatetimeOk(long nr, MySQLTime timeRes, long flags, LongPtr wasCut) {
        long part1 = nr / (1000000L);
        timeRes.setYear((int) (part1 / 10000L));
        part1 %= 10000L;
        timeRes.setMonth((int) part1 / 100);
        timeRes.setDay((int) part1 % 100);

        long part2 = nr - part1 * (1000000L);
        timeRes.setHour((int) (part2 / 10000L));
        part2 %= 10000L;
        timeRes.setMinute((int) part2 / 100);
        timeRes.setSecond((int) part2 % 100);

        if (!checkDatetimeRange(timeRes) && !checkDate(timeRes, (nr != 0), flags, wasCut))
            return nr;

    /* Don't want to have was_cut get set if NO_ZERO_DATE was violated. */
        if (nr == 0 && (flags & TIME_NO_ZERO_DATE) != 0)
            return -1;
        wasCut.set(MYSQL_TIME_WARN_TRUNCATED);
        return -1;
    }

    /**
     * Convert number to TIME
     *
     * @param nr       Number to convert.
     * @param ltime    ltime Variable to convert to.
     * @param warnings warnings Warning vector.
     * @retval false OK
     * @retval true No. is out of range
     */
    private static boolean numberToTime(long nr, MySQLTime ltime, LongPtr warnings) {
        if (nr > TIME_MAX_VALUE) {
    /* For huge numbers try full DATETIME, like strToTime does. */
            if (nr >= 10000000000L) /* '0001-00-00 00-00-00' */ {
                long warningsBackup = warnings.get();
                if (numberToDatetime(nr, ltime, 0, warnings) != (-1))
                    return false;
                warnings.set(warningsBackup);
            }
            setMaxTime(ltime, false);
            warnings.set(warnings.get() | MYSQL_TIME_WARN_OUT_OF_RANGE);
            return true;
        } else if (nr < -TIME_MAX_VALUE) {
            setMaxTime(ltime, true);
            warnings.set(warnings.get() | MYSQL_TIME_WARN_OUT_OF_RANGE);
            return true;
        }
        ltime.setNeg((nr < 0));
        if (ltime.isNeg()) {
            nr = -nr;
        }
        if (nr % 100 >= 60 || nr / 100 % 100 >= 60) /* Check hours and minutes */ {
            ltime.setZeroTime(MySQLTimestampType.MYSQL_TIMESTAMP_TIME);
            warnings.set(warnings.get() | MYSQL_TIME_WARN_OUT_OF_RANGE);
            return true;
        }
        ltime.setTimeType(MySQLTimestampType.MYSQL_TIMESTAMP_TIME);
        ltime.setDay(0);
        ltime.setMonth(0);
        ltime.setYear(0);
        timeSetHhmmss(ltime, nr);
        ltime.setSecondPart(0);
        return false;
    }

    public static long timeToUlonglongDatetime(final MySQLTime myTime) {
        return ((myTime.getYear() * 10000L + myTime.getMonth() * 100 + myTime.getDay()) * (1000000) +
                myTime.getHour() * 10000L + myTime.getMinute() * 100L + myTime.getSecond());
    }

    public static long timeToUlonglongDate(final MySQLTime myTime) {
        return myTime.getYear() * 10000L + myTime.getMonth() * 100L + myTime.getDay();
    }

    public static long timeToUlonglongTime(final MySQLTime myTime) {
        return myTime.getHour() * 10000L + myTime.getMinute() * 100L + myTime.getSecond();
    }

    public static long timeToUlonglong(final MySQLTime myTime) {
        if (myTime.getTimeType() == MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME) {
            return timeToUlonglongDatetime(myTime);
        } else if (myTime.getTimeType() == MySQLTimestampType.MYSQL_TIMESTAMP_DATE) {
            return timeToUlonglongDate(myTime);
        } else if (myTime.getTimeType() == MySQLTimestampType.MYSQL_TIMESTAMP_TIME) {
            return timeToUlonglongTime(myTime);
        } else if (myTime.getTimeType() == MySQLTimestampType.MYSQL_TIMESTAMP_NONE || myTime.getTimeType() == MySQLTimestampType.MYSQL_TIMESTAMP_ERROR) {
            return 0;
        } else {
            assert (false);
        }
        return 0;
    }

    public static long myPackedTimeGetIntPart(long x) {
        return x >> 24;
    }

    public static long myPackedTimeGetFracPart(long x) {
        return ((x) % (1L << 24));
    }

    public static long myPackedTimeMake(long i, long f) {
        return (i << 24) + f;
    }

    public static long myPackedTimeMakeInt(long i) {
        return i << 24;
    }

    /**
     * Convert year to packed numeric date representation. Packed value for YYYY
     * is the same to packed value for date YYYY-00-00.
     */
    public static long yearToLonglongDatetimePacked(long year) {
        long ymd = ((year * 13) << 5);
        return myPackedTimeMakeInt(ymd << 17);
    }

    /**
     * Convert datetime to packed numeric datetime representation.
     *
     * @param ltime The value to convert.
     * @return Packed numeric representation of ltime.
     */
    public static long timeToLonglongDatetimePacked(final MySQLTime ltime) {
        long ymd = ((ltime.getYear() * 13 + ltime.getMonth()) << 5) | ltime.getDay();
        long hms = (ltime.getHour() << 12) | (ltime.getMinute() << 6) | ltime.getSecond();
        long tmp = myPackedTimeMake(((ymd << 17) | hms), ltime.getSecondPart());
        return ltime.isNeg() ? -tmp : tmp;
    }

    /**
     * Convert date to packed numeric date representation. Numeric packed date
     * format is similar to numeric packed datetime representation, with zero
     * hhmmss part.
     *
     * @param ltime The value to convert.
     * @return Packed numeric representation of ltime.
     */
    public static long timeToLonglongDatePacked(final MySQLTime ltime) {
        long ymd = ((ltime.getYear() * 13 + ltime.getMonth()) << 5) | ltime.getDay();
        return myPackedTimeMakeInt(ymd << 17);
    }

    /**
     * Convert time value to numeric packed representation.
     *
     * @param ltime The value to convert.
     * @return Numeric packed representation.
     */
    public static long timeToLonglongTimePacked(final MySQLTime ltime) {
    /* If month is 0, we mix day with hours: "1 00:10:10" . "24:00:10" */
        long hms = (((ltime.getMonth() != 0 ? 0 : ltime.getDay() * 24) + ltime.getHour()) << 12) | (ltime.getMinute() << 6) | ltime.getSecond();
        long tmp = myPackedTimeMake(hms, ltime.getSecondPart());
        return ltime.isNeg() ? -tmp : tmp;
    }

    /**
     * Convert a temporal value to packed numeric temporal representation,
     * depending on its time_type.
     *
     * @return Packed numeric time/date/datetime representation.
     * @ltime The value to convert.
     */
    public static long timeToLonglongPacked(final MySQLTime ltime) {
        if (ltime.getTimeType() == MySQLTimestampType.MYSQL_TIMESTAMP_DATE) {
            return timeToLonglongDatePacked(ltime);
        } else if (ltime.getTimeType() == MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME) {
            return timeToLonglongDatetimePacked(ltime);
        } else if (ltime.getTimeType() == MySQLTimestampType.MYSQL_TIMESTAMP_TIME) {
            return timeToLonglongTimePacked(ltime);
        } else if (ltime.getTimeType() == MySQLTimestampType.MYSQL_TIMESTAMP_NONE || ltime.getTimeType() == MySQLTimestampType.MYSQL_TIMESTAMP_ERROR) {
            return 0;
        }
        assert (false);
        return 0;
    }

    public static long timeToLonglongPacked(final MySQLTime ltime, FieldTypes type) {
        if (type == FieldTypes.MYSQL_TYPE_TIME) {
            return timeToLonglongTimePacked(ltime);
        } else if (type == FieldTypes.MYSQL_TYPE_DATETIME || type == FieldTypes.MYSQL_TYPE_TIMESTAMP) {
            return timeToLonglongDatetimePacked(ltime);
        } else if (type == FieldTypes.MYSQL_TYPE_DATE) {
            return timeToLonglongDatePacked(ltime);
        } else {
            return timeToLonglongPacked(ltime);
        }
    }

    /**
     * Convert packed numeric datetime representation to MYSQL_TIME.
     *
     * @param ltime ltime The datetime variable to convert to.
     * @param tmp   The packed numeric datetime value.
     */
    public static void timeFromLonglongDatetimePacked(MySQLTime ltime, long tmp) {
        ltime.setNeg((tmp < 0));
        if (ltime.isNeg()) {
            tmp = -tmp;
        }

        ltime.setSecondPart(myPackedTimeGetFracPart(tmp));
        long ymdhms = myPackedTimeGetIntPart(tmp);

        long ymd = ymdhms >> 17;
        long ym = ymd >> 5;

        ltime.setDay(ymd % (1 << 5));
        ltime.setMonth(ym % 13);
        ltime.setYear(ym / 13);

        long hms = ymdhms % (1 << 17);
        ltime.setSecond(hms % (1 << 6));
        ltime.setMinute((hms >> 6) % (1 << 6));
        ltime.setHour((hms >> 12));

        ltime.setTimeType(MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME);
    }

    /**
     * Convert packed numeric date representation to MYSQL_TIME.
     *
     * @param ltime ltime The date variable to convert to.
     * @param tmp   The packed numeric date value.
     */
    public static void timeFromLonglongDatePacked(MySQLTime ltime, long tmp) {
        timeFromLonglongDatetimePacked(ltime, tmp);
        ltime.setTimeType(MySQLTimestampType.MYSQL_TIMESTAMP_DATE);
    }

    /**
     * Convert time packed numeric representation to time.
     *
     * @param ltime The MYSQL_TIME variable to set.
     * @param tmp   The packed numeric representation.
     */
    public static void timeFromLonglongTimePacked(MySQLTime ltime, long tmp) {
        long hms;
        ltime.setNeg((tmp < 0));
        if (ltime.isNeg()) {
            tmp = -tmp;
        }
        hms = myPackedTimeGetIntPart(tmp);
        ltime.setYear(0);
        ltime.setMonth(0);
        ltime.setDay(0);
        ltime.setHour((hms >> 12) % (1 << 10)); /* 10 bits starting at 12th */
        ltime.setMinute((hms >> 6) % (1 << 6)); /* 6 bits starting at 6th */
        ltime.setSecond(hms % (1 << 6)); /* 6 bits starting at 0th */
        ltime.setSecondPart(myPackedTimeGetFracPart(tmp));
        ltime.setTimeType(MySQLTimestampType.MYSQL_TIMESTAMP_TIME);
    }

    /**
     * Set day, month and year from a number
     *
     * @param ltime  MYSQL_TIME variable
     * @param yymmdd Number in YYYYMMDD format
     */
    public static void timeSetYymmdd(MySQLTime ltime, long yymmdd) {
        ltime.setDay((int) (yymmdd % 100));
        ltime.setMonth((int) (yymmdd / 100) % 100);
        ltime.setYear((int) (yymmdd / 10000));
    }

    /**
     * Set hour, minute and secondr from a number
     *
     * @param ltime  MYSQL_TIME variable
     * @param hhmmss Number in HHMMSS format
     */
    public static void timeSetHhmmss(MySQLTime ltime, long hhmmss) {
        ltime.setSecond((int) (hhmmss % 100));
        ltime.setMinute((int) (hhmmss / 100) % 100);
        ltime.setHour((int) (hhmmss / 10000));
    }

    /*
     * Calculate nr of day since year 0 in new date-system (from 1615)
     *
     * SYNOPSIS calc_daynr() year Year (exact 4 digit year, no year conversions)
     * month Month day Day
     *
     * NOTES: 0000-00-00 is a valid date, and will return 0
     *
     * RETURN Days since 0000-00-00
     */

    public static long calcDaynr(long year, long month, long day) {
        long delsum;
        int y = (int) year; /* may be < 0 temporarily */

        if (y == 0 && month == 0)
            return 0; /* Skip errors */
    /* Cast to int to be able to handle month == 0 */
        delsum = (long) (365 * y + 31 * ((int) month - 1) + (int) day);
        if (month <= 2)
            y--;
        else
            delsum -= (long) ((int) month * 4 + 23) / 10;
        int temp = (y / 100 + 1) * 3 / 4;
        assert (delsum + y / 4 - temp >= 0);
        return (delsum + y / 4 - temp);
    } /* calc_daynr */
    /* Calc days in one year. works with 0 <= year <= 99 */

    public static int calcDaysInYear(int year) {
        return ((year & 3) == 0 && (year % 100 != 0 || (year % 400 == 0 && year != 0)) ? 366 : 365);
    }

    public static long calcDaysInYear(long year) {
        return ((year & 3) == 0 && (year % 100 != 0 || (year % 400 == 0 && year != 0)) ? 366 : 365);
    }

    public static long weekMode(int mode) {
        int weekFormat = (mode & 7);
        if ((weekFormat & WEEK_MONDAY_FIRST) == 0)
            weekFormat ^= WEEK_FIRST_WEEKDAY;
        return weekFormat;
    }

    public static String myTimeToStrL(MySQLTime lTime, long dec) {
        String stime = String.format("%s%02d:%02d:%02d", (lTime.isNeg() ? "-" : ""), lTime.getHour(), lTime.getMinute(),
                lTime.getSecond());
        if (dec != 0) {
            // TODO: 6 digits after Decimal point
            // String stmp = String.format("%06d", l_time.second_part);
            // stime += "." + stmp.substring(0, (int) dec);
        }
        return stime;
    }

    public static String myDateToStr(MySQLTime mysqlTime) {
        return String.format("%04d-%02d-%02d", mysqlTime.getYear(), mysqlTime.getMonth(), mysqlTime.getDay());
    }

    /**
     * Print a datetime value with an optional fractional part.
     *
     * @return The length of the result string.
     * @l_time The MYSQL_TIME value to print.
     * @to OUT The string pointer to print at.
     */
    public static String myDatetimeToStr(final MySQLTime lTime, long dec) {
        StringPtr ptrtmp = new StringPtr("");
        timeToDatetimeStr(ptrtmp, lTime);
        String res = ptrtmp.get();
        if (dec != 0) {
            // TODO: 6 digits after Decimal point
            // String stmp = String.format("%06d", l_time.second_part);
            // res += "." + stmp.substring(0, (int) dec);
        }
        return res;
    }

    /*
     * Convert struct DATE/TIME/DATETIME value to string using built-in MySQL
     * time conversion formats.
     *
     * SYNOPSIS my_TIME_to_string()
     *
     * NOTE The string must have at least MAX_DATE_STRING_REP_LENGTH bytes
     * reserved.
     */

    public static String myTimeToStr(final MySQLTime lTime, int dec) {
        if (lTime.getTimeType() == MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME) {
            return myDatetimeToStr(lTime, dec);
        } else if (lTime.getTimeType() == MySQLTimestampType.MYSQL_TIMESTAMP_DATE) {
            return myDateToStr(lTime);
        } else if (lTime.getTimeType() == MySQLTimestampType.MYSQL_TIMESTAMP_TIME) {
            return myTimeToStrL(lTime, dec);
        } else if (lTime.getTimeType() == MySQLTimestampType.MYSQL_TIMESTAMP_NONE || lTime.getTimeType() == MySQLTimestampType.MYSQL_TIMESTAMP_ERROR) {
            return null;
        } else {
            return null;
        }
    }

    /*-------------------------My_time.h  end------------------------------*/
    /*-------------------------the other type change to time type----------------------------*/

    /* Rounding functions */

    private static final long[] MSEC_ROUND_ADD = new long[]{500000000, 50000000, 5000000, 500000, 50000, 5000, 0};

    /**
     * Convert decimal value to datetime value with a warning.
     *
     * @param decimal The value to convert from.
     * @param flags   Conversion flags.
     * @return False on success, true on error.
     * @param[out] ltime The variable to convert to.
     */
    public static boolean myDecimalToDatetimeWithWarn(BigDecimal decimal, MySQLTime ltime, long flags) {
        LongPtr warnings = new LongPtr(0);
        String sbd = decimal.toString();
        String[] sbds = sbd.split("\\.");
        long intPart = Long.parseLong(sbds[0]);
        long secondPart = 0;
        if (sbds.length == 2)
            secondPart = Long.parseLong(sbds[1]);
        if (numberToDatetime(intPart, ltime, flags, warnings) == -1) {
            ltime.setZeroTime(MySQLTimestampType.MYSQL_TIMESTAMP_ERROR);
            return true;
        } else if (ltime.getTimeType() == MySQLTimestampType.MYSQL_TIMESTAMP_DATE) {
            /**
             * Generate a warning in case of DATE with fractional part:
             * 20011231.1234 . '2001-12-31' unless the caller does not want the
             * warning: for example, CAST does.
             */
            if (secondPart != 0 && (flags & TIME_NO_DATE_FRAC_WARN) == 0) {
                warnings.set(warnings.get() | MYSQL_TIME_WARN_TRUNCATED);
            }
        } else if ((flags & TIME_NO_NSEC_ROUNDING) == 0) {
            ltime.setSecondPart(secondPart);
        }
        return false;
    }

    /**
     * Round time value to the given precision.
     *
     * @param ltime The value to round.
     * @param dec   Precision.
     * @return False on success, true on error.
     */
    public static boolean myTimeRound(MySQLTime ltime, int dec) {
    /* Add half away from zero */
        boolean rc = timeAddNanosecondsWithRound(ltime, MSEC_ROUND_ADD[dec]);
    /* Truncate non-significant digits */
        myTimeTrunc(ltime, dec);
        return rc;
    }

    /**
     * Round datetime value to the given precision.
     *
     * @param ltime ltime The value to round.
     * @param dec   Precision.
     * @return False on success, true on error.
     */
    public static boolean myDatetimeRound(MySQLTime ltime, int dec) {
        assert (dec <= DATETIME_MAX_DECIMALS);
    /* Add half away from zero */
        boolean rc = datetimeAddNanosecondsWithRound(ltime, MSEC_ROUND_ADD[dec]);
    /* Truncate non-significant digits */
        myTimeTrunc(ltime, dec);
        return rc;
    }

    public static boolean dateAddInterval(MySQLTime ltime, SQLIntervalUnit intType, Interval interval) {
        long period, sign;

        ltime.setNeg(false);

        sign = (interval.isNeg() ? -1 : 1);

        if (intType == SQLIntervalUnit.SECOND ||
                intType == SQLIntervalUnit.SECOND_MICROSECOND ||
                intType == SQLIntervalUnit.MICROSECOND ||
                intType == SQLIntervalUnit.MINUTE ||
                intType == SQLIntervalUnit.HOUR ||
                intType == SQLIntervalUnit.MINUTE_MICROSECOND ||
                intType == SQLIntervalUnit.MINUTE_SECOND ||
                intType == SQLIntervalUnit.HOUR_MICROSECOND ||
                intType == SQLIntervalUnit.HOUR_SECOND ||
                intType == SQLIntervalUnit.HOUR_MINUTE ||
                intType == SQLIntervalUnit.DAY_MICROSECOND ||
                intType == SQLIntervalUnit.DAY_SECOND ||
                intType == SQLIntervalUnit.DAY_MINUTE ||
                intType == SQLIntervalUnit.DAY_HOUR) {
            long microseconds, extraSec;
            ltime.setTimeType(MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME); // Return
            // full
            // date
            microseconds = ltime.getSecondPart() + sign * interval.getSecondPart();
            extraSec = microseconds / 1000000L;
            microseconds = microseconds % 1000000L;

            long sec = ((ltime.getDay() - 1) * 3600 * 24L + ltime.getHour() * 3600 + ltime.getMinute() * 60 + ltime.getSecond() +
                    sign * (interval.getDay() * 3600 * 24L + interval.getHour() * 3600 + interval.getMinute() * (60) + interval.getSecond())) +
                    extraSec;
            if (microseconds < 0) {
                microseconds += (1000000L);
                sec--;
            }
            long days = sec / (3600 * (24));
            sec -= days * 3600 * (24);
            if (sec < 0) {
                days--;
                sec += 3600 * 24;
            }
            ltime.setSecondPart(microseconds);
            ltime.setSecond((sec % 60));
            ltime.setMinute((sec / 60 % 60));
            ltime.setHour((sec / 3600));
            long daynr = calcDaynr(ltime.getYear(), ltime.getMonth(), 1) + days;
    /* Day number from year 0 to 9999-12-31 */
            if (daynr > MAX_DAY_NUMBER)
                return true;
            LongPtr ptrYear = new LongPtr(ltime.getYear());
            LongPtr ptrMonth = new LongPtr(ltime.getMonth());
            LongPtr ptrDay = new LongPtr(ltime.getDay());
            getDateFromDaynr(daynr, ptrYear, ptrMonth, ptrDay);
            ltime.setYear(ptrYear.get());
            ltime.setMonth(ptrMonth.get());
            ltime.setDay(ptrDay.get());
        } else if (intType == SQLIntervalUnit.DAY || intType == SQLIntervalUnit.WEEK) {
            period = (calcDaynr(ltime.getYear(), ltime.getMonth(), ltime.getDay()) + sign * interval.getDay());
    /* Daynumber from year 0 to 9999-12-31 */
            if (period > MAX_DAY_NUMBER)
                return true;
            LongPtr ptrYear = new LongPtr(ltime.getYear());
            LongPtr ptrMonth = new LongPtr(ltime.getMonth());
            LongPtr ptrDay = new LongPtr(ltime.getDay());
            getDateFromDaynr(period, ptrYear, ptrMonth, ptrDay);
            ltime.setYear(ptrYear.get());
            ltime.setMonth(ptrMonth.get());
            ltime.setDay(ptrDay.get());

        } else if (intType == SQLIntervalUnit.YEAR) {
            ltime.setYear(ltime.getYear() + sign * interval.getYear());
            if (ltime.getYear() >= 10000)
                return true;
            if (ltime.getMonth() == 2 && ltime.getDay() == 29 && calcDaysInYear(ltime.getYear()) != 366)
                ltime.setDay(28); // Was leap-year

        } else if (intType == SQLIntervalUnit.YEAR_MONTH || intType == SQLIntervalUnit.QUARTER || intType == SQLIntervalUnit.MONTH) {
            period = (ltime.getYear() * 12 + sign * interval.getYear() * 12 + ltime.getMonth() - 1 + sign * interval.getMonth());
            if (period >= 120000L)
                return true;
            ltime.setYear((period / 12));
            ltime.setMonth((period % 12L) + 1);
    /* Adjust day if the new month doesn't have enough days */
            if (ltime.getDay() > DAYS_IN_MONTH[(int) ltime.getMonth() - 1]) {
                ltime.setDay(DAYS_IN_MONTH[(int) ltime.getMonth() - 1]);
                if (ltime.getMonth() == 2 && calcDaysInYear(ltime.getYear()) == 366)
                    ltime.setDay(ltime.getDay() + 1); // Leap-year
            }

        } else {
            return true;
        }

        return false; // Ok

    }

    /**
     * Convert double value to datetime value with a warning.
     *
     * @param db    The value to convert from.
     * @param flags Conversion flags.
     * @return False on success, true on error.
     * @param[out] ltime The variable to convert to.
     */
    public static boolean myDoubleToDatetimeWithWarn(double db, MySQLTime ltime, long flags) {
        LongPtr warnings = new LongPtr(0);
        String sbd = String.valueOf(db);
        String[] sbds = sbd.split("\\.");
        long intPart = Long.parseLong(sbds[0]);
        long secondPart = 0;
        if (sbds.length == 2)
            secondPart = Long.parseLong(sbds[1]);
        if (numberToDatetime(intPart, ltime, flags, warnings) == -1) {
            ltime.setZeroTime(MySQLTimestampType.MYSQL_TIMESTAMP_ERROR);
            return true;
        } else if (ltime.getTimeType() == MySQLTimestampType.MYSQL_TIMESTAMP_DATE) {
            /**
             * Generate a warning in case of DATE with fractional part:
             * 20011231.1234 . '2001-12-31' unless the caller does not want the
             * warning: for example, CAST does.
             */
            if (secondPart != 0 && (flags & TIME_NO_DATE_FRAC_WARN) == 0) {
                warnings.set(warnings.get() | MYSQL_TIME_WARN_TRUNCATED);
            }
        } else if ((flags & TIME_NO_NSEC_ROUNDING) == 0) {
            ltime.setSecondPart(secondPart);
        }
        return false;
    }

    /**
     * Convert longlong value to datetime value with a warning.
     *
     * @param nr The value to convert from.
     * @return False on success, true on error.
     * @param[out] ltime The variable to convert to.
     */
    public static boolean myLonglongToDatetimeWithWarn(long nr, MySQLTime ltime, long flags) {
        LongPtr warning = new LongPtr(0);
        return numberToDatetime(nr, ltime, flags, warning) == -1;
    }

    public static boolean strToDatetimeWithWarn(String str, MySQLTime ltime, long flags) {
        return strToDatetime(str, str.length(), ltime, flags, new MySQLTimeStatus());
    }

    public static boolean myDecimalToTimeWithWarn(BigDecimal decimal, MySQLTime ltime) {
        LongPtr warnings = new LongPtr(0);
        String sbd = decimal.toString();
        String[] sbds = sbd.split("\\.");
        long intPart = Long.parseLong(sbds[0]);
        long secondPart = 0;
        if (sbds.length == 2)
            secondPart = Long.parseLong(sbds[1]);
        if (numberToTime(intPart, ltime, warnings)) {
            ltime.setZeroTime(MySQLTimestampType.MYSQL_TIMESTAMP_ERROR);
            return true;
        }
        ltime.setSecondPart(secondPart);
        return false;
    }

    public static boolean myDoubleToTimeWithWarn(double db, MySQLTime ltime) {
        LongPtr warnings = new LongPtr(0);
        String sbd = String.valueOf(db);
        String[] sbds = sbd.split("\\.");
        long intPart = Long.parseLong(sbds[0]);
        long secondPart = 0;
        if (sbds.length == 2)
            secondPart = Long.parseLong(sbds[1]);
        if (numberToTime(intPart, ltime, warnings)) {
            ltime.setZeroTime(MySQLTimestampType.MYSQL_TIMESTAMP_ERROR);
            return true;
        }
        ltime.setSecondPart(secondPart);
        return false;
    }

    public static boolean myLonglongToTimeWithWarn(long nr, MySQLTime ltime) {
        LongPtr warning = new LongPtr(0);
        return numberToTime(nr, ltime, warning);
    }

    public static boolean strToTimeWithWarn(String str, MySQLTime ltime) {
        return strToTime(str, str.length(), ltime, new MySQLTimeStatus());
    }

    /**
     * Convert time to datetime.
     * <p>
     * The time value is added to the current datetime value.
     *
     * @param tm  Time value to convert from.
     * @param ltime Datetime value to convert to.
     */
    public static void timeToDatetime(final MySQLTime tm, MySQLTime ltime) {
        java.util.Calendar cal1 = tm.toCalendar();
        java.util.Calendar cal2 = java.util.Calendar.getInstance();
        ltime.setYear(cal2.get(java.util.Calendar.YEAR));
        ltime.setMonth(cal2.get(java.util.Calendar.MONTH));
        ltime.setDay(cal2.get(java.util.Calendar.DAY_OF_MONTH));
        ltime.setHour(cal1.get(java.util.Calendar.HOUR_OF_DAY));
        ltime.setMinute(cal1.get(java.util.Calendar.MINUTE));
        ltime.setSecond(cal1.get(java.util.Calendar.SECOND));
        ltime.setSecondPart(cal1.get(java.util.Calendar.MILLISECOND) * 1000L);
    }

    public static void copy(final MySQLTime tm, MySQLTime ltime) {
        ltime.setYear(tm.getYear());
        ltime.setMonth(tm.getMonth());
        ltime.setDay(tm.getDay());
        ltime.setHour(tm.getHour());
        ltime.setMinute(tm.getMinute());
        ltime.setSecond(tm.getSecond());
        ltime.setSecondPart(tm.getSecondPart());
        ltime.setNeg(tm.isNeg());
        ltime.setTimeType(tm.getTimeType());
    }

    public static void datetimeToTime(MySQLTime ltime) {
        ltime.setDay(0);
        ltime.setMonth(0);
        ltime.setYear(0);
        ltime.setTimeType(MySQLTimestampType.MYSQL_TIMESTAMP_TIME);
    }

    public static void datetimeToDate(MySQLTime ltime) {
        ltime.setSecondPart(0);
        ltime.setSecond(0);
        ltime.setMinute(0);
        ltime.setHour(0);
        ltime.setTimeType(MySQLTimestampType.MYSQL_TIMESTAMP_DATE);
    }

    public static void dateToDatetime(MySQLTime ltime) {
        ltime.setTimeType(MySQLTimestampType.MYSQL_TIMESTAMP_DATETIME);
    }

    public static long timeToUlonglongDatetimeRound(final MySQLTime ltime) {
        // Catch simple cases
        if (ltime.getSecondPart() < 500000)
            return timeToUlonglongDatetime(ltime);
        if (ltime.getSecond() < 59)
            return timeToUlonglongDatetime(ltime) + 1;
        return timeToUlonglongDatetime(ltime); // TIME_microseconds_round(ltime);
    }

    public static long timeToUlonglongTimeRound(final MySQLTime ltime) {
        if (ltime.getSecondPart() < 500000)
            return timeToUlonglongTime(ltime);
        if (ltime.getSecond() < 59)
            return timeToUlonglongTime(ltime) + 1;
        // Corner case e.g. 'hh:mm:59.5'. Proceed with slower method.
        return timeToUlonglongTime(ltime);
    }

    public static double timeToDoubleDatetime(final MySQLTime ltime) {
        return (double) timeToUlonglongDatetime(ltime) + timeMicroseconds(ltime);
    }

    public static double timeToDoubleTime(final MySQLTime ltime) {
        return (double) timeToUlonglongTime(ltime) + timeMicroseconds(ltime);
    }

    public static double timeToDouble(final MySQLTime ltime) {
        return (double) timeToUlonglong(ltime) + timeMicroseconds(ltime);
    }

    public static long timeToTimestamp(final MySQLTime t) {
        long timestamp = t.toCalendar().getTimeInMillis() / 1000;

        return timestamp;
    }

    public static void timeFromLonglongPacked(MySQLTime ltime, FieldTypes type, long packedValue) {
        if (type == FieldTypes.MYSQL_TYPE_TIME) {
            timeFromLonglongTimePacked(ltime, packedValue);

        } else if (type == FieldTypes.MYSQL_TYPE_DATE) {
            timeFromLonglongDatePacked(ltime, packedValue);

        } else if (type == FieldTypes.MYSQL_TYPE_DATETIME || type == FieldTypes.MYSQL_TYPE_TIMESTAMP) {
            timeFromLonglongDatetimePacked(ltime, packedValue);

        } else {
            assert (false);
            ltime.setZeroTime(MySQLTimestampType.MYSQL_TIMESTAMP_ERROR);

        }
    }

    /**
     * Unpack packed numeric temporal value to date/time value and then convert
     * to decimal representation.
     *
     * @param type        MySQL field type.
     * @param packedValue Packed numeric temporal representation.
     * @return A decimal value in on of the following formats, depending on
     * type: YYYYMMDD, hhmmss.ffffff or YYMMDDhhmmss.ffffff.
     */
    public static BigDecimal myDecimalFromDatetimePacked(FieldTypes type, long packedValue) {
        MySQLTime ltime = new MySQLTime();
        if (type == FieldTypes.MYSQL_TYPE_TIME) {
            timeFromLonglongTimePacked(ltime, packedValue);
            return time2MyDecimal(ltime);
        } else if (type == FieldTypes.MYSQL_TYPE_DATE) {
            timeFromLonglongDatePacked(ltime, packedValue);
            return ulonglong2decimal(timeToUlonglongDate(ltime));
        } else if (type == FieldTypes.MYSQL_TYPE_DATETIME || type == FieldTypes.MYSQL_TYPE_TIMESTAMP) {
            timeFromLonglongDatetimePacked(ltime, packedValue);
            return date2MyDecimal(ltime);
        } else {
            assert (false);
            return ulonglong2decimal(0);
        }
    }

    public static long longlongFromDatetimePacked(FieldTypes type, long packedValue) {
        MySQLTime ltime = new MySQLTime();
        if (type == FieldTypes.MYSQL_TYPE_TIME) {
            timeFromLonglongTimePacked(ltime, packedValue);
            return timeToUlonglongTime(ltime);
        } else if (type == FieldTypes.MYSQL_TYPE_DATE) {
            timeFromLonglongDatePacked(ltime, packedValue);
            return timeToUlonglongDate(ltime);
        } else if (type == FieldTypes.MYSQL_TYPE_DATETIME || type == FieldTypes.MYSQL_TYPE_TIMESTAMP) {
            timeFromLonglongDatetimePacked(ltime, packedValue);
            return timeToUlonglongDatetime(ltime);
        } else {
            return 0;
        }
    }

    public static double doubleFromDatetimePacked(FieldTypes type, long packedValue) {
        long result = longlongFromDatetimePacked(type, packedValue);
        return result + ((double) myPackedTimeGetFracPart(packedValue)) / 1000000;
    }

    /**
     * Convert time value to my_decimal in format hhmmss.ffffff
     *
     * @param ltime Date value to convert from.
     */
    public static BigDecimal time2MyDecimal(final MySQLTime ltime) {
        String stmp = String.format("%02d%02d%02d.%06d", ltime.getHour(), ltime.getMinute(), ltime.getSecond(), ltime.getSecondPart());
        return new BigDecimal(stmp);
    }

    /**
     * Convert datetime value to my_decimal in format YYYYMMDDhhmmss.ffffff
     *
     * @param ltime Date value to convert from.
     */
    public static BigDecimal date2MyDecimal(final MySQLTime ltime) {
        String stmp = String.format("%04d%02d%02d%02d%02d%02d.%06d", ltime.getYear(), ltime.getMonth(), ltime.getDay(), ltime.getHour(),
                ltime.getMinute(), ltime.getSecond(), ltime.getSecondPart());
        return new BigDecimal(stmp);
    }
    /* Functions to handle periods */

    public static long convertPeriodToMonth(long period) {
        long a, b;
        if (period == 0)
            return 0L;
        if ((a = period / 100) < YY_PART_YEAR)
            a += 2000;
        else if (a < 100)
            a += 1900;
        b = period % 100;
        return a * 12 + b - 1;
    }

    public static long convertMonthToPeriod(long month) {
        long year;
        if (month == 0L)
            return 0L;
        if ((year = month / 12) < 100) {
            year += (year < YY_PART_YEAR) ? 2000 : 1900;
        }
        return year * 100 + month % 12 + 1;
    }
    /* Calc weekday from daynr */

    /* Returns 0 for monday, 1 for tuesday .... */

    public static int calcWeekday(long daynr, boolean sundayFirstDayOfWeek) {
        return ((int) ((daynr + 5L + (sundayFirstDayOfWeek ? 1L : 0L)) % 7));
    }

    /*
     * The bits in week_format 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 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 releveant 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.
     */

    public static long calcWeek(MySQLTime lTime, long weekBehaviour, LongPtr year) {
        long days;
        long daynr = calcDaynr(lTime.getYear(), lTime.getMonth(), lTime.getDay());
        long firstDaynr = calcDaynr(lTime.getYear(), 1, 1);
        boolean mondayFirst = (weekBehaviour & WEEK_MONDAY_FIRST) != 0;
        boolean weekYear = (weekBehaviour & WEEK_YEAR) != 0;
        boolean firstWeekday = (weekBehaviour & WEEK_FIRST_WEEKDAY) != 0;

        long weekday = calcWeekday(firstDaynr, !mondayFirst);
        year.set(lTime.getYear());

        if (lTime.getMonth() == 1 && lTime.getDay() <= 7 - weekday) {
            if (!weekYear && ((firstWeekday && weekday != 0) || (!firstWeekday && weekday >= 4)))
                return 0;
            weekYear = true;
            year.decre();
            firstDaynr -= (days = calcDaysInYear(year.get()));
            weekday = (weekday + 53 * 7 - days) % 7;
        }

        if ((firstWeekday && weekday != 0) || (!firstWeekday && weekday >= 4))
            days = daynr - (firstDaynr + (7 - weekday));
        else
            days = daynr - (firstDaynr - weekday);

        if (weekYear && days >= 52 * 7) {
            weekday = (weekday + calcDaysInYear(year.get())) % 7;
            if ((!firstWeekday && weekday < 4) || (firstWeekday && weekday == 0)) {
                year.incre();
                return 1;
            }
        }
        return days / 7 + 1;
    }

    /**
     * Convert a datetime MYSQL_TIME representation to corresponding
     * "struct timeval" value.
     * <p>
     * Things like '0000-01-01', '2000-00-01', '2000-01-00' (i.e. incomplete
     * date) return error.
     * <p>
     * Things like '0000-00-00 10:30:30' or '0000-00-00 00:00:00.123456' (i.e.
     * empty date with non-empty time) return error.
     * <p>
     * Zero datetime '0000-00-00 00:00:00.000000' is allowed and is mapper to
     * {tv_sec=0, tv_usec=0}.
     * <p>
     * Note: In case of error, tm value is not initialized.
     * <p>
     * Note: "warnings" is not initialized to zero, so new warnings are added to
     * the old ones. Caller must make sure to initialize "warnings".
     *
     * @return
     * @param[in] thd current thd
     * @param[in] ltime datetime value
     * @param[out] tm timeval value
     * @param[out] warnings pointer to warnings vector
     * @retval false on success
     * @retval true on error
     */
    public static boolean datetimeToTimeval(final MySQLTime ltime, Timeval tm) {
        return checkDate(ltime, ltime.isNonZeroDate(), TIME_NO_ZERO_IN_DATE, new LongPtr(0)) ||
                datetimeWithNoZeroInDateToTimeval(ltime, tm);
    }
    /* Change a daynr to year, month and day */

    /* Daynr 0 is returned as date 00.00.00 */

    public static void getDateFromDaynr(long daynr, LongPtr retYear, LongPtr retMonth, LongPtr retDay) {
        int year, temp, leapDay, dayOfYear, daysInYear;
        int monthPos;
        if (daynr <= 365L || daynr >= 3652500) { /* Fix if wrong daynr */
            retYear.set(0);
            retMonth.set(0);
            retDay.set(0);
        } else {
            year = (int) (daynr * 100 / 36525L);
            temp = (((year - 1) / 100 + 1) * 3) / 4;
            dayOfYear = (int) (daynr - (long) year * 365L) - (year - 1) / 4 + temp;
            while (dayOfYear > (daysInYear = calcDaysInYear(year))) {
                dayOfYear -= daysInYear;
                (year)++;
            }
            leapDay = 0;
            if (daysInYear == 366) {
                if (dayOfYear > 31 + 28) {
                    dayOfYear--;
                    if (dayOfYear == 31 + 28)
                        leapDay = 1; /* Handle leapyears leapday */
                }
            }
            retMonth.set(1);
            for (monthPos = 0; dayOfYear > DAYS_IN_MONTH[monthPos]; dayOfYear -= DAYS_IN_MONTH[monthPos++], retMonth.incre()) {
                //do nothing
            }
            retYear.set(year);
            retDay.set(dayOfYear + leapDay);
        }
    }
    /*-----------------------helper method---------------------------*/

    private static boolean checkTimeMmssffRange(final MySQLTime ltime) {
        return ltime.getMinute() >= 60 || ltime.getSecond() >= 60 || ltime.getSecondPart() > 999999;
    }

    private static void setMaxTime(MySQLTime tm, boolean neg) {
        tm.setZeroTime(MySQLTimestampType.MYSQL_TIMESTAMP_TIME);
        setMaxHhmmss(tm);
        tm.setNeg(neg);
    }

    /**
     * Set hour, minute and second of a MYSQL_TIME variable to maximum time
     * value. Unlike set_max_time(), does not touch the other structure members.
     */
    private static void setMaxHhmmss(MySQLTime tm) {
        tm.setHour(TIME_MAX_HOUR);
        tm.setMinute(TIME_MAX_MINUTE);
        tm.setSecond(TIME_MAX_SECOND);
    }

    private static double timeMicroseconds(final MySQLTime ltime) {
        return (double) ltime.getSecondPart() / 1000000;
    }

    private static BigDecimal ulonglong2decimal(long from) {
        BigInteger bi = MySQLcom.getUnsignedLong(from);
        return new BigDecimal(bi);
    }

    private static int timeToDatetimeStr(StringPtr ptr, final MySQLTime ltime) {
        char[] res = new char[19];
        int index = 0;
        long temp;
    /* Year */
        temp = ltime.getYear() / 100;
        res[index++] = (char) ('0' + temp / 10);
        res[index++] = (char) ('0' + temp % 10);
        temp = ltime.getYear() % 100;
        res[index++] = (char) ('0' + temp / 10);
        res[index++] = (char) ('0' + temp % 10);
        res[index++] = '-';
    /* Month */
        temp = ltime.getMonth();
        long temp2 = temp / 10;
        temp = temp - temp2 * 10;
        res[index++] = (char) ('0' + (char) (temp2));
        res[index++] = (char) ('0' + (char) (temp));
        res[index++] = '-';
    /* Day */
        temp = ltime.getDay();
        temp2 = temp / 10;
        temp = temp - temp2 * 10;
        res[index++] = (char) ('0' + (char) (temp2));
        res[index++] = (char) ('0' + (char) (temp));
        res[index++] = ' ';
    /* Hour */
        temp = ltime.getHour();
        temp2 = temp / 10;
        temp = temp - temp2 * 10;
        res[index++] = (char) ('0' + (char) (temp2));
        res[index++] = (char) ('0' + (char) (temp));
        res[index++] = ':';
    /* Minute */
        temp = ltime.getMinute();
        temp2 = temp / 10;
        temp = temp - temp2 * 10;
        res[index++] = (char) ('0' + (char) (temp2));
        res[index++] = (char) ('0' + (char) (temp));
        res[index++] = ':';
    /* Second */
        temp = ltime.getSecond();
        temp2 = temp / 10;
        temp = temp - temp2 * 10;
        res[index++] = (char) ('0' + (char) (temp2));
        res[index++] = (char) ('0' + (char) (temp));
        ptr.set(new String(res));
        return 19;
    }

    /**
     * Check datetime, date, or normalized time (i.e. time without days) range.
     *
     * @param ltime Datetime value.
     * @returns
     * @retval FALSE on success
     * @retval TRUE on error
     */
    private static boolean checkDatetimeRange(MySQLTime ltime) {
    /*
     * In case of MYSQL_TIMESTAMP_TIME hour value can be up to
     * TIME_MAX_HOUR. In case of MYSQL_TIMESTAMP_DATETIME it cannot be
     * bigger than 23.
     */
        return ltime.getYear() > 9999 || ltime.getMonth() > 12 || ltime.getDay() > 31 || ltime.getMinute() > 59 || ltime.getSecond() > 59 ||
                ltime.getSecondPart() > 999999 ||
                (ltime.getHour() > (ltime.getTimeType() == MySQLTimestampType.MYSQL_TIMESTAMP_TIME ? TIME_MAX_HOUR : 23));
    }

    /* Calc days in one year. works with 0 <= year <= 99 */

    private static void myTimeStatusInit(MySQLTimeStatus status) {
        status.setWarnings(0);
        status.setNanoseconds(0);
        status.setFractionalDigits(0);
    }

    /**
     * Convert a datetime MYSQL_TIME representation to corresponding
     * "struct timeval" value.
     * <p>
     * ltime must previously be checked for TIME_NO_ZERO_IN_DATE. Things like
     * '0000-01-01', '2000-00-01', '2000-01-00' are not allowed and asserted.
     * <p>
     * Things like '0000-00-00 10:30:30' or '0000-00-00 00:00:00.123456' (i.e.
     * empty date with non-empty time) return error.
     * <p>
     * Zero datetime '0000-00-00 00:00:00.000000' is allowed and is mapper to
     * {tv_sec=0, tv_usec=0}.
     * <p>
     * Note: In case of error, tm value is not initialized.
     * <p>
     * Note: "warnings" is not initialized to zero, so new warnings are added to
     * the old ones. Caller must make sure to initialize "warnings".
     *
     * @return
     * @param[in] thd current thd
     * @param[in] ltime datetime value
     * @param[out] tm timeval value
     * @param[out] warnings pointer to warnings vector
     * @retval false on success
     * @retval true on error
     */
    private static boolean datetimeWithNoZeroInDateToTimeval(final MySQLTime ltime, Timeval tm) {
        if (ltime.getMonth() == 0) /* Zero date */ {
            assert (ltime.getYear() == 0 && ltime.getDay() == 0);
            if (ltime.isNonZeroTime()) {
    /*
     * Return error for zero date with non-zero time, e.g.:
     * '0000-00-00 10:20:30' or '0000-00-00 00:00:00.123456'
     */
                return true;
            }
            tm.setTvUsec(0);
            tm.setTvSec(0); // '0000-00-00 00:00:00.000000'
            return false;
        }

        tm.setTvSec(timeToTimestamp(ltime));
        tm.setTvUsec(ltime.getSecondPart());
        return false;
    }

    private static boolean timeAddNanosecondsWithRound(MySQLTime ltime, long nanoseconds) {
    /* We expect correct input data */
        assert (nanoseconds < 1000000000);
        assert (!checkTimeMmssffRange(ltime));

        if (nanoseconds < 500)
            return false;

        ltime.setSecondPart(ltime.getSecondPart() + (nanoseconds + 500) / 1000);
        if (ltime.getSecondPart() < 1000000)
            return false;

        ltime.setSecondPart(ltime.getSecondPart() % 1000000);
        if (ltime.getSecond() < 59) {
            ltime.setSecond(ltime.getSecond() + 1);
            return false;
        }

        ltime.setSecond(0);
        if (ltime.getMinute() < 59) {
            ltime.setMinute(ltime.getMinute() + 1);
            return false;
        }
        ltime.setMinute(0);
        ltime.setHour(ltime.getHour() + 1);

    /*
     * We can get '838:59:59.000001' at this point, which is bigger than the
     * maximum possible value '838:59:59.000000'. Checking only "hour > 838"
     * is not enough. Do full adjust_time_range().
     */
        // adjust_time_range(ltime, warnings);
        return false;
    }

    /**
     * Add nanoseconds to a datetime value with rounding.
     *
     * @param ltime       MYSQL_TIME variable to add to.
     * @param nanoseconds Nanosecons value.
     * @retval False on success, true on error.
     */
    private static boolean datetimeAddNanosecondsWithRound(MySQLTime ltime, long nanoseconds) {
        assert (nanoseconds < 1000000000);
        if (nanoseconds < 500)
            return false;

        ltime.setSecondPart(ltime.getSecondPart() + (nanoseconds + 500) / 1000);
        if (ltime.getSecondPart() < 1000000)
            return false;

        ltime.setSecondPart(ltime.getSecondPart() % 1000000);
        Interval interval = new Interval();
        interval.setSecond(1);

        return dateAddInterval(ltime, SQLIntervalUnit.SECOND, interval);
    }

    private static void myTimeTrunc(MySQLTime ltime, int decimals) {
        ltime.setSecondPart(ltime.getSecondPart() - myTimeFractionRemainder(ltime.getSecondPart(), decimals));
    }

    private static long myTimeFractionRemainder(long nr, int decimals) {
        return nr % (long) MySQLcom.LOG_10_INT[DATETIME_MAX_DECIMALS - decimals];
    }

    /*
     * Calculate difference between two datetime values as seconds
     * microseconds.
     *
     * SYNOPSIS calc_time_diff() l_time1 - TIME/DATE/DATETIME value l_time2 -
     * TIME/DATE/DATETIME value seconds_out - Out parameter where difference
     * between l_time1 and l_time2 in seconds is stored. microseconds_out- Out
     * parameter where microsecond part of difference between l_time1 and
     * l_time2 is stored.
     *
     * NOTE This function calculates difference between l_time1 and l_time2
     * absolute values. So one should set l_sign and correct result if he want
     * to take signs into account (i.e. for MYSQL_TIME values).
     *
     * RETURN VALUES Returns sign of difference. 1 means negative result 0 means
     * positive result
     */
    public static boolean calcTimeDiff(MySQLTime lTime1, MySQLTime lTime2, int lSign, LongPtr secondsOut,
                                       LongPtr microsecondsOut) {
        long days;
        boolean neg;
        long microseconds;

    /*
     * We suppose that if first argument is MYSQL_TIMESTAMP_TIME the second
     * argument should be TIMESTAMP_TIME also. We should check it before
     * calc_time_diff call.
     */
        if (lTime1.getTimeType() == MySQLTimestampType.MYSQL_TIMESTAMP_TIME) // Time
            // value
            days = (long) lTime1.getDay() - lSign * (long) lTime2.getDay();
        else {
            days = calcDaynr(lTime1.getYear(), lTime1.getMonth(), lTime1.getDay());
            if (lTime2.getTimeType() == MySQLTimestampType.MYSQL_TIMESTAMP_TIME)
                days -= lSign * (long) lTime2.getDay();
            else
                days -= lSign * calcDaynr(lTime2.getYear(), lTime2.getMonth(), lTime2.getDay());
        }

        microseconds = ((long) days * SECONDS_IN_24H +
                (long) (lTime1.getHour() * 3600L + lTime1.getMinute() * 60L + lTime1.getSecond()) -
                lSign * (long) (lTime2.getHour() * 3600L + lTime2.getMinute() * 60L + lTime2.getSecond())) * (1000000) +
                (long) lTime1.getSecondPart() - lSign * (long) lTime2.getSecondPart();

        neg = false;
        if (microseconds < 0) {
            microseconds = -microseconds;
            neg = true;
        }
        secondsOut.set(microseconds / 1000000L);
        microsecondsOut.set((long) (microseconds % 1000000L));
        return neg;

        // boolean ret = false;
        // try {
        // Calendar cal1 = l_time1.toCalendar();
        // Calendar cal2 = l_time2.toCalendar();
        // long milsecond1 = cal1.getTimeInMillis();
        // long milsecond2 = cal2.getTimeInMillis();
        // if (milsecond1 < milsecond2)
        // ret = true;
        // long abs = Math.abs(milsecond1 - milsecond2);
        // seconds_out.set(abs / 1000);
        // microseconds_out.set(abs % 1000 * 1000);
        // return ret;
        // } catch (Exception e) {
        // return ret;
        // }
    }

    public static void calcTimeFromSec(MySQLTime to, long seconds, long microseconds) {
        // to.neg is not cleared, it may already be set to a useful value
        to.setTimeType(MySQLTimestampType.MYSQL_TIMESTAMP_TIME);
        to.setYear(0);
        to.setMonth(0);
        to.setDay(0);
        to.setHour((long) (seconds / 3600L));
        long tSeconds = (long) (seconds % 3600L);
        to.setMinute(tSeconds / 60L);
        to.setSecond(tSeconds % 60L);
        to.setSecondPart(microseconds);
    }

    /**
     * Convert a string to a interval value.
     * <p>
     * To make code easy, allow interval objects without separators.
     */

    public static boolean getIntervalValue(Item arg, SQLIntervalUnit unit, StringPtr strValue,
                                           Interval interval) {
        long[] array = new long[5];
        long value = 0;
        //        int int_type = unit.ordinal();

        if (unit == SQLIntervalUnit.SECOND && arg.getDecimals() != 0) {
            BigDecimal decimalValue = arg.valDecimal();
            if (decimalValue == null)
                return false;

            boolean neg = decimalValue.compareTo(BigDecimal.ZERO) < 0;
            if (!neg) {
                interval.setNeg(false);
                interval.setSecond(decimalValue.longValue());
                interval.setSecondPart((long) ((decimalValue.doubleValue() - interval.getSecond()) * 1000000));
            } else {
                interval.setNeg(true);
                interval.setSecond(-decimalValue.longValue());
                interval.setSecondPart((long) ((-decimalValue.doubleValue() - interval.getSecond()) * 1000000));
            }
            return false;
        } else if (unit == SQLIntervalUnit.YEAR || unit == SQLIntervalUnit.QUARTER ||
                unit == SQLIntervalUnit.MONTH || unit == SQLIntervalUnit.WEEK || unit == SQLIntervalUnit.DAY ||
                unit == SQLIntervalUnit.HOUR || unit == SQLIntervalUnit.MINUTE ||
                unit == SQLIntervalUnit.SECOND || unit == SQLIntervalUnit.MICROSECOND) {
            value = arg.valInt().longValue();
            if (arg.isNullValue())
                return true;
            if (value < 0) {
                interval.setNeg(true);
                value = -value;
            }
        }

        BoolPtr negPtr = new BoolPtr(interval.isNeg());
        if (unit == SQLIntervalUnit.YEAR) {
            interval.setYear(value);

        } else if (unit == SQLIntervalUnit.QUARTER) {
            interval.setMonth((value * 3));

        } else if (unit == SQLIntervalUnit.MONTH) {
            interval.setMonth(value);

        } else if (unit == SQLIntervalUnit.WEEK) {
            interval.setDay((value * 7));

        } else if (unit == SQLIntervalUnit.DAY) {
            interval.setDay(value);

        } else if (unit == SQLIntervalUnit.HOUR) {
            interval.setHour(value);

        } else if (unit == SQLIntervalUnit.MINUTE) {
            interval.setMinute(value);

        } else if (unit == SQLIntervalUnit.SECOND) {
            interval.setSecond(value);

        } else if (unit == SQLIntervalUnit.MICROSECOND) {
            interval.setSecondPart(value);

        } else if (unit == SQLIntervalUnit.YEAR_MONTH) {
            if (getIntervalInfo(arg, strValue, negPtr, 2, array, false))
                return true;
            interval.setYear(array[0]);
            interval.setMonth(array[1]);

        } else if (unit == SQLIntervalUnit.DAY_HOUR) {
            if (getIntervalInfo(arg, strValue, negPtr, 2, array, false))
                return true;
            interval.setDay(array[0]);
            interval.setHour(array[1]);

        } else if (unit == SQLIntervalUnit.DAY_MINUTE) {
            if (getIntervalInfo(arg, strValue, negPtr, 3, array, false))
                return true;
            interval.setDay(array[0]);
            interval.setHour(array[1]);
            interval.setMinute(array[2]);

        } else if (unit == SQLIntervalUnit.DAY_SECOND) {
            if (getIntervalInfo(arg, strValue, negPtr, 4, array, false))
                return true;
            interval.setDay(array[0]);
            interval.setHour(array[1]);
            interval.setMinute(array[2]);
            interval.setSecond(array[3]);

        } else if (unit == SQLIntervalUnit.HOUR_MINUTE) {
            if (getIntervalInfo(arg, strValue, negPtr, 2, array, false))
                return true;
            interval.setHour(array[0]);
            interval.setMinute(array[1]);

        } else if (unit == SQLIntervalUnit.HOUR_SECOND) {
            if (getIntervalInfo(arg, strValue, negPtr, 3, array, false))
                return true;
            interval.setHour(array[0]);
            interval.setMinute(array[1]);
            interval.setSecond(array[2]);

        } else if (unit == SQLIntervalUnit.MINUTE_SECOND) {
            if (getIntervalInfo(arg, strValue, negPtr, 2, array, false))
                return true;
            interval.setMinute(array[0]);
            interval.setSecond(array[1]);

        } else if (unit == SQLIntervalUnit.DAY_MICROSECOND) {
            if (getIntervalInfo(arg, strValue, negPtr, 5, array, true))
                return true;
            interval.setDay(array[0]);
            interval.setHour(array[1]);
            interval.setMinute(array[2]);
            interval.setSecond(array[3]);
            interval.setSecondPart(array[4]);

        } else if (unit == SQLIntervalUnit.HOUR_MICROSECOND) {
            if (getIntervalInfo(arg, strValue, negPtr, 4, array, true))
                return true;
            interval.setHour(array[0]);
            interval.setMinute(array[1]);
            interval.setSecond(array[2]);
            interval.setSecondPart(array[3]);

        } else if (unit == SQLIntervalUnit.MINUTE_MICROSECOND) {
            if (getIntervalInfo(arg, strValue, negPtr, 3, array, true))
                return true;
            interval.setMinute(array[0]);
            interval.setSecond(array[1]);
            interval.setSecondPart(array[2]);

        } else if (unit == SQLIntervalUnit.SECOND_MICROSECOND) {
            if (getIntervalInfo(arg, strValue, negPtr, 2, array, true))
                return true;
            interval.setSecond(array[0]);
            interval.setSecondPart(array[1]);

        }
        interval.setNeg(negPtr.get());
        return false;
    }

    /**
     * @param args           item expression which we convert to an ASCII string
     * @param strValue       string buffer
     * @param negPtr         set to true if interval is prefixed by '-'
     * @param count         count of elements in result array
     * @param values        array of results
     * @param transformMsec  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'
     * @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.
     */
    public static boolean getIntervalInfo(Item args, StringPtr strValue, BoolPtr negPtr, int count, long[] values,
                                          boolean transformMsec) {
        String res = args.valStr();
        strValue.set(res);
        if (res == null)
            return true;

        char[] cs = res.toCharArray();
        int str = 0;
        int end = cs.length;

        if (str < end && cs[str] == '-') {
            negPtr.set(true);
            str++;
        }

        while (str < end && !Ctype.isDigit(cs[str]))
            str++;

        long msecLength = 0;
        for (int i = 0; i < count; i++) {
            long value;
            int start = str;
            for (value = 0; str != end && Ctype.isDigit(cs[str]); str++)
                value = value * 10 + (cs[str] - '0');
            msecLength = 6 - (str - start);
            values[i] = value;
            while (str != end && !Ctype.isDigit(cs[str]))
                str++;
            if (str == end && i != count - 1) {
                i++;
    /* Change values[0...i-1] . values[0...count-1] */
                // FIXME
                break;
            }
        }

        if (transformMsec && msecLength > 0)
            values[count - 1] *= (long) MySQLcom.pow10((int) msecLength);

        return (str != end);
    }

    public static boolean secToTime(LLDivT seconds, MySQLTime ltime) {
        ltime.setTimeType(MySQLTimestampType.MYSQL_TIMESTAMP_TIME);

        if (seconds.getQuot() < 0 || seconds.getRem() < 0) {
            ltime.setNeg(true);
            seconds.setQuot(-seconds.getQuot());
            seconds.setRem(-seconds.getRem());
        }

        if (seconds.getQuot() > TIME_MAX_VALUE_SECONDS) {
            setMaxHhmmss(ltime);
            return true;
        }

        ltime.setHour((seconds.getQuot() / 3600));
        int sec = (int) (seconds.getQuot() % 3600);
        ltime.setMinute(sec / 60);
        ltime.setSecond(sec % 60);
        boolean warning = timeAddNanosecondsWithRound(ltime, seconds.getRem());
        return warning;
    }

    /**
     * Extract datetime value to MYSQL_TIME struct from string value according
     * to format string.
     *
     * @param format              date/time format specification
     * @param valStr              String to decode
     * @param lTime               Store result here
     * @param cachedTimestampType It uses to get an appropriate warning in the case when the
     *                            value is truncated.
     * @return 1 error
     * @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 Item_func_str_to_date::fix_from_format().
     */
    public static boolean extractDateTime(DateTimeFormat format, String valStr, MySQLTime lTime,
                                          MySQLTimestampType cachedTimestampType, String dateTimeType) {
        int weekday = 0, yearday = 0, daypart = 0;
        int weekNumber = -1;
        BoolPtr error = new BoolPtr(false);
        int strictWeekNumberYear = -1;
        int fracPart;
        boolean usaTime = false;
        boolean sundayFirstNFirstWeekNonIso = false;
        boolean strictWeekNumber = false;
        boolean strictWeekNumberYearType = false;
        int val = 0;
        int valEnd = valStr.length();
        char[] valcs = valStr.toCharArray();
        int ptr = 0;
        int end = format.getFormat().length();
        char[] ptrcs = format.getFormat().toCharArray();

        for (; ptr != end && val != valEnd; ptr++) {

            if (ptrcs[ptr] == '%' && ptr + 1 != end) {
                int valLen;
                int tmp;

                error.set(false);

                valLen = valEnd - val;
                switch (ptrcs[++ptr]) {
    /* Year */
                    case 'Y':
                        tmp = val + Math.min(4, valLen);
                        lTime.setYear(MySQLcom.myStrtoll10(valcs, val, tmp, error).intValue());
                        if (tmp - val <= 2)
                            lTime.setYear(MyTime.year2000Handling(lTime.getYear()));
                        val = tmp;
                        break;
                    case 'y':
                        tmp = val + Math.min(2, valLen);
                        lTime.setYear(MySQLcom.myStrtoll10(valcs, val, tmp, error).intValue());
                        val = tmp;
                        lTime.setYear(MyTime.year2000Handling(lTime.getYear()));
                        break;

                    /* Month */
                    case 'm':
                    case 'c':
                        tmp = val + Math.min(2, valLen);
                        lTime.setMonth(MySQLcom.myStrtoll10(valcs, val, tmp, error).intValue());
                        val = tmp;
                        break;
                    case 'M':
                        lTime.setMonth(MySQLcom.checkWord(MONTH_NAMES, valcs, val, valEnd));
                        if (lTime.getMonth() <= 0) {
                            // logger.warn
                            return true;
                        }
                        break;
                    case 'b':
                        lTime.setMonth(MySQLcom.checkWord(AB_MONTH_NAMES, valcs, val, valEnd));
                        if (lTime.getMonth() <= 0) {
                            // logger.warn
                            return true;
                        }
                        break;
    /* Day */
                    case 'd':
                    case 'e':
                        tmp = val + Math.min(2, valLen);
                        lTime.setDay(MySQLcom.myStrtoll10(valcs, val, tmp, error).intValue());
                        val = tmp;
                        break;
                    case 'D':
                        tmp = val + Math.min(2, valLen);
                        lTime.setDay(MySQLcom.myStrtoll10(valcs, val, tmp, error).intValue());
    /* Skip 'st, 'nd, 'th .. */
                        val = tmp + Math.min((int) (valEnd - tmp), 2);
                        break;

    /* Hour */
                    case 'h':
                    case 'I':
                    case 'l':
                        usaTime = true;
    /* fall through */
                    case 'k':
                    case 'H':
                        tmp = val + Math.min(2, valLen);
                        lTime.setHour(MySQLcom.myStrtoll10(valcs, val, tmp, error).intValue());
                        val = tmp;
                        break;

    /* Minute */
                    case 'i':
                        tmp = val + Math.min(2, valLen);
                        lTime.setMinute(MySQLcom.myStrtoll10(valcs, val, tmp, error).intValue());
                        val = tmp;
                        break;

    /* Second */
                    case 's':
                    case 'S':
                        tmp = val + Math.min(2, valLen);
                        lTime.setSecond(MySQLcom.myStrtoll10(valcs, val, tmp, error).intValue());
                        val = tmp;
                        break;

    /* Second part */
                    case 'f':
                        tmp = valEnd;
                        if (tmp - val > 6)
                            tmp = val + 6;
                        lTime.setSecondPart(MySQLcom.myStrtoll10(valcs, val, tmp, error).intValue());
                        fracPart = 6 - (int) (tmp - val);
                        if (fracPart > 0)
                            lTime.setSecondPart(lTime.getSecondPart() * MySQLcom.LOG_10_INT[fracPart]);
                        val = tmp;
                        break;

    /* AM / PM */
                    case 'p':
                        if (valLen < 2 || !usaTime) {
                            // logger.warn
                            return true;
                        }
                        if (new String(valcs, val, 2).compareTo("PM") == 0)
                            daypart = 12;
                        else if (new String(valcs, val, 2).compareTo("AM") == 0) {
                            {
                                // logger.warn
                                return true;
                            }
                        }
                        val += 2;
                        break;

    /* Exotic things */
                    case 'W':
                        if ((weekday = MySQLcom.checkWord(DAY_NAMES, valcs, val, valEnd)) <= 0) {
                            {
                                // logger.warn
                                return true;
                            }
                        }
                        break;
                    case 'a':
                        if ((weekday = MySQLcom.checkWord(AB_DAY_NAMES, valcs, val, valEnd)) <= 0) {
                            {
                                // logger.warn
                                return true;
                            }
                        }
                        break;
                    case 'w':
                        tmp = val + 1;
                        if ((weekday = MySQLcom.myStrtoll10(valcs, val, tmp, error).intValue()) < 0 || weekday >= 7) {
                            {
                                // logger.warn
                                return true;
                            }
                        }
    /* We should use the same 1 - 7 scale for %w as for %W */
                        if (weekday == 0)
                            weekday = 7;
                        val = tmp;
                        break;
                    case 'j':
                        tmp = val + Math.min(valLen, 3);
                        yearday = MySQLcom.myStrtoll10(valcs, val, tmp, error).intValue();
                        val = tmp;
                        break;

    /* Week numbers */
                    case 'V':
                    case 'U':
                    case 'v':
                    case 'u':
                        sundayFirstNFirstWeekNonIso = (ptrcs[ptr] == 'U' || ptrcs[ptr] == 'V');
                        strictWeekNumber = (ptrcs[ptr] == 'V' || ptrcs[ptr] == 'v');
                        tmp = val + Math.min(valLen, 2);
                        if ((weekNumber = MySQLcom.myStrtoll10(valcs, val, tmp, error).intValue()) < 0 ||
                                (strictWeekNumber && weekNumber == 0) || weekNumber > 53) {
                            {
                                // logger.warn
                                return true;
                            }
                        }
                        val = tmp;
                        break;

    /* Year used with 'strict' %V and %v week numbers */
                    case 'X':
                    case 'x':
                        strictWeekNumberYearType = (ptrcs[ptr] == 'X');
                        tmp = val + Math.min(4, valLen);
                        strictWeekNumberYear = MySQLcom.myStrtoll10(valcs, val, tmp, error).intValue();
                        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 (extractDateTime(TIME_AMPM_FORMAT, new String(valcs, val, valEnd - val), lTime,
                                cachedTimestampType, "time"))
                            return true;
                        break;

    /* Time in 24-hour notation */
                    case 'T':
                        if (extractDateTime(TIME_24_HRS_FORMAT, new String(valcs, val, valEnd - val), lTime,
                                cachedTimestampType, "time"))
                            return true;
                        break;

    /* Conversion specifiers that match classes of characters */
                    case '.':
                        while (val < valEnd && Ctype.isPunct(valcs[val]))
                            val++;
                        break;
                    case '@':
                        while (val < valEnd && Ctype.myIsAlpha(valcs[val]))
                            val++;
                        break;
                    case '#':
                        while (val < valEnd && Ctype.isDigit(valcs[val]))
                            val++;
                        break;
                    default: {
                        // logger.warn
                        return true;
                    }
                }
                if (error.get()) { // Error from MySql_com.myStrtoll10
                    // logger.warn
                    return true;
                }
            } else if (!Ctype.spaceChar(ptrcs[ptr])) {
                if (valcs[val] != ptrcs[ptr]) {
                    // logger.warn
                    return true;
                }
                val++;
            }
        }
        if (usaTime) {
            if (lTime.getHour() > 12 || lTime.getHour() < 1) {
                // logger.warn
                return true;
            }
            lTime.setHour(lTime.getHour() % 12 + daypart);
        }

        if (yearday > 0) {
            long days;
            days = calcDaynr(lTime.getYear(), 1L, 1L) + yearday - 1;
            if (days <= 0 || days > MAX_DAY_NUMBER) {
                // logger.warn
                return true;
            }
            LongPtr yPtr = new LongPtr(lTime.getYear());
            LongPtr mPtr = new LongPtr(lTime.getMonth());
            LongPtr dPtr = new LongPtr(lTime.getDay());
            getDateFromDaynr(days, yPtr, mPtr, dPtr);
            lTime.setYear(yPtr.get());
            lTime.setMonth(mPtr.get());
            lTime.setDay(dPtr.get());
        }

        if (weekNumber >= 0 && weekday != 0) {
            long days;
            long weekdayB;

    /*
     * %V,%v require %X,%x resprectively, %U,%u should be used with %Y
     * and not %X or %x
     */
            if ((strictWeekNumber && (strictWeekNumberYear < 0 ||
                    strictWeekNumberYearType != sundayFirstNFirstWeekNonIso)) ||
                    (!strictWeekNumber && strictWeekNumberYear >= 0)) {
                // logger.warn
                return true;
            }

    /* Number of days since year 0 till 1st Jan of this year */
            days = calcDaynr((strictWeekNumber ? strictWeekNumberYear : lTime.getYear()), 1, 1);
    /* Which day of week is 1st Jan of this year */
            weekdayB = calcWeekday(days, sundayFirstNFirstWeekNonIso);

    /*
     * 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 (sundayFirstNFirstWeekNonIso) {
                days += ((weekdayB == 0) ? 0L : 7L) - weekdayB + (weekNumber - 1) * 7L + weekday % 7;
            } else {
                days += ((weekdayB <= 3) ? 0L : 7L) - weekdayB + (weekNumber - 1) * 7L + (weekday - 1);
            }

            if (days <= 0 || days > MAX_DAY_NUMBER) {
                // logger.warn
                return true;
            }
            LongPtr yPtr = new LongPtr(lTime.getYear());
            LongPtr mPtr = new LongPtr(lTime.getMonth());
            LongPtr dPtr = new LongPtr(lTime.getDay());
            getDateFromDaynr(days, yPtr, mPtr, dPtr);
            lTime.setYear(yPtr.get());
            lTime.setMonth(mPtr.get());
            lTime.setDay(dPtr.get());
        }

        if (lTime.getMonth() > 12 || lTime.getDay() > 31 || lTime.getHour() > 23 || lTime.getMinute() > 59 || lTime.getSecond() > 59) {
            // logger.warn
            return true;
        }

        if (val != valEnd) {
            do {
                if (!Ctype.spaceChar(valcs[val])) {
                    // TS-TODO: extract_date_time is not UCS2 safe
                    //
                    break;
                }
            } while (++val != valEnd);
        }
        return false;

    }

    /**
     * Create a formated date/time value in a string.
     *
     * @return
     */
    public static boolean makeDateTime(DateTimeFormat format, MySQLTime lTime, MySQLTimestampType type,
                                       StringPtr strPtr) {
        int hoursI;
        int weekday;
        int ptr = 0, end;
        char[] formatChars = format.getFormat().toCharArray();
        StringBuilder str = new StringBuilder();
        MyLocale locale = MyLocales.MY_LOCALE_EN_US;

        if (lTime.isNeg())
            str.append('-');

        end = format.getFormat().length();
        for (; ptr != end; ptr++) {
            if (formatChars[ptr] != '%' || ptr + 1 == end)
                str.append(formatChars[ptr]);
            else {
                switch (formatChars[++ptr]) {
                    case 'M':
                        if (lTime.getMonth() == 0) {
                            strPtr.set(str.toString());
                            return true;
                        }
                        str.append(locale.getMonthNames().getTypeNames()[(int) (lTime.getMonth() - 1)]);

                        break;
                    case 'b':
                        if (lTime.getMonth() == 0) {
                            strPtr.set(str.toString());
                            return true;
                        }
                        str.append(locale.getAbMonthNames().getTypeNames()[(int) (lTime.getMonth() - 1)]);
                        break;
                    case 'W':
                        if (type == MySQLTimestampType.MYSQL_TIMESTAMP_TIME ||
                                (lTime.getMonth() == 0 && lTime.getYear() == 0)) {
                            strPtr.set(str.toString());
                            return true;
                        }
                        weekday = MyTime.calcWeekday(MyTime.calcDaynr(lTime.getYear(), lTime.getMonth(), lTime.getDay()), false);
                        str.append(locale.getDayNames().getTypeNames()[weekday]);
                        break;
                    case 'a':
                        if (type == MySQLTimestampType.MYSQL_TIMESTAMP_TIME ||
                                (lTime.getMonth() == 0 && lTime.getYear() == 0)) {
                            strPtr.set(str.toString());
                            return true;
                        }
                        weekday = MyTime.calcWeekday(MyTime.calcDaynr(lTime.getYear(), lTime.getMonth(), lTime.getDay()), false);
                        str.append(locale.getAbDayNames().getTypeNames()[weekday]);
                        break;
                    case 'D':
                        if (type == MySQLTimestampType.MYSQL_TIMESTAMP_TIME) {
                            strPtr.set(str.toString());
                            return true;
                        }
                        str.append(String.format("%01d", lTime.getDay()));
                        if (lTime.getDay() >= 10 && lTime.getDay() <= 19)
                            str.append("th");
                        else {
                            int tmp = (int) (lTime.getDay() % 10);
                            switch (tmp) {
                                case 1:
                                    str.append("st");
                                    break;
                                case 2:
                                    str.append("nd");
                                    break;
                                case 3:
                                    str.append("rd");
                                    break;
                                default:
                                    str.append("th");
                                    break;
                            }
                        }
                        break;
                    case 'Y':
                        str.append(String.format("%04d", lTime.getYear()));
                        break;
                    case 'y':
                        str.append(String.format("%02d", lTime.getYear()));
                        break;
                    case 'm':
                        str.append(String.format("%02d", lTime.getMonth()));
                        break;
                    case 'c':
                        str.append(String.format("%01d", lTime.getMonth()));
                        break;
                    case 'd':
                        str.append(String.format("%02d", lTime.getDay()));
                        break;
                    case 'e':
                        str.append(String.format("%01d", lTime.getDay()));
                        break;
                    case 'f':
                        str.append(String.format("%06d", lTime.getSecondPart()));
                        break;
                    case 'H':
                        str.append(String.format("%02d", lTime.getHour()));
                        break;
                    case 'h':
                    case 'I':
                        hoursI = (int) ((lTime.getHour() % 24 + 11) % 12 + 1);
                        str.append(String.format("%02d", hoursI));
                        break;
                    case 'i': /* minutes */
                        str.append(String.format("%02d", lTime.getMinute()));
                        break;
                    case 'j':
                        if (type == MySQLTimestampType.MYSQL_TIMESTAMP_TIME) {
                            strPtr.set(str.toString());
                            return true;
                        }
                        str.append(String.format("%03d", MyTime.calcDaynr(lTime.getYear(), lTime.getMonth(), lTime.getDay()) -
                                MyTime.calcDaynr(lTime.getYear(), 1, 1) + 1));
                        break;
                    case 'k':
                        str.append(String.format("%01d", lTime.getHour()));
                        break;
                    case 'l':
                        hoursI = (int) ((lTime.getHour() % 24 + 11) % 12 + 1);
                        str.append(String.format("%01d", lTime.getHour()));
                        break;
                    case 'p':
                        hoursI = (int) (lTime.getHour() % 24);
                        str.append(hoursI < 12 ? "AM" : "PM");
                        break;
                    case 'r':
                        String tmpFmt = ((lTime.getHour() % 24) < 12) ? "%02d:%02d:%02d AM" : "%02d:%02d:%02d PM";
                        str.append(String.format(tmpFmt, (lTime.getHour() + 11) % 12 + 1, lTime.getMinute(), lTime.getSecond()));
                        break;
                    case 'S':
                    case 's':
                        str.append(String.format("%02d", lTime.getSecond()));
                        break;
                    case 'T':
                        str.append(String.format("%02d:%02d:%02d", lTime.getHour(), lTime.getMinute(), lTime.getSecond()));
                        break;
                    case 'U':
                    case 'u': {
                        LongPtr year = new LongPtr(0);
                        if (type == MySQLTimestampType.MYSQL_TIMESTAMP_TIME) {
                            strPtr.set(str.toString());
                            return true;
                        }
                        str.append(String.format("%02d", MyTime.calcWeek(lTime,
                                formatChars[ptr] == 'U' ? MyTime.WEEK_FIRST_WEEKDAY : MyTime.WEEK_MONDAY_FIRST, year)));
                    }
                    break;
                    case 'v':
                    case 'V': {
                        LongPtr year = new LongPtr(0);
                        if (type == MySQLTimestampType.MYSQL_TIMESTAMP_TIME) {
                            strPtr.set(str.toString());
                            return true;
                        }
                        str.append(String.format("%02d",
                                MyTime.calcWeek(lTime,
                                        formatChars[ptr] == 'V' ? (MyTime.WEEK_YEAR | MyTime.WEEK_FIRST_WEEKDAY) :
                                                (MyTime.WEEK_YEAR | MyTime.WEEK_MONDAY_FIRST),
                                        year)));
                    }
                    break;
                    case 'x':
                    case 'X': {
                        LongPtr year = new LongPtr(0);
                        if (type == MySQLTimestampType.MYSQL_TIMESTAMP_TIME) {
                            strPtr.set(str.toString());
                            return true;
                        }
                        MyTime.calcWeek(lTime, formatChars[ptr] == 'X' ? MyTime.WEEK_YEAR | MyTime.WEEK_FIRST_WEEKDAY :
                                MyTime.WEEK_YEAR | MyTime.WEEK_MONDAY_FIRST, year);
                        str.append(String.format("%04d", year.get()));
                    }
                    break;
                    case 'w':
                        if (type == MySQLTimestampType.MYSQL_TIMESTAMP_TIME ||
                                (lTime.getMonth() == 0 && lTime.getYear() == 0)) {
                            strPtr.set(str.toString());
                            return true;
                        }
                        weekday = MyTime.calcWeekday(MyTime.calcDaynr(lTime.getYear(), lTime.getMonth(), lTime.getDay()), true);
                        str.append(String.format("%01d", weekday));
                        break;

                    default:
                        str.append(format.getFormat().substring(ptr));
                        break;
                }
            }
        }
        strPtr.set(str.toString());
        return false;
    }
}
