日期類轉換在工作中是比較常用的。寫法如下:
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
String s = simpleDateFormat.format(date);
運作即可得出目前日期字元串。
下面我們就來分析一下該資料轉化的過程。首先建立SimpleDateFormat 。
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
建立會調用 SimpleDateFormat 的構造方法
public SimpleDateFormat(String pattern)
{
this(pattern, Locale.getDefault(Locale.Category.FORMAT));
}
構造方法中調用了this(pattern, Locale.getDefault(Locale.Category.FORMAT));
public SimpleDateFormat(String pattern, Locale locale)
{
if (pattern == null || locale == null) {
throw new NullPointerException();
}
//初始化公曆
initializeCalendar(locale);
this.pattern = pattern;
this.formatData = DateFormatSymbols.getInstanceRef(locale);
this.locale = locale;
initialize(locale);
}
構造方法中進行了初始化公曆和指派操作。GregorianCalendar是Calendar的子類。
initializeCalendar(locale);
private void initializeCalendar(Locale loc) {
if (calendar == null) {
assert loc != null;
calendar = Calendar.getInstance(TimeZone.getDefault(), loc);
}
}
public static Calendar getInstance(TimeZone zone,Locale aLocale){
return createCalendar(zone, aLocale);
}
private static Calendar createCalendar(TimeZone zone,Locale aLocale){
return new GregorianCalendar(zone, aLocale);
}
public GregorianCalendar(TimeZone zone, Locale aLocale) {
super(zone, aLocale);
gdate = (BaseCalendar.Date) gcal.newCalendarDate(zone);
//GregorianCalendar指派
setTimeInMillis(System.currentTimeMillis());
}
initialize()方法
private void initialize(Locale loc) {
// Verify and compile the given pattern.
compiledPattern = compile(pattern);
/* try the cache first */
numberFormat = cachedNumberFormatData.get(loc);
if (numberFormat == null) { /* cache miss */
numberFormat = NumberFormat.getIntegerInstance(loc);
numberFormat.setGroupingUsed(false);
/* update cache */
cachedNumberFormatData.putIfAbsent(loc, numberFormat);
}
numberFormat = (NumberFormat) numberFormat.clone();
initializeDefaultCentury();
}
compile(pattern)方法對日期格式進行解析
private char[] compile(String pattern) {
int length = pattern.length();
boolean inQuote = false;
StringBuilder compiledPattern = new StringBuilder(length * );
StringBuilder tmpBuffer = null;
int count = ;
int lastTag = -;
for (int i = ; i < length; i++) {
char c = pattern.charAt(i);
if (c == '\'') {
// '' is treated as a single quote regardless of being
// in a quoted section.
if ((i + ) < length) {
c = pattern.charAt(i + );
if (c == '\'') {
i++;
if (count != ) {
encode(lastTag, count, compiledPattern);
lastTag = -;
count = ;
}
if (inQuote) {
tmpBuffer.append(c);
} else {
compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR << | c));
}
continue;
}
}
if (!inQuote) {
if (count != ) {
encode(lastTag, count, compiledPattern);
lastTag = -;
count = ;
}
if (tmpBuffer == null) {
tmpBuffer = new StringBuilder(length);
} else {
tmpBuffer.setLength();
}
inQuote = true;
} else {
int len = tmpBuffer.length();
if (len == ) {
char ch = tmpBuffer.charAt();
if (ch < ) {
compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR << | ch));
} else {
compiledPattern.append((char)(TAG_QUOTE_CHARS << | ));
compiledPattern.append(ch);
}
} else {
encode(TAG_QUOTE_CHARS, len, compiledPattern);
compiledPattern.append(tmpBuffer);
}
inQuote = false;
}
continue;
}
if (inQuote) {
tmpBuffer.append(c);
continue;
}
if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) {
if (count != ) {
encode(lastTag, count, compiledPattern);
lastTag = -;
count = ;
}
if (c < ) {
// In most cases, c would be a delimiter, such as ':'.
compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR << | c));
} else {
// Take any contiguous non-ASCII alphabet characters and
// put them in a single TAG_QUOTE_CHARS.
int j;
for (j = i + ; j < length; j++) {
char d = pattern.charAt(j);
if (d == '\'' || (d >= 'a' && d <= 'z' || d >= 'A' && d <= 'Z')) {
break;
}
}
compiledPattern.append((char)(TAG_QUOTE_CHARS << | (j - i)));
for (; i < j; i++) {
compiledPattern.append(pattern.charAt(i));
}
i--;
}
continue;
}
int tag;
if ((tag = DateFormatSymbols.patternChars.indexOf(c)) == -) {
throw new IllegalArgumentException("Illegal pattern character " +
"'" + c + "'");
}
if (lastTag == - || lastTag == tag) {
lastTag = tag;
count++;
continue;
}
encode(lastTag, count, compiledPattern);
lastTag = tag;
count = ;
}
if (inQuote) {
throw new IllegalArgumentException("Unterminated quote");
}
if (count != ) {
encode(lastTag, count, compiledPattern);
}
// Copy the compiled pattern to a char array
int len = compiledPattern.length();
char[] r = new char[len];
compiledPattern.getChars(, len, r, );
return r;
}
/**
* Encodes the given tag and length and puts encoded char(s) into buffer.
*/
private static final void encode(int tag, int length, StringBuilder buffer) {
if (tag == PATTERN_ISO_ZONE && length >= ) {
throw new IllegalArgumentException("invalid ISO 8601 format: length=" + length);
}
if (length < ) {
buffer.append((char)(tag << | length));
} else {
buffer.append((char)((tag << ) | ));
buffer.append((char)(length >>> ));
buffer.append((char)(length & ));
}
}
compile方法中通過 pattern.charAt(i)擷取字元 c ,然後判斷 該字元是否在a - z 和 A - Z 範圍值内,不在範圍内則判斷c 值是否小于 128 小于則将 100 左移8位 和 字元c 做 或運算 并存儲到 compiledPattern中,執行下一次循環
private final static int TAG_QUOTE_ASCII_CHAR = ;
private final static int TAG_QUOTE_CHARS = ;
static final String patternChars = "GyMdkHmsSEDFwWahKzZYuXLc";
static final int PATTERN_ERA = ; // G
static final int PATTERN_YEAR = ; // y
static final int PATTERN_MONTH = ; // M
static final int PATTERN_DAY_OF_MONTH = ; // d
static final int PATTERN_HOUR_OF_DAY1 = ; // k
static final int PATTERN_HOUR_OF_DAY0 = ; // H
static final int PATTERN_MINUTE = ; // m
static final int PATTERN_SECOND = ; // s
static final int PATTERN_MILLISECOND = ; // S
static final int PATTERN_DAY_OF_WEEK = ; // E
static final int PATTERN_DAY_OF_YEAR = ; // D
static final int PATTERN_DAY_OF_WEEK_IN_MONTH = ; // F
static final int PATTERN_WEEK_OF_YEAR = ; // w
static final int PATTERN_WEEK_OF_MONTH = ; // W
static final int PATTERN_AM_PM = ; // a
static final int PATTERN_HOUR1 = ; // h
static final int PATTERN_HOUR0 = ; // K
static final int PATTERN_ZONE_NAME = ; // z
static final int PATTERN_ZONE_VALUE = ; // Z
static final int PATTERN_WEEK_YEAR = ; // Y
static final int PATTERN_ISO_DAY_OF_WEEK = ; // u
static final int PATTERN_ISO_ZONE = ; // X
static final int PATTERN_STANDALONE_MONTH = ; // L
static final int PATTERN_STANDALONE_DAY_OF_WEEK = ; // c
在 a - z 和 A - Z 範圍内則判斷該字元 c 在 patternChars 中的索引值。并判斷lastTag == -1 || lastTag == tag,結果為true 則 指派 lastTag = tag,count++ ,執行下次循環
encode(lastTag, count, compiledPattern);方法用來将字元所在patternChars 的索引值 左移8位後和該字元出現的個數做或運算并存儲到compiledPattern中。
Date date = new Date();這個比較簡單,建立日期類,并将目前毫秒值指派給date。
public Date() {
this(System.currentTimeMillis());
}
public Date(long date) {
fastTime = date;
}
然後調用simpleDateFormat的format()方法,将date作為參數傳進去;
public final String format(Date date){
return format(date, new StringBuffer(),
DontCareFieldPosition.INSTANCE).toString();
}
public StringBuffer format(Date date, StringBuffer toAppendTo,
FieldPosition pos){
pos.beginIndex = pos.endIndex = ;
return format(date, toAppendTo, pos.getFieldDelegate());
}
private StringBuffer format(Date date, StringBuffer toAppendTo,
FieldDelegate delegate) {
// Convert input date to time field list
calendar.setTime(date);
boolean useDateFormatSymbols = useDateFormatSymbols();
for (int i = ; i < compiledPattern.length; ) {
int tag = compiledPattern[i] >>> ;
int count = compiledPattern[i++] & ;
if (count == ) {
count = compiledPattern[i++] << ;
count |= compiledPattern[i++];
}
switch (tag) {
case TAG_QUOTE_ASCII_CHAR:
toAppendTo.append((char)count);
break;
case TAG_QUOTE_CHARS:
toAppendTo.append(compiledPattern, i, count);
i += count;
break;
default:
subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
break;
}
}
return toAppendTo;
}
将date指派給calendar。
compiledPattern[i]無符号右移8位即可得出 tag值,compiledPattern[i++] & 0xff得出count值。
private void subFormat(int patternCharIndex, int count,
FieldDelegate delegate, StringBuffer buffer,
boolean useDateFormatSymbols)
{
int maxIntCount = Integer.MAX_VALUE;
String current = null;
int beginOffset = buffer.length();
int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
int value;
if (field == CalendarBuilder.WEEK_YEAR) {
if (calendar.isWeekDateSupported()) {
value = calendar.getWeekYear();
} else {
// use calendar year 'y' instead
patternCharIndex = PATTERN_YEAR;
field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
value = calendar.get(field);
}
} else if (field == CalendarBuilder.ISO_DAY_OF_WEEK) {
value = CalendarBuilder.toISODayOfWeek(calendar.get(Calendar.DAY_OF_WEEK));
} else {
value = calendar.get(field);
}
int style = (count >= ) ? Calendar.LONG : Calendar.SHORT;
if (!useDateFormatSymbols && field != CalendarBuilder.ISO_DAY_OF_WEEK) {
current = calendar.getDisplayName(field, style, locale);
}
// Note: zeroPaddingNumber() assumes that maxDigits is either
// 2 or maxIntCount. If we make any changes to this,
// zeroPaddingNumber() must be fixed.
switch (patternCharIndex) {
case PATTERN_ERA: // 'G'
if (useDateFormatSymbols) {
String[] eras = formatData.getEras();
if (value < eras.length)
current = eras[value];
}
if (current == null)
current = "";
break;
case PATTERN_WEEK_YEAR: // 'Y'
case PATTERN_YEAR: // 'y'
if (calendar instanceof GregorianCalendar) {
if (count != )
zeroPaddingNumber(value, count, maxIntCount, buffer);
else // count == 2
zeroPaddingNumber(value, , , buffer); // clip 1996 to 96
} else {
if (current == null) {
zeroPaddingNumber(value, style == Calendar.LONG ? : count,
maxIntCount, buffer);
}
}
break;
case PATTERN_STANDALONE_MONTH: // 'L'
{
current = formatMonth(count, value, maxIntCount, buffer, useDateFormatSymbols,
true /* standalone */);
break;
}
case PATTERN_MONTH: // 'M'
{
current = formatMonth(count, value, maxIntCount, buffer, useDateFormatSymbols,
false /* standalone */);
break;
}
case PATTERN_HOUR_OF_DAY1: // 'k' 1-based. eg, 23:59 + 1 hour =>> 24:59
if (current == null) {
if (value == )
zeroPaddingNumber(calendar.getMaximum(Calendar.HOUR_OF_DAY)+,
count, maxIntCount, buffer);
else
zeroPaddingNumber(value, count, maxIntCount, buffer);
}
break;
case PATTERN_STANDALONE_DAY_OF_WEEK: // 'c'
{
current = formatWeekday(count, value, useDateFormatSymbols, true /* standalone */);
break;
}
case PATTERN_DAY_OF_WEEK: // 'E'
{
current = formatWeekday(count, value, useDateFormatSymbols, false /* standalone */);
break;
}
case PATTERN_AM_PM: // 'a'
if (useDateFormatSymbols) {
String[] ampm = formatData.getAmPmStrings();
current = ampm[value];
}
break;
case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM
if (current == null) {
if (value == )
zeroPaddingNumber(calendar.getLeastMaximum(Calendar.HOUR)+,
count, maxIntCount, buffer);
else
zeroPaddingNumber(value, count, maxIntCount, buffer);
}
break;
case PATTERN_ZONE_NAME: // 'z'
if (current == null) {
TimeZone tz = calendar.getTimeZone();
boolean daylight = (calendar.get(Calendar.DST_OFFSET) != );
int tzstyle = count < ? TimeZone.SHORT : TimeZone.LONG;
String zoneString;
if (formatData.isZoneStringsSet) {
zoneString = TimeZoneNames.getDisplayName(
formatData.getZoneStringsWrapper(), tz.getID(), daylight, tzstyle);
} else {
zoneString = tz.getDisplayName(daylight, tzstyle, formatData.locale);
}
if (zoneString != null) {
buffer.append(zoneString);
} else {
int offsetMillis = calendar.get(Calendar.ZONE_OFFSET) +
calendar.get(Calendar.DST_OFFSET);
buffer.append(TimeZone.createGmtOffsetString(true, true, offsetMillis));
}
}
break;
case PATTERN_ZONE_VALUE: // 'Z' ("-/+hhmm" form)
{
value = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
final boolean includeSeparator = (count >= );
final boolean includeGmt = (count == );
buffer.append(TimeZone.createGmtOffsetString(includeGmt, includeSeparator, value));
break;
}
case PATTERN_ISO_ZONE: // 'X'
value = calendar.get(Calendar.ZONE_OFFSET)
+ calendar.get(Calendar.DST_OFFSET);
if (value == ) {
buffer.append('Z');
break;
}
value /= ;
if (value >= ) {
buffer.append('+');
} else {
buffer.append('-');
value = -value;
}
CalendarUtils.sprintf0d(buffer, value / , );
if (count == ) {
break;
}
if (count == ) {
buffer.append(':');
}
CalendarUtils.sprintf0d(buffer, value % , );
break;
case PATTERN_MILLISECOND: // 'S'
// Fractional seconds must be treated specially. We must always convert the parsed
// value into a fractional second [0, 1) and then widen it out to the appropriate
// formatted size. For example, an initial value of 789 will be converted
// 0.789 and then become ".7" (S) or ".78" (SS) or "0.789" (SSS) or "0.7890" (SSSS)
// in the resulting formatted output.
if (current == null) {
value = (int) (((double) value / ) * Math.pow(, count));
zeroPaddingNumber(value, count, count, buffer);
}
break;
default:
// case PATTERN_DAY_OF_MONTH: // 'd'
// case PATTERN_HOUR_OF_DAY0: // 'H' 0-based. eg, 23:59 + 1 hour =>> 00:59
// case PATTERN_MINUTE: // 'm'
// case PATTERN_SECOND: // 's'
// case PATTERN_DAY_OF_YEAR: // 'D'
// case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F'
// case PATTERN_WEEK_OF_YEAR: // 'w'
// case PATTERN_WEEK_OF_MONTH: // 'W'
// case PATTERN_HOUR0: // 'K' eg, 11PM + 1 hour =>> 0 AM
// case PATTERN_ISO_DAY_OF_WEEK: // 'u' pseudo field, Monday = 1, ..., Sunday = 7
if (current == null) {
zeroPaddingNumber(value, count, maxIntCount, buffer);
}
break;
} // switch (patternCharIndex)
if (current != null) {
buffer.append(current);
}
int fieldID = PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex];
Field f = PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID[patternCharIndex];
delegate.formatted(fieldID, f, f, beginOffset, buffer.length(), buffer);
}
private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD =
{
Calendar.ERA, Calendar.YEAR, Calendar.MONTH, Calendar.DATE,
Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY, Calendar.MINUTE,
Calendar.SECOND, Calendar.MILLISECOND, Calendar.DAY_OF_WEEK,
Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH,
Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH,
Calendar.AM_PM, Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET,
Calendar.ZONE_OFFSET,
// Pseudo Calendar fields
CalendarBuilder.WEEK_YEAR,
CalendarBuilder.ISO_DAY_OF_WEEK,
Calendar.ZONE_OFFSET,
// 'L' and 'c',
Calendar.MONTH,
Calendar.DAY_OF_WEEK
};
先通過索引值擷取field,然後調用calendar.get(field)得出對應的資料。
private final void zeroPaddingNumber(int value, int minDigits, int maxDigits, StringBuffer buffer)
{
// Optimization for 1, 2 and 4 digit numbers. This should
// cover most cases of formatting date/time related items.
// Note: This optimization code assumes that maxDigits is
// either 2 or Integer.MAX_VALUE (maxIntCount in format()).
try {
if (zeroDigit == ) {
zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit();
}
if (value >= ) {
if (value < && minDigits >= && minDigits <= ) {
if (value < ) {
if (minDigits == ) {
buffer.append(zeroDigit);
}
buffer.append((char)(zeroDigit + value));
} else {
buffer.append((char)(zeroDigit + value / ));
buffer.append((char)(zeroDigit + value % ));
}
return;
} else if (value >= && value < ) {
if (minDigits == ) {
buffer.append((char)(zeroDigit + value / ));
value %= ;
buffer.append((char)(zeroDigit + value / ));
value %= ;
buffer.append((char)(zeroDigit + value / ));
buffer.append((char)(zeroDigit + value % ));
return;
}
if (minDigits == && maxDigits == ) {
zeroPaddingNumber(value % , , , buffer);
return;
}
}
}
} catch (Exception e) {
}
numberFormat.setMinimumIntegerDigits(minDigits);
numberFormat.setMaximumIntegerDigits(maxDigits);
numberFormat.format((long)value, buffer, DontCareFieldPosition.INSTANCE);
}
通過 zeroPaddingNumber()方法将 得到的資料存儲到buffer中,循環完成之後将buffer傳回回去即可得到對應的日期資料。