JDK8 官网文档:javase/8/docs
提示:由于JDK内部众多类互相引用的情况很多,SimpleDateFormat源码也不例外,所以可能会涉及很多其他JDK原生的类,通常这些类在本文中只会说明其作用,不会深入讲解,因为本文是专注于SimpleDateFormat的源码阅读。
对于国内众多使用JDK8的Java开发者,SimpleDateFormat类应该不陌生,是日期格式化的常用类。
本文以实际使用出发,主要从三个使用点进行源码学习,分别是:类创建、format方法、parse方法。其中重点关注类创建相关代码,其他方法在本文中重点了解其调用逻辑,其具体的代码算法请自行阅读源码理解(PS:偷个懒,其实是我还没理解透彻。)。
经过阅读源码发现,以下代码会报错,报错内容是:java.lang.IllegalArgumentException: Unterminated quote。原因在于使用了'单引号,但使用的不正确:没有结尾。意思是如果使用单引号,必须正确使用,如:"G 'yyyy年' YY年 MM 月 dd日 ,这样不会报错,报错原因咱们阅读源码便知。
javaSimpleDateFormat simpleDateFormat = new SimpleDateFormat("G 'yyyy年 YY年 MM 月 dd日 HH:mm:ss.S");
阅读源码一定要动手,自己去测试验证自己的理解是否正确,本文使用的基本测试代码如下:
java    public static void main(String[] args) throws ParseException {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("G yyyy年 YY年 MM 月 dd日 HH:mm:ss.S");
        System.out.println(simpleDateFormat.format(new Date()));
    }
正常输出内容为:
公元 2023年 23年 01 月 17日 15:27:04.570
下面正式开始源码的阅读理解。
点进SimpleDateFormat类的源码中,会发现类注释很长,这里只粘出一部分注释:
SimpleDateFormat is a concrete class for formatting and parsing dates in a locale-sensitive manner. It allows for formatting (date → text), parsing (text → date), and normalization. SimpleDateFormat allows you to start by choosing any user-defined patterns for date-time formatting. However, you are encouraged to create a date-time formatter with either getTimeInstance, getDateInstance, or getDateTimeInstance in DateFormat. Each of these class methods can return a date/time formatter initialized with a default format pattern. You may modify the format pattern using the applyPattern methods as desired. For more information on using these methods, see DateFormat.
经过谷歌翻译,内容如下:
SimpleDateFormat 是一个具体的类,用于以区域设置敏感的方式格式化和解析日期。 它允许格式化(date → text)、解析(text → date)和规范化。 SimpleDateFormat 允许您从选择任何用户定义的日期时间格式模式开始。但是,我们鼓励您使用 DateFormat 中的 getTimeInstance、getDateInstance 或 getDateTimeInstance 创建日期时间格式化程序。 这些类方法中的每一个都可以返回使用默认格式模式初始化的日期/时间格式化程序。 您可以根据需要使用 applyPattern 方法修改格式模式。 有关使用这些方法的详细信息,请参阅 DateFormat。
首先阅读源码可知SimpleDateFormat的族谱如下:
SimpleDateFormat提供了四个构造方法,分别是:
SimpleDateFormat()SimpleDateFormat(String pattern)SimpleDateFormat(String pattern, Locale locale)SimpleDateFormat(String pattern, DateFormatSymbols formatSymbols)工作中我们最常用的应该是构造2。但构造1和构造2都是复用构造3,所以重点关注构造3即可,构造2、3源码如下:
java
    /**
     * Constructs a <code>SimpleDateFormat</code> using the given pattern and
     * the default date format symbols for the default
     * {@link java.util.Locale.Category#FORMAT FORMAT} locale.
     * <b>Note:</b> This constructor may not support all locales.
     * For full coverage, use the factory methods in the {@link DateFormat}
     * class.
     * <p>This is equivalent to calling
     * {@link #SimpleDateFormat(String, Locale)
     *     SimpleDateFormat(pattern, Locale.getDefault(Locale.Category.FORMAT))}.
     *
     * @see java.util.Locale#getDefault(java.util.Locale.Category)
     * @see java.util.Locale.Category#FORMAT
     * @param pattern the pattern describing the date and time format
     * @exception NullPointerException if the given pattern is null
     * @exception IllegalArgumentException if the given pattern is invalid
     */
    public SimpleDateFormat(String pattern)
    {
        this(pattern, Locale.getDefault(Locale.Category.FORMAT));
    }
    /**
     * Constructs a <code>SimpleDateFormat</code> using the given pattern and
     * the default date format symbols for the given locale.
     * <b>Note:</b> This constructor may not support all locales.
     * For full coverage, use the factory methods in the {@link DateFormat}
     * class.
     *
     * @param pattern the pattern describing the date and time format
     * @param locale the locale whose date format symbols should be used
     * @exception NullPointerException if the given pattern or locale is null
     * @exception IllegalArgumentException if the given pattern is invalid
     */
    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);
    }
