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
与很多类的关联性较强,主要有以下几个类:
Calendar
Locale
NumberFormat
DateFormatSymbols
FieldPosition
要想真正了解SimpleDateFormat
是如何实现日期格式转换的,以上重点关联类必须了解,本文到此为止,后续阅读这些关联类源码时,会将链接添加到本文适当位置。
本文作者:DingDangDog
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!