天天看點

SimpleDateFormat 源碼分析

日期類轉換在工作中是比較常用的。寫法如下:

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = new Date();
        String s = simpleDateFormat.format(date);
           

運作即可得出目前日期字元串。

SimpleDateFormat 源碼分析

下面我們就來分析一下該資料轉化的過程。首先建立SimpleDateFormat 。

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
           
建立會調用 SimpleDateFormat 的構造方法
           
public SimpleDateFormat(String pattern)
    {
        this(pattern, Locale.getDefault(Locale.Category.FORMAT));
    }
           
構造方法中調用了this(pattern, Locale.getDefault(Locale.Category.FORMAT));
           
public SimpleDateFormat(String pattern, Locale locale)
    {
        if (pattern == null || locale == null) {
            throw new NullPointerException();
        }
    //初始化公曆
        initializeCalendar(locale);
        this.pattern = pattern;
        this.formatData = DateFormatSymbols.getInstanceRef(locale);
        this.locale = locale;
        initialize(locale);
    }
           
構造方法中進行了初始化公曆和指派操作。GregorianCalendar是Calendar的子類。
initializeCalendar(locale);
           
private void initializeCalendar(Locale loc) {
        if (calendar == null) {
            assert loc != null;
            calendar = Calendar.getInstance(TimeZone.getDefault(), loc);
        }
    }
           
public static Calendar getInstance(TimeZone zone,Locale aLocale){
        return createCalendar(zone, aLocale);
    }

    private static Calendar createCalendar(TimeZone zone,Locale aLocale){
        return new GregorianCalendar(zone, aLocale);
    }
    public GregorianCalendar(TimeZone zone, Locale aLocale) {
        super(zone, aLocale);
        gdate = (BaseCalendar.Date) gcal.newCalendarDate(zone);
        //GregorianCalendar指派
        setTimeInMillis(System.currentTimeMillis());
    }
           
initialize()方法
           
private void initialize(Locale loc) {
        // Verify and compile the given pattern.
        compiledPattern = compile(pattern);

        /* try the cache first */
        numberFormat = cachedNumberFormatData.get(loc);
        if (numberFormat == null) { /* cache miss */
            numberFormat = NumberFormat.getIntegerInstance(loc);
            numberFormat.setGroupingUsed(false);

            /* update cache */
            cachedNumberFormatData.putIfAbsent(loc, numberFormat);
        }
        numberFormat = (NumberFormat) numberFormat.clone();

        initializeDefaultCentury();
    }
           
compile(pattern)方法對日期格式進行解析
           
