2023-01-17
班门弄斧
00

目录

简介
有趣发现
测试实例
了解SimpleDateFormat
类创建
构造方法
解析
入参
代码作用
格式解析compile
逻辑流程图
源码
总结
约定字符
提示
拓展
format方法
调用逻辑
解析
入参
parse方法
调用逻辑
重点关联

简介

JDK8 官网文档:javase/8/docs

提示:由于JDK内部众多类互相引用的情况很多,SimpleDateFormat源码也不例外,所以可能会涉及很多其他JDK原生的类,通常这些类在本文中只会说明其作用,不会深入讲解,因为本文是专注于SimpleDateFormat的源码阅读。

对于国内众多使用JDK8的Java开发者,SimpleDateFormat类应该不陌生,是日期格式化的常用类。

本文以实际使用出发,主要从三个使用点进行源码学习,分别是:类创建、format方法、parse方法。其中重点关注类创建相关代码,其他方法在本文中重点了解其调用逻辑,其具体的代码算法请自行阅读源码理解(PS:偷个懒,其实是我还没理解透彻。)。

有趣发现

经过阅读源码发现,以下代码会报错,报错内容是:java.lang.IllegalArgumentException: Unterminated quote。原因在于使用了'单引号,但使用的不正确:没有结尾。意思是如果使用单引号,必须正确使用,如:"G 'yyyy年' YY年 MM 月 dd日 ,这样不会报错,报错原因咱们阅读源码便知。

java
SimpleDateFormat 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类的源码中,会发现类注释很长,这里只粘出一部分注释:

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的族谱如下:

  • Format(抽象类)
    • DateFormat(抽象类)
      • SimpleDateFormat(普通类)

构造方法

SimpleDateFormat提供了四个构造方法,分别是:

  • 构造1:SimpleDateFormat()
  • 构造2:SimpleDateFormat(String pattern)
  • 构造3:SimpleDateFormat(String pattern, Locale locale)
  • 构造4: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为解析对象,分别解析入参和内部代码作用。

入参

参数名参数类型参数说明
patternString格式化日期的规则定义(简称格式定义),如常见的yyyy-MM-dd HH:mm:ss
localeLocale该类内部内容较多,可以深入学习,概括来讲,其内部存储当前程序所在的地区时区、国家、语言等等本地信息。

代码作用

构造内部主要工作是初始化SimpleDateFormat的一些属性,其中重要属性有:

属性名属性类型属性说明
calendarCalendar待格式化的日期会转为Calendar,解析时会直接复用Calendar类的方法
patternString格式化规则定义
formatDataDateFormatSymbols年、周等日期格式化方法的实现类
localeLocale本地时区
compiledPatternchar[]格式化规则解析后的数字
numberFormatNumberFormat数字格式化方法的实现类
cachedNumberFormatDataConcurrentMap<Locale, NumberFormat>Locale和NumberFormat的对应关系缓存,初始缓存大小是3

其中compiledPattern参数较为重要,是由将pattern参数解析而来,解析逻辑较为复杂,解析方法详情:

格式解析compile

逻辑流程图

解析

源码

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的逻辑简单概括为以下三步:

  1. 遍历pattern中的每一个字符。
  2. 判断字符是否需要解析赋值。
  3. 将每一个字符解析后的内容添加到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

java
static 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方法,想必大家用的最多的就是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; }

解析

入参

参数名参数类型参数说明
dateDate要转换为日期字符串的日期类型值
toAppendToStringBuffer存储处理后的数据,处理后的字符串数据添加到该参数中存储
posFieldPosition格式化数据的定位参数

pos参数较为复杂,实际上类是DontCareFieldPosition,是通过DontCareFieldPosition.INSTANCE获得的单例类,在此不做深入阅读,可认为其作用是:用于确定日期格式化后每个字符的索引位置。

parse方法

调用逻辑

该方法调用逻辑与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与很多类的关联性较强,主要有以下几个类:

  • Calendar
  • Locale
  • NumberFormat
  • DateFormatSymbols
  • FieldPosition

要想真正了解SimpleDateFormat是如何实现日期格式转换的,以上重点关联类必须了解,本文到此为止,后续阅读这些关联类源码时,会将链接添加到本文适当位置。

如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:DingDangDog

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!