以构造3为解析对象,分别解析入参和内部代码作用。
| 参数名 | 参数类型 | 参数说明 | 
|---|---|---|
| pattern | String | 格式化日期的规则定义(简称格式定义),如常见的 yyyy-MM-dd HH:mm:ss。 | 
| locale | Locale | 该类内部内容较多,可以深入学习,概括来讲,其内部存储当前程序所在的地区时区、国家、语言等等本地信息。 | 
构造内部主要工作是初始化SimpleDateFormat的一些属性,其中重要属性有:
| 属性名 | 属性类型 | 属性说明 | 
|---|---|---|
| calendar | Calendar | 待格式化的日期会转为Calendar,解析时会直接复用Calendar类的方法 | 
| pattern | String | 格式化规则定义 | 
| formatData | DateFormatSymbols | 年、周等日期格式化方法的实现类 | 
| locale | Locale | 本地时区 | 
| compiledPattern | char[] | 格式化规则解析后的数字 | 
| numberFormat | NumberFormat | 数字格式化方法的实现类 | 
| cachedNumberFormatData | ConcurrentMap<Locale, NumberFormat> | Locale和NumberFormat的对应关系缓存,初始缓存大小是3 | 
其中compiledPattern参数较为重要,是由将pattern参数解析而来,解析逻辑较为复杂,解析方法详情:

java
    /**
     * Returns the compiled form of the given pattern. The syntax of
     * the compiled pattern is:
     * <blockquote>
     * CompiledPattern:
     *     EntryList
     * EntryList:
     *     Entry
     *     EntryList Entry
     * Entry:
     *     TagField
     *     TagField data
     * TagField:
     *     Tag Length
     *     TaggedData
     * Tag:
     *     pattern_char_index
     *     TAG_QUOTE_CHARS
     * Length:
     *     short_length
     *     long_length
     * TaggedData:
     *     TAG_QUOTE_ASCII_CHAR ascii_char
     *
     * </blockquote>
     *
     * where `short_length' is an 8-bit unsigned integer between 0 and
     * 254.  `long_length' is a sequence of an 8-bit integer 255 and a
     * 32-bit signed integer value which is split into upper and lower
     * 16-bit fields in two char's. `pattern_char_index' is an 8-bit
     * integer between 0 and 18. `ascii_char' is an 7-bit ASCII
     * character value. `data' depends on its Tag value.
     * <p>
     * If Length is short_length, Tag and short_length are packed in a
     * single char, as illustrated below.
     * <blockquote>
     *     char[0] = (Tag << 8) | short_length;
     * </blockquote>
     *
     * If Length is long_length, Tag and 255 are packed in the first
     * char and a 32-bit integer, as illustrated below.
     * <blockquote>
     *     char[0] = (Tag << 8) | 255;
     *     char[1] = (char) (long_length >>> 16);
     *     char[2] = (char) (long_length & 0xffff);
     * </blockquote>
     * <p>
     * If Tag is a pattern_char_index, its Length is the number of
     * pattern characters. For example, if the given pattern is
     * "yyyy", Tag is 1 and Length is 4, followed by no data.
     * <p>
     * If Tag is TAG_QUOTE_CHARS, its Length is the number of char's
     * following the TagField. For example, if the given pattern is
     * "'o''clock'", Length is 7 followed by a char sequence of
     * <code>o&nbs;'&nbs;c&nbs;l&nbs;o&nbs;c&nbs;k</code>.
     * <p>
     * TAG_QUOTE_ASCII_CHAR is a special tag and has an ASCII
     * character in place of Length. For example, if the given pattern
     * is "'o'", the TaggedData entry is
     * <code>((TAG_QUOTE_ASCII_CHAR&nbs;<<&nbs;8)&nbs;|&nbs;'o')</code>.
     *
     * @exception NullPointerException if the given pattern is null
     * @exception IllegalArgumentException if the given pattern is invalid
     */
        private char[] compile(String pattern) {
        // 格式定义长度
        int length = pattern.length();
        // 是否在引号内部--标识符
        boolean inQuote = false;
        // 解析结果存储集合
        StringBuilder compiledCode = new StringBuilder(length * 2);
        // 临时缓存
        StringBuilder tmpBuffer = null;
        // count tagcount
        int count = 0, tagcount = 0;
        // lastTag结束索引	prevTag开始索引
        int lastTag = -1, prevTag = -1;
        for (int i = 0; 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 + 1) < length) {
                    // 单引号下一个字符索引小于整体字符串长度,说明后面还有其他字符,则取到下一个字符进行处理
                    c = pattern.charAt(i + 1);
                    if (c == '\'') {
                        // 如果下一个字符值也是单引号,直接将循环索引+1,准备进入下次循环
                        i++;
                        if (count != 0) {
                            // 如果count不是0,进行字符解析
                            encode(lastTag, count, compiledCode);
                            // tagcount + 1
                            tagcount++;
                            // 开始索引置为结束索引
                            prevTag = lastTag;
                            // 结束索引重置
                            lastTag = -1;
                            // count重置为0
                            count = 0;
                        }
                        if (inQuote) {
                            // 如果原本已经在引号里,将当前引号的下一个字符放到临时缓存中
                            // 此种情况只有一种可能:前边出现一次单引号后,又连续出现两次单引号,一个引号结束又立即进入下一个引号中:如:yy'years'' old'
                            tmpBuffer.append(c);
                        } else {
                            // 如果原本不在引号里,则说明当前连续出现两个引号,引号已经结束。
                            // 将TAG_QUOTE_ASCII_CHAR左移8位 按位或 c (其中TAG_QUOTE_ASCII_CHAR = 100,c = "'")
                            compiledCode.append((char) (TAG_QUOTE_ASCII_CHAR << 8 | c));
                        }
                        continue;
                    }
                }
                if (!inQuote) {
                    // 如果不是连续两个单引号,则判断出现当前这个引号之前,是否已经在引号内部,如果之前不是在引号内部,则进入当前逻辑
                    if (count != 0) {
                        // 如果count不是0,进行编译
                        encode(lastTag, count, compiledCode);
                        // tagcount + 1
                        tagcount++;
                        // 开始索引置为结束索引
                        prevTag = lastTag;
                        // 结束索引重置
                        lastTag = -1;
                        // count重置为0
                        count = 0;
                    }
                    if (tmpBuffer == null) {
                        // 判断临时缓存对象是否为空?是空,则创建临时缓存对象,待后续添加临时缓存数据
                        tmpBuffer = new StringBuilder(length);
                    } else {
                        // 不是空,则重置缓存内容,清除可能是上个引号缓存的数据。
                        tmpBuffer.setLength(0);
                    }
                    // 将是否在引号内的标识置为true(是)
                    inQuote = true;
                } else {
                    // 如果当前引号出现之前,已经在引号内了(即:第二次出现引号,表示引号结束),则进入当前逻辑。
                    int len = tmpBuffer.length();
                    if (len == 1) {
                        // 如果缓存中只有一个字符,则执行当前逻辑
                        char ch = tmpBuffer.charAt(0);
                        if (ch < 128) {
                            compiledCode.append((char) (TAG_QUOTE_ASCII_CHAR << 8 | ch));
                        } else {
                            compiledCode.append((char) (TAG_QUOTE_CHARS << 8 | 1));
                            compiledCode.append(ch);
                        }
                    } else {
                        // 如果缓存区不只有一个字符
                        encode(TAG_QUOTE_CHARS, len, compiledCode);
                        // 将缓存区字符原样添加到编译后数据中
                        compiledCode.append(tmpBuffer);
                    }
                    inQuote = false;
                }
                continue;
            }
            if (inQuote) {
                // 如果当前在引号内部,直接将本次字符添加到临时缓存区
                tmpBuffer.append(c);
                continue;
            }
            if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) {
                // 如果当前没有在引号中,且字符不是26个字母中的一个(任意大小写)
                if (count != 0) {
                    // 如果count不是0,则进入当前逻辑
                    encode(lastTag, count, compiledCode);
                    tagcount++;
                    prevTag = lastTag;
                    lastTag = -1;
                    count = 0;
                }
                if (c < 128) {
                    // 字符小于128?什么含义?TAG_QUOTE_ASCII_CHAR = 100,左移8位,再按位或当前字符
                    // ASCII码在128以内的,是常见字符;128及以上为不常见字符,包括很多特殊符号
                    // 若是常见字符,则用100左移8位再 按位或 c
                    // In most cases, c would be a delimiter, such as ':'.
                    compiledCode.append((char) (TAG_QUOTE_ASCII_CHAR << 8 | c));
                } else {
                    // Take any contiguous non-ASCII alphabet characters and
                    // put them in a single TAG_QUOTE_CHARS.
                    // 遇到不常见字符,进入内循环,直到遇到引号,或找到下一个英文字母
                    int j;
                    for (j = i + 1; j < length; j++) {
                        char d = pattern.charAt(j);
                        if (d == '\'' || (d >= 'a' && d <= 'z' || d >= 'A' && d <= 'Z')) {
                            break;
                        }
                    }
                    // 不常见字符,101左移8位,或 j-i。j代表再次出现的常见字符索引,i代表首次出现不常见字符的索引。由此可知,j-i是不常见字符的长度。
                    compiledCode.append((char) (TAG_QUOTE_CHARS << 8 | (j - i)));
                    for (; i < j; i++) {
                        // 将每个不常见字符原样保存到解析结果中
                        compiledCode.append(pattern.charAt(i));
                    }
                    // 由于上面的for循环的i++,最终i会是回退至不常见字符后的常见字符索引
                    i--;
                }
                continue;
            }
            // 字符在约定字符中的索引
            int tag;
            if ((tag = DateFormatSymbols.patternChars.indexOf(c)) == -1) {
                // 经过上面的逻辑执行,如果发现不是约定字符,则直接抛出异常:非法字符
                throw new IllegalArgumentException("Illegal pattern character " +
                        "'" + c + "'");
            }
            if (lastTag == -1 || lastTag == tag) {
                // 首次出现约定字符,或间隔一些非约定字符后,再次出现约定字符 || 上个约定字符与本约定字符一样(连续出现)
                // 字符中索引标志变更
                lastTag = tag;
                // 字符数量+1
                count++;
                continue;
            }
            // 连续出现的一批约定字符结束,编译并保存结果
            encode(lastTag, count, compiledCode);
            // 约定字符出现次数+1
            tagcount++;
            // 上次出现的约定字符索引与变更为本次约定字符索引
            prevTag = lastTag;
            // 本次出现的字符索引再次确认变更
            lastTag = tag;
            // 出现次数重置为1
            count = 1;
        }
        if (inQuote) {
            // 字符都遍历编译完了,还在引号内,抛出异常
            throw new IllegalArgumentException("Unterminated quote");
        }
        // 如果待编译的字符数量不为0,则编译
        if (count != 0) {
            encode(lastTag, count, compiledCode);
            tagcount++;
            prevTag = lastTag;
        }
        // 只有一个约定字符,且约定字符标志是2?(其中 PATTERN_MONTH = 2 )
        forceStandaloneForm = (tagcount == 1 && prevTag == PATTERN_MONTH);
        // Copy the compiled pattern to a char array
        int len = compiledCode.length();
        char[] r = new char[len];
        compiledCode.getChars(0, len, r, 0);
        return r;
    }
