Package arjdbc.util

Source Code of arjdbc.util.DateTimeUtils

/*
* The MIT License
*
* Copyright 2014 Karol Bucek.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package arjdbc.util;

import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.TimeZone;

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyFixnum;
import org.jruby.RubyFloat;
import org.jruby.RubyString;
import org.jruby.RubyTime;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;

import static arjdbc.jdbc.RubyJdbcConnection.getBase;
import static arjdbc.util.StringHelper.decByte;

/**
* Utilities for handling/converting dates and times.
* @author kares
*/
public abstract class DateTimeUtils {

    @SuppressWarnings("deprecation")
    public static ByteList timeToString(final Time time) {
        final ByteList str = new ByteList(8); // hh:mm:ss

        int hours = time.getHours();
        int minutes = time.getMinutes();
        int seconds = time.getSeconds();

        str.append( decByte( hours / 10 ) );
        str.append( decByte( hours % 10 ) );

        str.append( ':' );

        str.append( decByte( minutes / 10 ) );
        str.append( decByte( minutes % 10 ) );

        str.append( ':' );

        str.append( decByte( seconds / 10 ) );
        str.append( decByte( seconds % 10 ) );

        return str;
    }

    @SuppressWarnings("deprecation")
    public static ByteList dateToString(final Date date) {
        final ByteList str = new ByteList(10); // "2000-00-00"

        int year = date.getYear() + 1900;
        int month = date.getMonth() + 1;
        int day = date.getDate();

        str.append( decByte( ( year / 1000 ) % 10 ) );
        str.append( decByte( ( year / 100 ) % 10 ) );
        str.append( decByte( ( year / 10 ) % 10 ) );
        str.append( decByte( year % 10 ) );

        str.append( '-' );

        str.append( decByte( month / 10 ) );
        str.append( decByte( month % 10 ) );

        str.append( '-' );

        str.append( decByte( day / 10 ) );
        str.append( decByte( day % 10 ) );

        return str;
    }

    @SuppressWarnings("deprecation")
    public static ByteList timestampToString(final Timestamp timestamp) {
        final ByteList str = new ByteList(29); // yyyy-mm-dd hh:mm:ss.fffffffff

        int year = timestamp.getYear() + 1900;
        int month = timestamp.getMonth() + 1;
        int day = timestamp.getDate();
        int hours = timestamp.getHours();
        int minutes = timestamp.getMinutes();
        int seconds = timestamp.getSeconds();
        int nanos = timestamp.getNanos();

        str.append( decByte( ( year / 1000 ) % 10 ) );
        str.append( decByte( ( year / 100 ) % 10 ) );
        str.append( decByte( ( year / 10 ) % 10 ) );
        str.append( decByte( year % 10 ) );

        str.append( '-' );

        str.append( decByte( month / 10 ) );
        str.append( decByte( month % 10 ) );

        str.append( '-' );

        str.append( decByte( day / 10 ) );
        str.append( decByte( day % 10 ) );

        if ( hours != 0 || minutes != 0 || seconds != 0 || nanos != 0 ) {
            str.append(' ');

            str.append( decByte( hours / 10 ) );
            str.append( decByte( hours % 10 ) );

            str.append( ':' );

            str.append( decByte( minutes / 10 ) );
            str.append( decByte( minutes % 10 ) );

            str.append( ':' );

            str.append( decByte( seconds / 10 ) );
            str.append( decByte( seconds % 10 ) );

            if ( nanos != 0 ) {
                str.append( '.' );

                int pow = 100000000; // nanos <= 999999999
                for ( int i = 0; i < 8; i++ ) {
                    final int b = nanos / pow;
                    if ( b == 0 ) break; // done (no trailing zeros)
                    str.append( decByte( b % 10 ) );
                    pow = pow / 10;
                }
            }
        }

        return str;
    }