private char[] compile(String pattern) {
        int length = pattern.length();
        boolean inQuote = false;
        StringBuilder compiledPattern = new StringBuilder(length * );
        StringBuilder tmpBuffer = null;
        int count = ;
        int lastTag = -;

        for (int i = ; i < length; i++) {
            char c = pattern.charAt(i);

            if (c == '\'') {
                // '' is treated as a single quote regardless of being
                // in a quoted section.
                if ((i + ) < length) {
                    c = pattern.charAt(i + );
                    if (c == '\'') {
                        i++;
                        if (count != ) {
                            encode(lastTag, count, compiledPattern);
                            lastTag = -;
                            count = ;
                        }
                        if (inQuote) {
                            tmpBuffer.append(c);
                        } else {
                            compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR <<  | c));
                        }
                        continue;
                    }
                }
                if (!inQuote) {
                    if (count != ) {
                        encode(lastTag, count, compiledPattern);
                        lastTag = -;
                        count = ;
                    }
                    if (tmpBuffer == null) {
                        tmpBuffer = new StringBuilder(length);
                    } else {
                        tmpBuffer.setLength();
                    }
                    inQuote = true;
                } else {
                    int len = tmpBuffer.length();
                    if (len == ) {
                        char ch = tmpBuffer.charAt();
                        if (ch < ) {
                            compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR <<  | ch));
                        } else {
                            compiledPattern.append((char)(TAG_QUOTE_CHARS <<  | ));
                            compiledPattern.append(ch);
                        }
                    } else {
                        encode(TAG_QUOTE_CHARS, len, compiledPattern);
                        compiledPattern.append(tmpBuffer);
                    }
                    inQuote = false;
                }
                continue;
            }
            if (inQuote) {
                tmpBuffer.append(c);
                continue;
            }
            if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) {
                if (count != ) {
                    encode(lastTag, count, compiledPattern);
                    lastTag = -;
                    count = ;
                }
                if (c < ) {
                    // In most cases, c would be a delimiter, such as ':'.
                    compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR <<  | c));
                } else {
                    // Take any contiguous non-ASCII alphabet characters and
                    // put them in a single TAG_QUOTE_CHARS.
                    int j;
                    for (j = i + ; j < length; j++) {
                        char d = pattern.charAt(j);
                        if (d == '\'' || (d >= 'a' && d <= 'z' || d >= 'A' && d <= 'Z')) {
                            break;
                        }
                    }
                    compiledPattern.append((char)(TAG_QUOTE_CHARS <<  | (j - i)));
                    for (; i < j; i++) {
                        compiledPattern.append(pattern.charAt(i));
                    }
                    i--;
                }
                continue;
            }

            int tag;
            if ((tag = DateFormatSymbols.patternChars.indexOf(c)) == -) {
                throw new IllegalArgumentException("Illegal pattern character " +
                                                   "'" + c + "'");
            }
            if (lastTag == - || lastTag == tag) {
                lastTag = tag;
                count++;
                continue;
            }
            encode(lastTag, count, compiledPattern);
            lastTag = tag;
            count = ;
        }

        if (inQuote) {
            throw new IllegalArgumentException("Unterminated quote");
        }

        if (count != ) {
            encode(lastTag, count, compiledPattern);
        }

        // Copy the compiled pattern to a char array
        int len = compiledPattern.length();
        char[] r = new char[len];
        compiledPattern.getChars(, len, r, );
        return r;
    }
           
/**
     * Encodes the given tag and length and puts encoded char(s) into buffer.
     */
    private static final void encode(int tag, int length, StringBuilder buffer) {
        if (tag == PATTERN_ISO_ZONE && length >= ) {
            throw new IllegalArgumentException("invalid ISO 8601 format: length=" + length);
        }
        if (length < ) {
            buffer.append((char)(tag <<  | length));
        } else {
            buffer.append((char)((tag << ) | ));
            buffer.append((char)(length >>> ));
            buffer.append((char)(length & ));
        }
    }
           
compile方法中通過 pattern.charAt(i)擷取字元 c ,然後判斷 該字元是否在a - z 和 A - Z 範圍值内,不在範圍内則判斷c 值是否小于 128  小于則将 100 左移8位  和 字元c 做 或運算 并存儲到 compiledPattern中,執行下一次循環
           
private final static int TAG_QUOTE_ASCII_CHAR       = ;
        private final static int TAG_QUOTE_CHARS            = ;

        static final String  patternChars = "GyMdkHmsSEDFwWahKzZYuXLc";

        static final int PATTERN_ERA                  =  ; // G
        static final int PATTERN_YEAR                 =  ; // y
        static final int PATTERN_MONTH                =  ; // M
        static final int PATTERN_DAY_OF_MONTH         =  ; // d
        static final int PATTERN_HOUR_OF_DAY1         =  ; // k
        static final int PATTERN_HOUR_OF_DAY0         =  ; // H
        static final int PATTERN_MINUTE               =  ; // m
        static final int PATTERN_SECOND               =  ; // s
        static final int PATTERN_MILLISECOND          =  ; // S
        static final int PATTERN_DAY_OF_WEEK          =  ; // E
        static final int PATTERN_DAY_OF_YEAR          = ; // D
        static final int PATTERN_DAY_OF_WEEK_IN_MONTH = ; // F
        static final int PATTERN_WEEK_OF_YEAR         = ; // w
        static final int PATTERN_WEEK_OF_MONTH        = ; // W
        static final int PATTERN_AM_PM                = ; // a
        static final int PATTERN_HOUR1                = ; // h
        static final int PATTERN_HOUR0                = ; // K
        static final int PATTERN_ZONE_NAME            = ; // z
        static final int PATTERN_ZONE_VALUE           = ; // Z
        static final int PATTERN_WEEK_YEAR            = ; // Y
        static final int PATTERN_ISO_DAY_OF_WEEK      = ; // u
        static final int PATTERN_ISO_ZONE             = ; // X
        static final int PATTERN_STANDALONE_MONTH     = ; // L
        static final int PATTERN_STANDALONE_DAY_OF_WEEK = ; // c
           