开头有趣发现中的报错,就是该部分源码中抛出的:
java        if (inQuote) {
            throw new IllegalArgumentException("Unterminated quote");
        }
在pattern解析成compiledPattern时,单引号''被视为特殊作用,单引号内部的内容,不会被解析,会被原样保留,即像格式定义中的中文一样不会解析。
经过阅读源码,我将pattern解析为compiledPattern的逻辑简单概括为以下三步:
pattern中的每一个字符。compiledPattern中。详细逻辑请阅读上方流程图。
其中主要代码都集中在第2步,判断条件先后顺序又可概括为:是否是单引号中内容 > 是否是英文字母,如果是需要被解析赋值的字符,会执行encode(int tag, int length, StringBuilder buffer)方法,将其转译为待处理的字符集,encode源码如下:
java    /**
     * Encodes the given tag and length and puts encoded char(s) into buffer.
     */
    private static void encode(int tag, int length, StringBuilder buffer) {
        if (tag == PATTERN_ISO_ZONE && length >= 4) {
            throw new IllegalArgumentException("invalid ISO 8601 format: length=" + length);
        }
        if (length < 255) {
            buffer.append((char)(tag << 8 | length));
        } else {
            buffer.append((char)((tag << 8) | 0xff));
            buffer.append((char)(length >>> 16));
            buffer.append((char)(length & 0xffff));
        }
    }
