package cn.emay.sdk.util.json.gson.internal.bind.util; 
 | 
  
 | 
import java.text.ParseException; 
 | 
import java.text.ParsePosition; 
 | 
import java.util.Calendar; 
 | 
import java.util.Date; 
 | 
import java.util.GregorianCalendar; 
 | 
import java.util.Locale; 
 | 
import java.util.TimeZone; 
 | 
  
 | 
/** 
 | 
 * Utilities methods for manipulating dates in iso8601 format. This is much much 
 | 
 * faster and GC friendly than using SimpleDateFormat so highly suitable if you 
 | 
 * (un)serialize lots of date objects. 
 | 
 *  
 | 
 * Supported parse format: 
 | 
 * [yyyy-MM-dd|yyyyMMdd][T(hh:mm[:ss[.sss]]|hhmm[ss[.sss]])]?[Z|[+-]hh[:]mm]] 
 | 
 *  
 | 
 * @see <a href="http://www.w3.org/TR/NOTE-datetime">this specification</a> 
 | 
 */ 
 | 
// Date parsing code from Jackson databind ISO8601Utils.java 
 | 
// https://github.com/FasterXML/jackson-databind/blob/master/src/main/java/com/fasterxml/jackson/databind/util/ISO8601Utils.java 
 | 
public class ISO8601Utils { 
 | 
    /** 
 | 
     * ID to represent the 'UTC' string, default timezone since Jackson 2.7 
 | 
     *  
 | 
     * @since 2.7 
 | 
     */ 
 | 
    private static final String UTC_ID = "UTC"; 
 | 
    /** 
 | 
     * The UTC timezone, prefetched to avoid more lookups. 
 | 
     *  
 | 
     * @since 2.7 
 | 
     */ 
 | 
    private static final TimeZone TIMEZONE_UTC = TimeZone.getTimeZone(UTC_ID); 
 | 
  
 | 
    /* 
 | 
     * /********************************************************** /* Formatting 
 | 
     * /********************************************************** 
 | 
     */ 
 | 
  