在 a - z 和 A - Z  範圍内則判斷該字元 c  在 patternChars  中的索引值。并判斷lastTag == -1 || lastTag == tag,結果為true 則 指派 lastTag = tag,count++ ,執行下次循環

encode(lastTag, count, compiledPattern);方法用來将字元所在patternChars  的索引值 左移8位後和該字元出現的個數做或運算并存儲到compiledPattern中。

Date date = new Date();這個比較簡單,建立日期類,并将目前毫秒值指派給date。
           
public Date() {
        this(System.currentTimeMillis());
    }
    public Date(long date) {
        fastTime = date;
    }
           
然後調用simpleDateFormat的format()方法,将date作為參數傳進去;
           
public final String format(Date date){
        return format(date, new StringBuffer(),
                      DontCareFieldPosition.INSTANCE).toString();
    }
    public StringBuffer format(Date date, StringBuffer toAppendTo,
                               FieldPosition pos){
        pos.beginIndex = pos.endIndex = ;
        return format(date, toAppendTo, pos.getFieldDelegate());
    }

    private StringBuffer format(Date date, StringBuffer toAppendTo,
                                FieldDelegate delegate) {
        // Convert input date to time field list
        calendar.setTime(date);

        boolean useDateFormatSymbols = useDateFormatSymbols();

        for (int i = ; i < compiledPattern.length; ) {
            int tag = compiledPattern[i] >>> ;
            int count = compiledPattern[i++] & ;
            if (count == ) {
                count = compiledPattern[i++] << ;
                count |= compiledPattern[i++];
            }

            switch (tag) {
            case TAG_QUOTE_ASCII_CHAR:
                toAppendTo.append((char)count);
                break;

            case TAG_QUOTE_CHARS:
                toAppendTo.append(compiledPattern, i, count);
                i += count;
                break;

            default:
                subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
                break;
            }
        }
        return toAppendTo;
    }
           
将date指派給calendar。
compiledPattern[i]無符号右移8位即可得出 tag值,compiledPattern[i++] & 0xff得出count值。
           