上面提到了约定字符的概念,其实就是SimpleDateFormat定义好的含有特殊含义的英文字母,如y代表年,M代表月等等,下面就给出一份约定的字符清单。(在SimpleDateFormat类的注释上也有完整清单)。
且在有一个常量中更是直接将所有字符定义为了一个字符串:DateFormatSymbols.patternChars
javastatic final String  patternChars = "GyMdkHmsSEDFwWahKzZYuXL";
| Letter | Date or Time Component | Presentation | Examples | 
|---|---|---|---|
| G | Era designator | Text | AD | 
| y | Year | Year | 1996;96 | 
| Y | Week year | Year | 2009;09 | 
| M | Month in year (context sensitive) | Month | July;Jul;07 | 
| L | Month in year (standalone form) | Month | July;Jul;07 | 
| w | Week in year | Number | 27 | 
| W | Week in month | Number | 2 | 
| D | Day in year | Number | 189 | 
| d | Day in month | Number | 10 | 
| F | Day of week in month | Number | 2 | 
| E | Day name in week | Text | Tuesday;Tue | 
| u | Day number of week (1 = Monday, ..., 7 = Sunday) | Number | 1 | 
| a | Am/pm marker | Text | PM | 
| H | Hour in day (0-23) | Number | 0 | 
| k | Hour in day (1-24) | Number | 24 | 
| K | Hour in am/pm (0-11) | Number | 0 | 
| h | Hour in am/pm (1-12) | Number | 12 | 
| m | Minute in hour | Number | 30 | 
| s | Second in minute | Number | 55 | 
| S | Millisecond | Number | 978 | 
| z | Time zone | General time zone | Pacific Standard Time;PST;GMT-08:00 | 
| Z | Time zone | RFC 822 time zone | -0800 | 
| X | Time zone | ISO 8601 time zone | -08;-0800;-08:00 | 
如果在格式定义时,使用了约定字符以外的英文字母,要用单引号框起来,否则会报错!
如下使用字符b后的报错:
java
// SimpleDateFormat simpleDateFormat = new SimpleDateFormat("G yyyy年 aaa b zzz YY年 MM 月 dd日 HH:mm:ss.S");
Exception in thread "main" java.lang.IllegalArgumentException: Illegal pattern character 'b'
	at java.text.SimpleDateFormat.compile(SimpleDateFormat.java:826)
	at java.text.SimpleDateFormat.initialize(SimpleDateFormat.java:634)
	at java.text.SimpleDateFormat.<init>(SimpleDateFormat.java:605)
	at java.text.SimpleDateFormat.<init>(SimpleDateFormat.java:580)
	at text.TextTest.main(TextTest.java:17)
