2023-01-17
班门弄斧
00
请注意,本文编写于 674 天前,最后修改于 319 天前,其中某些信息可能已经过时。

目录

简介
有趣发现
测试实例
了解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 许可协议。转载请注明出处!