private void subFormat(int patternCharIndex, int count,
                           FieldDelegate delegate, StringBuffer buffer,
                           boolean useDateFormatSymbols)
    {
        int     maxIntCount = Integer.MAX_VALUE;
        String  current = null;
        int     beginOffset = buffer.length();

        int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
        int value;
        if (field == CalendarBuilder.WEEK_YEAR) {
            if (calendar.isWeekDateSupported()) {
                value = calendar.getWeekYear();
            } else {
                // use calendar year 'y' instead
                patternCharIndex = PATTERN_YEAR;
                field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
                value = calendar.get(field);
            }
        } else if (field == CalendarBuilder.ISO_DAY_OF_WEEK) {
            value = CalendarBuilder.toISODayOfWeek(calendar.get(Calendar.DAY_OF_WEEK));
        } else {
            value = calendar.get(field);
        }

        int style = (count >= ) ? Calendar.LONG : Calendar.SHORT;
        if (!useDateFormatSymbols && field != CalendarBuilder.ISO_DAY_OF_WEEK) {
            current = calendar.getDisplayName(field, style, locale);
        }

        // Note: zeroPaddingNumber() assumes that maxDigits is either
        // 2 or maxIntCount. If we make any changes to this,
        // zeroPaddingNumber() must be fixed.

        switch (patternCharIndex) {
        case PATTERN_ERA: // 'G'
            if (useDateFormatSymbols) {
                String[] eras = formatData.getEras();
                if (value < eras.length)
                    current = eras[value];
            }
            if (current == null)
                current = "";
            break;

        case PATTERN_WEEK_YEAR: // 'Y'
        case PATTERN_YEAR:      // 'y'
            if (calendar instanceof GregorianCalendar) {
                if (count != )
                    zeroPaddingNumber(value, count, maxIntCount, buffer);
                else // count == 2
                    zeroPaddingNumber(value, , , buffer); // clip 1996 to 96
            } else {
                if (current == null) {
                    zeroPaddingNumber(value, style == Calendar.LONG ?  : count,
                                      maxIntCount, buffer);
                }
            }
            break;

        case PATTERN_STANDALONE_MONTH: // 'L'
        {
            current = formatMonth(count, value, maxIntCount, buffer, useDateFormatSymbols,
                    true /* standalone */);
            break;
        }

        case PATTERN_MONTH: // 'M'
        {
            current = formatMonth(count, value, maxIntCount, buffer, useDateFormatSymbols,
                    false /* standalone */);
            break;
        }

        case PATTERN_HOUR_OF_DAY1: // 'k' 1-based.  eg, 23:59 + 1 hour =>> 24:59
            if (current == null) {
                if (value == )
                    zeroPaddingNumber(calendar.getMaximum(Calendar.HOUR_OF_DAY)+,
                                      count, maxIntCount, buffer);
                else
                    zeroPaddingNumber(value, count, maxIntCount, buffer);
            }
            break;

        case PATTERN_STANDALONE_DAY_OF_WEEK: // 'c'
        {
            current = formatWeekday(count, value, useDateFormatSymbols, true /* standalone */);
            break;
        }

        case PATTERN_DAY_OF_WEEK: // 'E'
        {
            current = formatWeekday(count, value, useDateFormatSymbols, false /* standalone */);
            break;
        }

        case PATTERN_AM_PM:    // 'a'
            if (useDateFormatSymbols) {
                String[] ampm = formatData.getAmPmStrings();
                current = ampm[value];
            }
            break;

        case PATTERN_HOUR1:    // 'h' 1-based.  eg, 11PM + 1 hour =>> 12 AM
            if (current == null) {
                if (value == )
                    zeroPaddingNumber(calendar.getLeastMaximum(Calendar.HOUR)+,
                                      count, maxIntCount, buffer);
                else
                    zeroPaddingNumber(value, count, maxIntCount, buffer);
            }
            break;

        case PATTERN_ZONE_NAME: // 'z'
            if (current == null) {
                TimeZone tz = calendar.getTimeZone();
                boolean daylight = (calendar.get(Calendar.DST_OFFSET) != );
                int tzstyle = count <  ? TimeZone.SHORT : TimeZone.LONG;
                String zoneString;
                if (formatData.isZoneStringsSet) {
                    zoneString = TimeZoneNames.getDisplayName(
                            formatData.getZoneStringsWrapper(), tz.getID(), daylight, tzstyle);
                } else {
                    zoneString = tz.getDisplayName(daylight, tzstyle, formatData.locale);
                }
                if (zoneString != null) {
                    buffer.append(zoneString);
                } else {
                    int offsetMillis = calendar.get(Calendar.ZONE_OFFSET) +
                        calendar.get(Calendar.DST_OFFSET);
                    buffer.append(TimeZone.createGmtOffsetString(true, true, offsetMillis));
                }
            }
            break;

        case PATTERN_ZONE_VALUE: // 'Z' ("-/+hhmm" form)
        {
            value = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
            final boolean includeSeparator = (count >= );
            final boolean includeGmt = (count == );
            buffer.append(TimeZone.createGmtOffsetString(includeGmt, includeSeparator, value));

            break;
        }

        case PATTERN_ISO_ZONE:   // 'X'
            value = calendar.get(Calendar.ZONE_OFFSET)
                    + calendar.get(Calendar.DST_OFFSET);

            if (value == ) {
                buffer.append('Z');
                break;
            }

            value /=  ;
            if (value >= ) {
                buffer.append('+');
            } else {
                buffer.append('-');
                value = -value;
            }

            CalendarUtils.sprintf0d(buffer, value / , );
            if (count == ) {
                break;
            }

            if (count == ) {
                buffer.append(':');
            }
            CalendarUtils.sprintf0d(buffer, value % , );
            break;
        case PATTERN_MILLISECOND: // 'S'
            // Fractional seconds must be treated specially. We must always convert the parsed
            // value into a fractional second [0, 1) and then widen it out to the appropriate
            // formatted size. For example, an initial value of 789 will be converted
            // 0.789 and then become ".7" (S) or ".78" (SS) or "0.789" (SSS) or "0.7890" (SSSS)
            // in the resulting formatted output.
            if (current == null) {
                value = (int) (((double) value / ) * Math.pow(, count));
                zeroPaddingNumber(value, count, count, buffer);
            }
            break;

        default:
     // case PATTERN_DAY_OF_MONTH:         // 'd'
     // case PATTERN_HOUR_OF_DAY0:         // 'H' 0-based.  eg, 23:59 + 1 hour =>> 00:59
     // case PATTERN_MINUTE:               // 'm'
     // case PATTERN_SECOND:               // 's'
     // case PATTERN_DAY_OF_YEAR:          // 'D'
     // case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F'
     // case PATTERN_WEEK_OF_YEAR:         // 'w'
     // case PATTERN_WEEK_OF_MONTH:        // 'W'
     // case PATTERN_HOUR0:                // 'K' eg, 11PM + 1 hour =>> 0 AM
     // case PATTERN_ISO_DAY_OF_WEEK:      // 'u' pseudo field, Monday = 1, ..., Sunday = 7
            if (current == null) {
                zeroPaddingNumber(value, count, maxIntCount, buffer);
            }
            break;
        } // switch (patternCharIndex)

        if (current != null) {
            buffer.append(current);
        }

        int fieldID = PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex];
        Field f = PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID[patternCharIndex];

        delegate.formatted(fieldID, f, f, beginOffset, buffer.length(), buffer);
    }
           