有时我们不是在SimpleDateFormat类创建时就确定好日期格式,会用到applyPattern方法添加或变更新的日期格式定义。
在添加时,就会将新的格式解析,源码如下(PS:这里写个Impl好像有点多此一举吧?不是很理解。。。你觉得呢):
java    /**
     * Applies the given pattern string to this date format.
     *
     * @param pattern the new date and time pattern for this date format
     * @exception NullPointerException if the given pattern is null
     * @exception IllegalArgumentException if the given pattern is invalid
     */
    public void applyPattern(String pattern)
    {
        applyPatternImpl(pattern);
    }
    private void applyPatternImpl(String pattern) {
        compiledPattern = compile(pattern);
        this.pattern = pattern;
    }
对于format方法,想必大家用的最多的就是format(Date date)了,其实format(Date date)是DateFormat的一个方法,然后引用了自己的抽象方法StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition),该抽象方法只有一个具体实现,就是在SimpleDateFormat中,实现源码如下:
java    /**
     * Formats the given <code>Date</code> into a date/time string and appends
     * the result to the given <code>StringBuffer</code>.
     *
     * @param date the date-time value to be formatted into a date-time string.
     * @param toAppendTo where the new date-time text is to be appended.
     * @param pos the formatting position. On input: an alignment field,
     * if desired. On output: the offsets of the alignment field.
     * @return the formatted date-time string.
     * @exception NullPointerException if the given {@code date} is {@code null}.
     */
    @Override
    public StringBuffer format(Date date, StringBuffer toAppendTo,
                               FieldPosition pos)
    {
        pos.beginIndex = pos.endIndex = 0;
        return format(date, toAppendTo, pos.getFieldDelegate());
    }
    // Called from Format after creating a FieldDelegate
    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 = 0; i < compiledPattern.length; ) {
            int tag = compiledPattern[i] >>> 8;
            int count = compiledPattern[i++] & 0xff;
            if (count == 255) {
                count = compiledPattern[i++] << 16;
                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 | Date | 要转换为日期字符串的日期类型值 | 
| toAppendTo | StringBuffer | 存储处理后的数据,处理后的字符串数据添加到该参数中存储 | 
| pos | FieldPosition | 格式化数据的定位参数 | 
pos参数较为复杂,实际上类是DontCareFieldPosition,是通过DontCareFieldPosition.INSTANCE获得的单例类,在此不做深入阅读,可认为其作用是:用于确定日期格式化后每个字符的索引位置。
该方法调用逻辑与format逻辑基本一致,由DateFormat为入口,最终实现是在SimpleDateFormat中。实现源码如下:
java
    /**
     * Parses text from a string to produce a <code>Date</code>.
     * <p>
     * The method attempts to parse text starting at the index given by
     * <code>pos</code>.
     * If parsing succeeds, then the index of <code>pos</code> is updated
     * to the index after the last character used (parsing does not necessarily
     * use all characters up to the end of the string), and the parsed
     * date is returned. The updated <code>pos</code> can be used to
     * indicate the starting point for the next call to this method.
     * If an error occurs, then the index of <code>pos</code> is not
     * changed, the error index of <code>pos</code> is set to the index of
     * the character where the error occurred, and null is returned.
     *
     * <p>This parsing operation uses the {@link DateFormat#calendar
     * calendar} to produce a {@code Date}. All of the {@code
     * calendar}'s date-time fields are {@linkplain Calendar#clear()
     * cleared} before parsing, and the {@code calendar}'s default
     * values of the date-time fields are used for any missing
     * date-time information. For example, the year value of the
     * parsed {@code Date} is 1970 with {@link GregorianCalendar} if
     * no year value is given from the parsing operation.  The {@code
     * TimeZone} value may be overwritten, depending on the given
     * pattern and the time zone value in {@code text}. Any {@code
     * TimeZone} value that has previously been set by a call to
     * {@link #setTimeZone(java.util.TimeZone) setTimeZone} may need
     * to be restored for further operations.
     *
     * @param text  A <code>String</code>, part of which should be parsed.
     * @param pos   A <code>ParsePosition</code> object with index and error
     *              index information as described above.
     * @return A <code>Date</code> parsed from the string. In case of
     *         error, returns null.
     * @exception NullPointerException if <code>text</code> or <code>pos</code> is null.
     */
    @Override
    public Date parse(String text, ParsePosition pos)
    {
        checkNegativeNumberExpression();
        int start = pos.index;
        int oldStart = start;
        int textLength = text.length();
        boolean[] ambiguousYear = {false};
        CalendarBuilder calb = new CalendarBuilder();
        for (int i = 0; i < compiledPattern.length; ) {
            int tag = compiledPattern[i] >>> 8;
            int count = compiledPattern[i++] & 0xff;
            if (count == 255) {
                count = compiledPattern[i++] << 16;
                count |= compiledPattern[i++];
            }
            switch (tag) {
            case TAG_QUOTE_ASCII_CHAR:
                if (start >= textLength || text.charAt(start) != (char)count) {
                    pos.index = oldStart;
                    pos.errorIndex = start;
                    return null;
                }
                start++;
                break;
            case TAG_QUOTE_CHARS:
                while (count-- > 0) {
                    if (start >= textLength || text.charAt(start) != compiledPattern[i++]) {
                        pos.index = oldStart;
                        pos.errorIndex = start;
                        return null;
                    }
                    start++;
                }
                break;
            default:
                // Peek the next pattern to determine if we need to
                // obey the number of pattern letters for
                // parsing. It's required when parsing contiguous
                // digit text (e.g., "20010704") with a pattern which
                // has no delimiters between fields, like "yyyyMMdd".
                boolean obeyCount = false;
                // In Arabic, a minus sign for a negative number is put after
                // the number. Even in another locale, a minus sign can be
                // put after a number using DateFormat.setNumberFormat().
                // If both the minus sign and the field-delimiter are '-',
                // subParse() needs to determine whether a '-' after a number
                // in the given text is a delimiter or is a minus sign for the
                // preceding number. We give subParse() a clue based on the
                // information in compiledPattern.
                boolean useFollowingMinusSignAsDelimiter = false;
                if (i < compiledPattern.length) {
                    int nextTag = compiledPattern[i] >>> 8;
                    if (!(nextTag == TAG_QUOTE_ASCII_CHAR ||
                          nextTag == TAG_QUOTE_CHARS)) {
                        obeyCount = true;
                    }
                    if (hasFollowingMinusSign &&
                        (nextTag == TAG_QUOTE_ASCII_CHAR ||
                         nextTag == TAG_QUOTE_CHARS)) {
                        int c;
                        if (nextTag == TAG_QUOTE_ASCII_CHAR) {
                            c = compiledPattern[i] & 0xff;
                        } else {
                            c = compiledPattern[i+1];
                        }
                        if (c == minusSign) {
                            useFollowingMinusSignAsDelimiter = true;
                        }
                    }
                }
                start = subParse(text, start, tag, count, obeyCount,
                                 ambiguousYear, pos,
                                 useFollowingMinusSignAsDelimiter, calb);
                if (start < 0) {
                    pos.index = oldStart;
                    return null;
                }
            }
        }
        // At this point the fields of Calendar have been set.  Calendar
        // will fill in default values for missing fields when the time
        // is computed.
        pos.index = start;
        Date parsedDate;
        try {
            parsedDate = calb.establish(calendar).getTime();
            // If the year value is ambiguous,
            // then the two-digit year == the default start year
            if (ambiguousYear[0]) {
                if (parsedDate.before(defaultCenturyStart)) {
                    parsedDate = calb.addYear(100).establish(calendar).getTime();
                }
            }
        }
        // An IllegalArgumentException will be thrown by Calendar.getTime()
        // if any fields are out of range, e.g., MONTH == 17.
        catch (IllegalArgumentException e) {
            pos.errorIndex = start;
            pos.index = oldStart;
            return null;
        }
        return parsedDate;
    }
经过源码阅读,可以发现SimpleDateFormat与很多类的关联性较强,主要有以下几个类:
CalendarLocaleNumberFormatDateFormatSymbolsFieldPosition要想真正了解SimpleDateFormat是如何实现日期格式转换的,以上重点关联类必须了解,本文到此为止,后续阅读这些关联类源码时,会将链接添加到本文适当位置。
本文作者:DingDangDog
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!