    @SuppressWarnings("deprecation")
    public static IRubyObject newTime(final ThreadContext context, final Time time) {
        //if ( time == null ) return context.nil;
        final int hours = time.getHours();
        final int minutes = time.getMinutes();
        final int seconds = time.getSeconds();
        //final int offset = time.getTimezoneOffset();

        final DateTime dateTime;
        if ( isDefaultTimeZoneUTC(context) ) {
            dateTime = new DateTime(2000, 1, 1, hours, minutes, seconds, 0, DateTimeZone.UTC);
        }
        else {
            dateTime = new DateTime(2000, 1, 1, hours, minutes, seconds, 0);
        }
        return RubyTime.newTime(context.runtime, dateTime);
    }

    @SuppressWarnings("deprecation")
    public static IRubyObject newTime(final ThreadContext context, final Timestamp timestamp) {
        //if ( time == null ) return context.nil;

        final int year = timestamp.getYear() + 1900;
        final int month = timestamp.getMonth() + 1;
        final int day = timestamp.getDate();
        final int hours = timestamp.getHours();
        final int minutes = timestamp.getMinutes();
        final int seconds = timestamp.getSeconds();
        final int nanos = timestamp.getNanos(); // max 999-999-999

        final DateTime dateTime;
        if ( isDefaultTimeZoneUTC(context) ) {
            dateTime = new DateTime(year, month, day, hours, minutes, seconds, 0, DateTimeZone.UTC);
        }
        else {
            dateTime = new DateTime(year, month, day, hours, minutes, seconds, 0);
        }
        return RubyTime.newTime(context.runtime, dateTime, nanos);
    }

    @SuppressWarnings("deprecation")
    public static IRubyObject newTime(final ThreadContext context, final Date date) {
        //if ( time == null ) return context.nil;

        final int year = date.getYear() + 1900;
        final int month = date.getMonth() + 1;
        final int day = date.getDate();

        DateTime dateTime = new DateTime(year, month, day, 0, 0, 0, 0);
        return RubyTime.newTime(context.runtime, dateTime);
    }

    public static Timestamp convertToTimestamp(final RubyFloat value) {
        final Timestamp timestamp = new Timestamp(value.getLongValue() * 1000); // millis

        // for usec we shall not use: ((long) floatValue * 1000000) % 1000
        // if ( usec >= 0 ) timestamp.setNanos( timestamp.getNanos() + usec * 1000 );
        // due doubles inaccurate precision it's better to parse to_s :
        final ByteList strValue = ((RubyString) value.to_s()).getByteList();
        final int dot1 = strValue.lastIndexOf('.') + 1, dot4 = dot1 + 3;
        final int len = strValue.getRealSize() - strValue.getBegin();
        if ( dot1 > 0 && dot4 < len ) { // skip .123 but handle .1234
            final int end = Math.min( len - dot4, 3 );
            CharSequence usecSeq = strValue.subSequence(dot4, end);
            final int usec = Integer.parseInt( usecSeq.toString() );
            if ( usec < 10 ) { // 0.1234 ~> 4
                timestamp.setNanos( timestamp.getNanos() + usec * 100 );
            }
            else if ( usec < 100 ) { // 0.12345 ~> 45
                timestamp.setNanos( timestamp.getNanos() + usec * 10 );
            }
            else { // if ( usec < 1000 ) { // 0.123456 ~> 456
                timestamp.setNanos( timestamp.getNanos() + usec );
            }
        }

        return timestamp;
    }

    public static IRubyObject getTimeInDefaultTimeZone(final ThreadContext context, IRubyObject value) {
        if ( value.respondsTo("to_time") ) {
            value = value.callMethod(context, "to_time");
        }
        final String method = isDefaultTimeZoneUTC(context) ? "getutc" : "getlocal";
        if ( value.respondsTo(method) ) {
            value = value.callMethod(context, method);
        }
        return value;
    }

    public static boolean isDefaultTimeZoneUTC(final ThreadContext context) {
        final String defaultTimeZone = getDefaultTimeZone(context);
        if ( defaultTimeZone.length() != 3 ) return false;
        return "utc".equalsIgnoreCase( defaultTimeZone );
    }

    private static String defaultTimeZone;

    public static String getDefaultTimeZone(final ThreadContext context) {
        String default_timezone = defaultTimeZone;
        if ( default_timezone == null ) {
            final RubyClass base = getBase(context.runtime);
            default_timezone = base.callMethod(context, "default_timezone").toString(); // :utc
            //synchronized (DateTimeUtils.class) { defaultTimeZone = default_timezone; }
        }
        return default_timezone;
    }