private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD =
    {
        Calendar.ERA, Calendar.YEAR, Calendar.MONTH, Calendar.DATE,
        Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY, Calendar.MINUTE,
        Calendar.SECOND, Calendar.MILLISECOND, Calendar.DAY_OF_WEEK,
        Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH,
        Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH,
        Calendar.AM_PM, Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET,
        Calendar.ZONE_OFFSET,
        // Pseudo Calendar fields
        CalendarBuilder.WEEK_YEAR,
        CalendarBuilder.ISO_DAY_OF_WEEK,
        Calendar.ZONE_OFFSET,
        // 'L' and 'c',
        Calendar.MONTH,
        Calendar.DAY_OF_WEEK
    };
           
先通過索引值擷取field,然後調用calendar.get(field)得出對應的資料。
           
private final void zeroPaddingNumber(int value, int minDigits, int maxDigits, StringBuffer buffer)
    {
        // Optimization for 1, 2 and 4 digit numbers. This should
        // cover most cases of formatting date/time related items.
        // Note: This optimization code assumes that maxDigits is
        // either 2 or Integer.MAX_VALUE (maxIntCount in format()).
        try {
            if (zeroDigit == ) {
                zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit();
            }
            if (value >= ) {
                if (value <  && minDigits >=  && minDigits <= ) {
                    if (value < ) {
                        if (minDigits == ) {
                            buffer.append(zeroDigit);
                        }
                        buffer.append((char)(zeroDigit + value));
                    } else {
                        buffer.append((char)(zeroDigit + value / ));
                        buffer.append((char)(zeroDigit + value % ));
                    }
                    return;
                } else if (value >=  && value < ) {
                    if (minDigits == ) {
                        buffer.append((char)(zeroDigit + value / ));
                        value %= ;
                        buffer.append((char)(zeroDigit + value / ));
                        value %= ;
                        buffer.append((char)(zeroDigit + value / ));
                        buffer.append((char)(zeroDigit + value % ));
                        return;
                    }
                    if (minDigits ==  && maxDigits == ) {
                        zeroPaddingNumber(value % , , , buffer);
                        return;
                    }
                }
            }
        } catch (Exception e) {
        }

        numberFormat.setMinimumIntegerDigits(minDigits);
        numberFormat.setMaximumIntegerDigits(maxDigits);
        numberFormat.format((long)value, buffer, DontCareFieldPosition.INSTANCE);
    }
           
通過 zeroPaddingNumber()方法将 得到的資料存儲到buffer中,循環完成之後将buffer傳回回去即可得到對應的日期資料。