 | 
    /** 
 | 
     * Format a date into 'yyyy-MM-ddThh:mm:ssZ' (default timezone, no milliseconds 
 | 
     * precision) 
 | 
     *  
 | 
     * @param date 
 | 
     *            the date to format 
 | 
     * @return the date formatted as 'yyyy-MM-ddThh:mm:ssZ' 
 | 
     */ 
 | 
    public static String format(Date date) { 
 | 
        return format(date, false, TIMEZONE_UTC); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Format a date into 'yyyy-MM-ddThh:mm:ss[.sss]Z' (GMT timezone) 
 | 
     *  
 | 
     * @param date 
 | 
     *            the date to format 
 | 
     * @param millis 
 | 
     *            true to include millis precision otherwise false 
 | 
     * @return the date formatted as 'yyyy-MM-ddThh:mm:ss[.sss]Z' 
 | 
     */ 
 | 
    public static String format(Date date, boolean millis) { 
 | 
        return format(date, millis, TIMEZONE_UTC); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Format date into yyyy-MM-ddThh:mm:ss[.sss][Z|[+-]hh:mm] 
 | 
     *  
 | 
     * @param date 
 | 
     *            the date to format 
 | 
     * @param millis 
 | 
     *            true to include millis precision otherwise false 
 | 
     * @param tz 
 | 
     *            timezone to use for the formatting (UTC will produce 'Z') 
 | 
     * @return the date formatted as yyyy-MM-ddThh:mm:ss[.sss][Z|[+-]hh:mm] 
 | 
     */ 
 | 
    public static String format(Date date, boolean millis, TimeZone tz) { 
 | 
        Calendar calendar = new GregorianCalendar(tz, Locale.US); 
 | 
        calendar.setTime(date); 
 | 
  
 | 
        // estimate capacity of buffer as close as we can (yeah, that's pedantic ;) 
 | 
        int capacity = "yyyy-MM-ddThh:mm:ss".length(); 
 | 
        capacity += millis ? ".sss".length() : 0; 
 | 
        capacity += tz.getRawOffset() == 0 ? "Z".length() : "+hh:mm".length(); 
 | 
        StringBuilder formatted = new StringBuilder(capacity); 
 | 
  
 | 
        padInt(formatted, calendar.get(Calendar.YEAR), "yyyy".length()); 
 | 
        formatted.append('-'); 
 | 
        padInt(formatted, calendar.get(Calendar.MONTH) + 1, "MM".length()); 
 | 
        formatted.append('-'); 
 | 
        padInt(formatted, calendar.get(Calendar.DAY_OF_MONTH), "dd".length()); 
 | 
        formatted.append('T'); 
 | 
        padInt(formatted, calendar.get(Calendar.HOUR_OF_DAY), "hh".length()); 
 | 
        formatted.append(':'); 
 | 
        padInt(formatted, calendar.get(Calendar.MINUTE), "mm".length()); 
 | 
        formatted.append(':'); 
 | 
        padInt(formatted, calendar.get(Calendar.SECOND), "ss".length()); 
 | 
        if (millis) { 
 | 
            formatted.append('.'); 
 | 
            padInt(formatted, calendar.get(Calendar.MILLISECOND), "sss".length()); 
 | 
        } 
 | 
  
 | 
        int offset = tz.getOffset(calendar.getTimeInMillis()); 
 | 
        if (offset != 0) { 
 | 
            int hours = Math.abs((offset / (60 * 1000)) / 60); 
 | 
            int minutes = Math.abs((offset / (60 * 1000)) % 60); 
 | 
            formatted.append(offset < 0 ? '-' : '+'); 
 | 
            padInt(formatted, hours, "hh".length()); 
 | 
            formatted.append(':'); 
 | 
            padInt(formatted, minutes, "mm".length()); 
 | 
        } else { 
 | 
            formatted.append('Z'); 
 | 
        } 
 | 
  
 | 
        return formatted.toString(); 
 | 
    } 
 | 
  
 | 
    /* 
 | 
     * /********************************************************** /* Parsing 
 | 
     * /********************************************************** 
 | 
     */ 
 | 
  
 | 
    /** 
 | 
     * Parse a date from ISO-8601 formatted string. It expects a format 
 | 
     * [yyyy-MM-dd|yyyyMMdd][T(hh:mm[:ss[.sss]]|hhmm[ss[.sss]])]?[Z|[+-]hh[:mm]]] 
 | 
     *  
 | 
     * @param date 
 | 
     *            ISO string to parse in the appropriate format. 
 | 
     * @param pos 
 | 
     *            The position to start parsing from, updated to where parsing 
 | 
     *            stopped. 
 | 
     * @return the parsed date 
 | 
     * @throws ParseException 
 | 
     *             if the date is not in the appropriate format 
 | 
     */ 
 | 
    public static Date parse(String date, ParsePosition pos) throws ParseException { 
 | 
        Exception fail = null; 
 | 
        try { 
 | 
            int offset = pos.getIndex(); 
 | 
  
 | 
            // extract year 
 | 
            int year = parseInt(date, offset, offset += 4); 
 | 
            if (checkOffset(date, offset, '-')) { 
 | 
                offset += 1; 
 | 
            } 
 | 
  
 | 
            // extract month 
 | 
            int month = parseInt(date, offset, offset += 2); 
 | 
            if (checkOffset(date, offset, '-')) { 
 | 
                offset += 1; 
 | 
            } 
 | 
  
 | 
            // extract day 
 | 
            int day = parseInt(date, offset, offset += 2); 
 | 
            // default time value 
 | 
            int hour = 0; 
 | 
            int minutes = 0; 
 | 
            int seconds = 0; 
 | 
            int milliseconds = 0; // always use 0 otherwise returned date will include millis of current time 
 | 
  
 | 
            // if the value has no time component (and no time zone), we are done 
 | 
            boolean hasT = checkOffset(date, offset, 'T'); 
 | 
  
 | 
            if (!hasT && (date.length() <= offset)) { 
 | 
                Calendar calendar = new GregorianCalendar(year, month - 1, day); 
 | 
  
 | 
                pos.setIndex(offset); 
 | 
                return calendar.getTime(); 
 | 
            } 
 | 
  
 | 
            if (hasT) { 
 | 
  
 | 
                // extract hours, minutes, seconds and milliseconds 
 | 
                hour = parseInt(date, offset += 1, offset += 2); 
 | 
                if (checkOffset(date, offset, ':')) { 
 | 
                    offset += 1; 
 | 
                } 
 | 
  
 | 
                minutes = parseInt(date, offset, offset += 2); 
 | 
                if (checkOffset(date, offset, ':')) { 
 | 
                    offset += 1; 
 | 
                } 
 | 
                // second and milliseconds can be optional 
 | 
                if (date.length() > offset) { 
 | 
                    char c = date.charAt(offset); 
 | 
                    if (c != 'Z' && c != '+' && c != '-') { 
 | 
                        seconds = parseInt(date, offset, offset += 2); 
 | 
                        if (seconds > 59 && seconds < 63) 
 | 
                            seconds = 59; // truncate up to 3 leap seconds 
 | 
                        // milliseconds can be optional in the format 
 | 
                        if (checkOffset(date, offset, '.')) { 
 | 
                            offset += 1; 
 | 
                            int endOffset = indexOfNonDigit(date, offset + 1); // assume at least one digit 
 | 
                            int parseEndOffset = Math.min(endOffset, offset + 3); // parse up to 3 digits 
 | 
                            int fraction = parseInt(date, offset, parseEndOffset); 
 | 
                            // compensate for "missing" digits 
 | 
                            switch (parseEndOffset - offset) { // number of digits parsed 
 | 
                            case 2: 
 | 
                                milliseconds = fraction * 10; 
 | 
                                break; 
 | 
                            case 1: 
 | 
                                milliseconds = fraction * 100; 
 | 
                                break; 
 | 
                            default: 
 | 
                                milliseconds = fraction; 
 | 
                            } 
 | 
                            offset = endOffset; 
 | 
                        } 
 | 
                    } 
 | 
                } 
 | 
            } 
 | 
  
 | 
            // extract timezone 
 | 
            if (date.length() <= offset) { 
 | 
                throw new IllegalArgumentException("No time zone indicator"); 
 | 
            } 
 | 
  
 | 
            TimeZone timezone = null; 
 | 
            char timezoneIndicator = date.charAt(offset); 
 | 
  
 | 
            if (timezoneIndicator == 'Z') { 
 | 
                timezone = TIMEZONE_UTC; 
 | 
                offset += 1; 
 | 
            } else if (timezoneIndicator == '+' || timezoneIndicator == '-') { 
 | 
                String timezoneOffset = date.substring(offset); 
 | 
  
 | 
                // When timezone has no minutes, we should append it, valid timezones are, for 
 | 
                // example: +00:00, +0000 and +00 
 | 
                timezoneOffset = timezoneOffset.length() >= 5 ? timezoneOffset : timezoneOffset + "00"; 
 | 
  
 | 
                offset += timezoneOffset.length(); 
 | 
                // 18-Jun-2015, tatu: Minor simplification, skip offset of "+0000"/"+00:00" 
 | 
                if ("+0000".equals(timezoneOffset) || "+00:00".equals(timezoneOffset)) { 
 | 
                    timezone = TIMEZONE_UTC; 
 | 
                } else { 
 | 
                    // 18-Jun-2015, tatu: Looks like offsets only work from GMT, not UTC... 
 | 
                    // not sure why, but that's the way it looks. Further, Javadocs for 
 | 
                    // `java.util.TimeZone` specifically instruct use of GMT as base for 
 | 
                    // custom timezones... odd. 
 | 
                    String timezoneId = "GMT" + timezoneOffset; 
 | 
                    // String timezoneId = "UTC" + timezoneOffset; 
 | 
  
 | 
                    timezone = TimeZone.getTimeZone(timezoneId); 
 | 
  
 | 
                    String act = timezone.getID(); 
 | 
                    if (!act.equals(timezoneId)) { 
 | 
                        /* 
 | 
                         * 22-Jan-2015, tatu: Looks like canonical version has colons, but we may be 
 | 
                         * given one without. If so, don't sweat. Yes, very inefficient. Hopefully not 
 | 
                         * hit often. If it becomes a perf problem, add 'loose' comparison instead. 
 | 
                         */ 
 | 
                        String cleaned = act.replace(":", ""); 
 | 
                        if (!cleaned.equals(timezoneId)) { 
 | 
                            throw new IndexOutOfBoundsException("Mismatching time zone indicator: " + timezoneId + " given, resolves to " + timezone.getID()); 
 | 
                        } 
 | 
                    } 
 | 
                } 
 | 
            } else { 
 | 
                throw new IndexOutOfBoundsException("Invalid time zone indicator '" + timezoneIndicator + "'"); 
 | 
            } 
 | 
  
 | 
            Calendar calendar = new GregorianCalendar(timezone); 
 | 
            calendar.setLenient(false); 
 | 
            calendar.set(Calendar.YEAR, year); 
 | 
            calendar.set(Calendar.MONTH, month - 1); 
 | 
            calendar.set(Calendar.DAY_OF_MONTH, day); 
 | 
            calendar.set(Calendar.HOUR_OF_DAY, hour); 
 | 
            calendar.set(Calendar.MINUTE, minutes); 
 | 
            calendar.set(Calendar.SECOND, seconds); 
 | 
            calendar.set(Calendar.MILLISECOND, milliseconds); 
 | 
  
 | 
            pos.setIndex(offset); 
 | 
            return calendar.getTime(); 
 | 
            // If we get a ParseException it'll already have the right message/offset. 
 | 
            // Other exception types can convert here. 
 | 
        } catch (IndexOutOfBoundsException e) { 
 | 
            fail = e; 
 | 
        } catch (NumberFormatException e) { 
 | 
            fail = e; 
 | 
        } catch (IllegalArgumentException e) { 
 | 
            fail = e; 
 | 
        } 
 | 
        String input = (date == null) ? null : ('"' + date + "'"); 
 | 
        String msg = fail.getMessage(); 
 | 
        if (msg == null || msg.isEmpty()) { 
 | 
            msg = "(" + fail.getClass().getName() + ")"; 
 | 
        } 
 | 
        ParseException ex = new ParseException("Failed to parse date [" + input + "]: " + msg, pos.getIndex()); 
 | 
        ex.initCause(fail); 
 | 
        throw ex; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Check if the expected character exist at the given offset in the value. 
 | 
     *  
 | 
     * @param value 
 | 
     *            the string to check at the specified offset 
 | 
     * @param offset 
 | 
     *            the offset to look for the expected character 
 | 
     * @param expected 
 | 
     *            the expected character 
 | 
     * @return true if the expected character exist at the given offset 
 | 
     */ 
 | 
    private static boolean checkOffset(String value, int offset, char expected) { 
 | 
        return (offset < value.length()) && (value.charAt(offset) == expected); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Parse an integer located between 2 given offsets in a string 
 | 
     *  
 | 
     * @param value 
 | 
     *            the string to parse 
 | 
     * @param beginIndex 
 | 
     *            the start index for the integer in the string 
 | 
     * @param endIndex 
 | 
     *            the end index for the integer in the string 
 | 
     * @return the int 
 | 
     * @throws NumberFormatException 
 | 
     *             if the value is not a number 
 | 
     */ 
 | 
    private static int parseInt(String value, int beginIndex, int endIndex) throws NumberFormatException { 
 | 
        if (beginIndex < 0 || endIndex > value.length() || beginIndex > endIndex) { 
 | 
            throw new NumberFormatException(value); 
 | 
        } 
 | 
        // use same logic as in Integer.parseInt() but less generic we're not supporting 
 | 
        // negative values 
 | 
        int i = beginIndex; 
 | 
        int result = 0; 
 | 
        int digit; 
 | 
        if (i < endIndex) { 
 | 
            digit = Character.digit(value.charAt(i++), 10); 
 | 
            if (digit < 0) { 
 | 
                throw new NumberFormatException("Invalid number: " + value.substring(beginIndex, endIndex)); 
 | 
            } 
 | 
            result = -digit; 
 | 
        } 
 | 
        while (i < endIndex) { 
 | 
            digit = Character.digit(value.charAt(i++), 10); 
 | 
            if (digit < 0) { 
 | 
                throw new NumberFormatException("Invalid number: " + value.substring(beginIndex, endIndex)); 
 | 
            } 
 | 
            result *= 10; 
 | 
            result -= digit; 
 | 
        } 
 | 
        return -result; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Zero pad a number to a specified length 
 | 
     *  
 | 
     * @param buffer 
 | 
     *            buffer to use for padding 
 | 
     * @param value 
 | 
     *            the integer value to pad if necessary. 
 | 
     * @param length 
 | 
     *            the length of the string we should zero pad 
 | 
     */ 
 | 
    private static void padInt(StringBuilder buffer, int value, int length) { 
 | 
        String strValue = Integer.toString(value); 
 | 
        for (int i = length - strValue.length(); i > 0; i--) { 
 | 
            buffer.append('0'); 
 | 
        } 
 | 
        buffer.append(strValue); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * Returns the index of the first character in the string that is not a digit, 
 | 
     * starting at offset. 
 | 
     */ 
 | 
    private static int indexOfNonDigit(String string, int offset) { 
 | 
        for (int i = offset; i < string.length(); i++) { 
 | 
            char c = string.charAt(i); 
 | 
            if (c < '0' || c > '9') 
 | 
                return i; 
 | 
        } 
 | 
        return string.length(); 
 | 
    } 
 | 
  
 | 
} 
 |