    public static double adjustTimeFromDefaultZone(final IRubyObject value) {
        // Time's to_f is : ( millis * 1000 + usec ) / 1_000_000.0
        final double time = value.convertToFloat().getDoubleValue(); // to_f
        // NOTE: MySQL assumes default TZ thus need to adjust to match :
        final int offset = TimeZone.getDefault().getOffset((long) time * 1000);
        // Time's to_f is : ( millis * 1000 + usec ) / 1_000_000.0
        return time - ( offset / 1000.0 );
    }

    public static IRubyObject parseDate(final ThreadContext context, final String str)
        throws IllegalArgumentException {
        final int len = str.length();

        int year; int month; int day;

        int start = nonSpaceIndex(str, 0, len); // Skip leading whitespace
        int end = nonDigitIndex(str, start, len);

        if ( end >= len ) {
            throw new IllegalArgumentException("unexpected date value: '" + str + "'");
        }

        // year
        year = extractIntValue(str, start, end);
        start = end + 1; // Skip '-'

        // month
        end = nonDigitIndex(str, start, len);
        month = extractIntValue(str, start, end);

        //sep = str.charAt(end);
        //if ( sep != '-' ) {
        //    throw new NumberFormatException("expected date to be dash-separated, got '" + sep + "'");
        //}

        start = end + 1; // Skip '-'

        // day of month
        end = nonDigitIndex(str, start, len);
        day = extractIntValue(str, start, end);

        final Ruby runtime = context.runtime;
        return runtime.getClass("Date").
            callMethod(context, "new", new IRubyObject[] {
                RubyFixnum.newFixnum(runtime, year),
                RubyFixnum.newFixnum(runtime, month),
                RubyFixnum.newFixnum(runtime, day)
        });
    }

    public static RubyTime parseDateTime(final ThreadContext context, final String str)
        throws IllegalArgumentException {

        boolean hasDate = false;
        int year = 2000; int month = 1; int day = 1;
        boolean hasTime = false;
        int minute = 0; int hour = 0; int second = 0;
        int nanos = 0;

        DateTimeZone zone = null; boolean bcEra = false;

        // We try to parse these fields in order; all are optional
        // (but some combinations don't make sense, e.g. if you have
        //  both date and time then they must be whitespace-separated).
        // At least one of date and time must be present.

        //   leading whitespace
        //   yyyy-mm-dd
        //   whitespace
        //   hh:mm:ss
        //   whitespace
        //   timezone in one of the formats:  +hh, -hh, +hh:mm, -hh:mm
        //   whitespace
        //   if date is present, an era specifier: AD or BC
        //   trailing whitespace

        final int len = str.length();

        int start = nonSpaceIndex(str, 0, len); // Skip leading whitespace
        int end = nonDigitIndex(str, start, len);
        int num;

        // Possibly read date.
        if ( end < len && str.charAt(end) == '-' ) {
            hasDate = true;

            // year
            year = extractIntValue(str, start, end);
            start = end + 1; // Skip '-'

            // month
            end = nonDigitIndex(str, start, len);
            month = extractIntValue(str, start, end);

            char sep = str.charAt(end);
            if ( sep != '-' ) {
                throw new IllegalArgumentException("expected date to be dash-separated, got '" + sep + "'");
            }

            start = end + 1; // Skip '-'

            // day of month
            end = nonDigitIndex(str, start, len);
            day = extractIntValue(str, start, end);

            start = nonSpaceIndex(str, end, len); // Skip trailing whitespace
        }

        // Possibly read time.
        if ( start < len && Character.isDigit( str.charAt(start) ) ) {
            hasTime = true;

            // hours
            end = nonDigitIndex(str, start, len);
            hour = extractIntValue(str, start, end);

            //sep = str.charAt(end);
            //if ( sep != ':' ) {
            //    throw new IllegalArgumentException("expected time to be colon-separated, got '" + sep + "'");
            //}

            start = end + 1; // Skip ':'

            // minutes
            end = nonDigitIndex(str, start, len);
            minute = extractIntValue(str, start, end);

            //sep = str.charAt(end);
            //if ( sep != ':' ) {
            //    throw new IllegalArgumentException("expected time to be colon-separated, got '" + sep + "'");
            //}

            start = end + 1; // Skip ':'

            // seconds
            end = nonDigitIndex(str, start, len);
            second = extractIntValue(str, start, end);
            start = end;

            // Fractional seconds.
            if ( start < len && str.charAt(start) == '.' ) {
                end = nonDigitIndex(str, start + 1, len); // Skip '.'
                num = extractIntValue(str, start + 1, end);

                for (int numlength = (end - (start+1)); numlength < 9; ++numlength)
                    num *= 10;

                nanos = num;
                start = end;
            }

            start = nonSpaceIndex(str, start, len); // Skip trailing whitespace
        }

        // Possibly read timezone.
        char sep = start < len ? str.charAt(start) : '\0';
        if ( sep == '+' || sep == '-' ) {
            int zoneSign = (sep == '-') ? -1 : 1;
            int hoursOffset, minutesOffset, secondsOffset;

            end = nonDigitIndex(str, start + 1, len);    // Skip +/-
            hoursOffset = extractIntValue(str, start + 1, end);
            start = end;

            if ( start < len && str.charAt(start) == ':' ) {
                end = nonDigitIndex(str, start + 1, len)// Skip ':'
                minutesOffset = extractIntValue(str, start + 1, end);
                start = end;
            } else {
                minutesOffset = 0;
            }

            secondsOffset = 0;
            if ( start < len && str.charAt(start) == ':' ) {
                end = nonDigitIndex(str, start + 1, len)// Skip ':'
                secondsOffset = extractIntValue(str, start + 1, end);
                start = end;
            }

            // Setting offset does not seem to work correctly in all
            // cases.. So get a fresh calendar for a synthetic timezone
            // instead

            int offset = zoneSign * hoursOffset * 60;
            if (offset < 0) {
                offset = offset - Math.abs(minutesOffset);
            } else {
                offset = offset + minutesOffset;
            }
            offset = (offset * 60 + secondsOffset) * 1000;
            zone = DateTimeZone.forOffsetMillis(offset);

            start = nonSpaceIndex(str, start, len); // Skip trailing whitespace
        }

        if ( hasDate && start < len ) {
            final char e1 = str.charAt(start);
            if ( e1 == 'A' && str.charAt(start + 1) == 'D' ) {
                bcEra = false; start += 2;
            }
            else if ( e1 == 'B' && str.charAt(start + 1) == 'C' ) {
                bcEra = true; start += 2;
            }
        }

        if ( start < len ) {
            throw new IllegalArgumentException("trailing junk: '" + str.substring(start, len - start) + "' on '" + str + "'");
        }
        if ( ! hasTime && ! hasDate ) {
            throw new IllegalArgumentException("'"+ str +"' has neither date nor time");
        }

        if ( bcEra ) year = -1 * year;

        if ( zone == null ) {
            zone = isDefaultTimeZoneUTC(context) ? DateTimeZone.UTC : DateTimeZone.getDefault();
        }

        DateTime dateTime = new DateTime(year, month, day, hour, minute, second, 0, zone);
        return RubyTime.newTime(context.runtime, dateTime, nanos);
    }

    @SuppressWarnings("deprecation")
    private static int nonSpaceIndex(final String str, int beg, int len) {
        for ( int i = beg; i < len; i++ ) {
            if ( ! Character.isSpace( str.charAt(i) ) ) return i;
        }
        return len;
    }

    private static int nonDigitIndex(final String str, int beg, int len) {
        for ( int i = beg; i < len; i++ ) {
            if ( ! Character.isDigit( str.charAt(i) ) ) return i;
        }
        return len;
    }

    private static int extractIntValue(final String str, int beg, int end) {
        int n = 0;
        for ( int i = beg; i < end; i++ ) {
            n = 10 * n + ( str.charAt(i) - '0' );
        }
        return n;
    }

}
TOP

Related Classes of arjdbc.util.DateTimeUtils

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.