技識訓,該賞
點贊再看,養成習慣
看本篇文章前,建議先對java原始碼的日期和時間有一定的了解,如果不了解的話,可以先看這篇文章:
萬字博文教你搞懂java原始碼的日期和時間相關用法
關聯文章:
hutool實戰(帶你掌握里面的各種工具)目錄
4hutool實戰:DateUtil-格式化時間
原始碼分析目的
知其然,知其所以然
專案參考
此博文的依據:hutool-5.6.5版本原始碼
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.6.5</version>
</dependency>
方法名稱:DateUtil.formatLocalDateTime(java.time.LocalDateTime)
方法描述
格式化日期時間<br>
格式 yyyy-MM-dd HH:mm:ss
原始碼分析一
/**
* 格式化日期時間<br>
* 格式 yyyy-MM-dd HH:mm:ss
*
* @param localDateTime 被格式化的日期
* @return 格式化后的字串
*/
public static String formatLocalDateTime(LocalDateTime localDateTime) {
return LocalDateTimeUtil.formatNormal(localDateTime);
}
首先formatLocalDateTime方法的入參是LocalDateTime(Java8支持的日期時間類,是執行緒安全的)
然后呼叫LocalDateTimeUtil.formatNormal(localDateTime)
//LocalDateTimeUtil
/**
* 格式化日期時間為yyyy-MM-dd HH:mm:ss格式
*
* @param time {@link LocalDateTime}
* @return 格式化后的字串
* @since 5.3.11
*/
public static String formatNormal(LocalDateTime time) {
return format(time, DatePattern.NORM_DATETIME_FORMATTER);
}
/**
* 格式化日期時間為指定格式
*
* @param time {@link LocalDateTime}
* @param formatter 日期格式化器,預定義的格式見:{@link DateTimeFormatter}
* @return 格式化后的字串
*/
public static String format(LocalDateTime time, DateTimeFormatter formatter) {
return TemporalAccessorUtil.format(time, formatter);
}
跟代碼,發現DatePattern.NORM_DATETIME_FORMATTER的日期時間格式為:
/**
* 標準日期時間格式,精確到秒:yyyy-MM-dd HH:mm:ss
*/
public static final String NORM_DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
然后會呼叫format(LocalDateTime time, DateTimeFormatter formatter),DateTimeFormatter 這個也是(Java8支持的日期格式化器類,是執行緒安全的),
hutool這里做了很好的示范,使用DateTimeFormatter替換了SimpleDateFormat(執行緒不安全的),
為什么SimpleDateFormat是執行緒不安全的,請看萬字博文教你搞懂java原始碼的日期和時間相關用法
然后我們繼續往下深挖TemporalAccessorUtil.format(time, formatter)
/**
* 格式化日期時間為指定格式
*
* @param time {@link TemporalAccessor}
* @param formatter 日期格式化器,預定義的格式見:{@link DateTimeFormatter}
* @return 格式化后的字串
* @since 5.3.10
*/
public static String format(TemporalAccessor time, DateTimeFormatter formatter) {
if (null == time) {
return null;
}
if(null == formatter){
formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
}
try {
return formatter.format(time);
} catch (UnsupportedTemporalTypeException e){
if(time instanceof LocalDate && e.getMessage().contains("HourOfDay")){
// 用戶傳入LocalDate,但是要求格式化帶有時間部分,轉換為LocalDateTime重試
return formatter.format(((LocalDate) time).atStartOfDay());
}else if(time instanceof LocalTime && e.getMessage().contains("YearOfEra")){
// 用戶傳入LocalTime,但是要求格式化帶有日期部分,轉換為LocalDateTime重試
return formatter.format(((LocalTime) time).atDate(LocalDate.now()));
}
throw e;
}
}
最前面加了兩個入參判空處理,time為null時,回傳null;formatter為null時,給格式默認值,eg:2011-12-03T10:15:30

然后執行formatter.format(time)相當于是DateTimeFormatter.format(LocalDateTime),這樣就格式化成功了,
值得一說的是**TemporalAccessorUtil.format(TemporalAccessor time, DateTimeFormatter formatter)**里面有加例外處理機制
try {
return formatter.format(time);
} catch (UnsupportedTemporalTypeException e){
if(time instanceof LocalDate && e.getMessage().contains("HourOfDay")){
// 用戶傳入LocalDate,但是要求格式化帶有時間部分,轉換為LocalDateTime重試
return formatter.format(((LocalDate) time).atStartOfDay());
}else if(time instanceof LocalTime && e.getMessage().contains("YearOfEra")){
// 用戶傳入LocalTime,但是要求格式化帶有日期部分,轉換為LocalDateTime重試
return formatter.format(((LocalTime) time).atDate(LocalDate.now()));
}
throw e;
}
因為入參TemporalAccessor time的實作類常用的有如下幾個(java8提供的):
- LocalDateTime
- LocalDate
- LocalTime
在進行日期時間轉化時,日期時間和要轉化的格式化字串要對應上,不然會拋出例外,所以做了如上的補救措施,
方法名稱:DateUtil.format(java.time.LocalDateTime, java.lang.String)
方法描述
根據特定格式格式化日期
原始碼分析一
/**
* 根據特定格式格式化日期
*
* @param localDateTime 被格式化的日期
* @param format 日期格式,常用格式見: {@link DatePattern}
* @return 格式化后的字串
*/
public static String format(LocalDateTime localDateTime, String format) {
return LocalDateTimeUtil.format(localDateTime, format);
}
首先:hutool提供了常用的日期時間格式
/**
* 日期格式化類,提供常用的日期格式化物件
*
*/
public class DatePattern {
...
}

然后:呼叫LocalDateTimeUtil.format(localDateTime, format)
/**
* 格式化日期時間為指定格式
*
* @param time {@link LocalDateTime}
* @param format 日期格式,類似于yyyy-MM-dd HH:mm:ss,SSS
* @return 格式化后的字串
*/
public static String format(LocalDateTime time, String format) {
if (null == time) {
return null;
}
return format(time, DateTimeFormatter.ofPattern(format));
}
原始碼**format(time, DateTimeFormatter.ofPattern(format))**可以拆解成兩部分:
-
DateTimeFormatter.ofPattern(format)
-
format(LocalDateTime time, DateTimeFormatter formatter)
第一部分:**DateTimeFormatter.ofPattern(format)**是把字串日期時間格式轉化為日期時間格式化物件DateTimeFormatter ;
注意DateTimeFormatter.ofPattern(format)的用法是有坑的(代碼詳解–>萬字博文教你搞懂java原始碼的日期和時間相關用法):
- 在正常配置按照標準格式的字串日期,是能夠正常轉換的,如果月,日,時,分,秒在不足兩位的情況需要補0,否則的話會轉換失敗,拋出例外,
- YYYY和DD謹慎使用
第二部分,format(LocalDateTime time, DateTimeFormatter formatter)上面有介紹了,這里就不水字了,
方法名稱:DateUtil.format(java.util.Date, java.text.DateFormat)
方法描述
根據特定格式格式化日期
原始碼分析一
/**
* 根據特定格式格式化日期
*
* @param date 被格式化的日期
* @param format 日期格式,常用格式見: {@link DatePattern}
* @return 格式化后的字串
*/
public static String format(Date date, String format) {
if (null == date || StrUtil.isBlank(format)) {
return null;
}
TimeZone timeZone = null;
if (date instanceof DateTime) {
timeZone = ((DateTime) date).getTimeZone();
}
return format(date, newSimpleFormat(format, null, timeZone));
}
從代碼中**format(Date date, String format)**方法提供了兩個入參,一個是Date 型別的 被格式化的日期和要日期格式的字串,這是為了兼容java8之前的舊日期時間API提供的方法,
方法內首先對兩個引數加了判空處理,
然后判斷時間是否是hutool的DateTime物件,如果是,則獲取時區TimeZone
接著呼叫format(date, newSimpleFormat(format, null, timeZone)),可拆解成兩部分:
-
newSimpleFormat(format, null, timeZone),獲取SimpleDateFormat物件(注:此方法是非執行緒安全的)
-
format(Date date, DateFormat format) 根據特定格式格式化日期
首先:**newSimpleFormat(format, null, timeZone)**代碼詳解:
/**
* 創建{@link SimpleDateFormat},注意此物件非執行緒安全!<br>
* 此物件默認為嚴格格式模式,即parse時如果格式不正確會報錯,
*
* @param pattern 運算式
* @param locale {@link Locale},{@code null}表示默認
* @param timeZone {@link TimeZone},{@code null}表示默認
* @return {@link SimpleDateFormat}
* @since 5.5.5
*/
public static SimpleDateFormat newSimpleFormat(String pattern, Locale locale, TimeZone timeZone) {
if (null == locale) {
locale = Locale.getDefault(Locale.Category.FORMAT);
}
final SimpleDateFormat format = new SimpleDateFormat(pattern, locale);
if (null != timeZone) {
format.setTimeZone(timeZone);
}
format.setLenient(false);
return format;
}
如果**format(Date date, String format)輸入的是Date物件的時間,那format(date, newSimpleFormat(format, null, timeZone))**具象化后,是這樣的:format(date, newSimpleFormat(format, null, null)),
//獲取當前的語言環境
locale = Locale.getDefault(Locale.Category.FORMAT);
然后new了一個SimpleDateFormat物件,并設定了時區和設定了setLenient,這個方法的含義是是否嚴格決議日期,setLenient設定為false時,就是嚴格決議日期:會嚴格按照日期時間格式,java不會幫忙計算,直接拋出例外,

然后**format(Date date, DateFormat format) **代碼分析:
/**
* 根據特定格式格式化日期
*
* @param date 被格式化的日期
* @param format {@link SimpleDateFormat}
* @return 格式化后的字串
*/
public static String format(Date date, DateFormat format) {
if (null == format || null == date) {
return null;
}
return format.format(date);
}
對兩個入參進行了判空處理,然后呼叫SimpleDateFormat.format(date),這是java8之前就有提供的方法,
方法名稱:DateUtil.format(java.util.Date, java.time.format.DateTimeFormatter)(方法有問題,已反饋,官方已修正)
方法描述
根據特定格式格式化日期
原始碼分析一
/**
* 根據特定格式格式化日期
*
* @param date 被格式化的日期
* @param format {@link DateTimeFormatter}
* @return 格式化后的字串
* @since 5.0.0
*/
public static String format(Date date, DateTimeFormatter format) {
if (null == format || null == date) {
return null;
}
return format.format(date.toInstant());
}
首先對兩個入參做了判空處理,
然后,執行了format.format(date.toInstant()),代碼可拆解成兩部分:
- date.toInstant():回傳Instant物件
- DateTimeFormatter.format(Instant):java8提供的格式化日期時間的方法
代碼**DateTimeFormatter.format(Instant)**是怎么處理的呢?
public String format(TemporalAccessor temporal) {
StringBuilder buf = new StringBuilder(32);
formatTo(temporal, buf);
return buf.toString();
}
首先new了個StringBuilder物件,用來拼接字串;
然后呼叫**formatTo(temporal, buf)**方法
public void formatTo(TemporalAccessor temporal, Appendable appendable) {
Objects.requireNonNull(temporal, "temporal");
Objects.requireNonNull(appendable, "appendable");
try {
DateTimePrintContext context = new DateTimePrintContext(temporal, this);
if (appendable instanceof StringBuilder) {
printerParser.format(context, (StringBuilder) appendable);
} else {
// buffer output to avoid writing to appendable in case of error
StringBuilder buf = new StringBuilder(32);
printerParser.format(context, buf);
appendable.append(buf);
}
} catch (IOException ex) {
throw new DateTimeException(ex.getMessage(), ex);
}
}
**formatTo(temporal, buf)**方法也是先判斷兩個入參空處理,
然后,Instant物件被封裝在一個新new的DateTimePrintContext物件
運行demo有問題,進行排查
//根據特定格式格式化日期
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String dateStr = DateUtil.format(new Date(),dtf);
System.out.println(dateStr);

到這里已經是jdk的原始碼了DateTimeFormatter.format



從上面可知,會呼叫 NumberPrinterParser.format() NumberPrinterParser是在DateTimeFormatterBuilder類中的,

到這一步會報錯

為什么會報錯呢,我們來看下context.getValue(field)發生了什么:

從上面代碼可行,temporal實際上是Instant物件,Instant.getLong只支持四種欄位型別,,
NANO_OF_SECOND
MICRO_OF_SECOND
MILLI_OF_SECOND
INSTANT_SECONDS

如果不是上面這幾種欄位型別,則拋出例外
DateUtil.format當遇到DateTimeFormatter會將Date物件首先轉換為Instant,因為缺少時區,導致報錯,
建議改法
/**
* 根據特定格式格式化日期
*
* @param date 被格式化的日期
* @param format {@link SimpleDateFormat} todo-zhw DateTimeFormatter
* @return 格式化后的字串
* @since 5.0.0
*/
public static String format(Date date, DateTimeFormatter format) {
if (null == format || null == date) {
return null;
}
Instant instant = date.toInstant();
ZoneId zoneId = ZoneId.systemDefault();
ZonedDateTime zonedDateTime = instant.atZone(zoneId);
LocalDateTime localDateTime = zonedDateTime.toLocalDateTime();
return format.format(localDateTime);
}
先把date型別轉化為LocalDateTime型別,然后再進行DateTimeFormatter.format(LocalDateTime)的格式化
測驗demo
//根據特定格式格式化日期
String str = "2021-07-25 20:11:25";
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:dd");
Date date = DateUtil.parse(str);
String dateStr = DateUtil.format(date,dtf);
System.out.println(dateStr);
Assert.assertEquals(str, dateStr);

官方改法
修訂版本 #5.7.5
/**
* 根據特定格式格式化日期
*
* @param date 被格式化的日期
* @param format {@link SimpleDateFormat} {@link DatePattern#NORM_DATETIME_FORMATTER}
* @return 格式化后的字串
* @since 5.0.0
*/
public static String format(Date date, DateTimeFormatter format) {
if (null == format || null == date) {
return null;
}
// java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: YearOfEra
// 出現以上報錯時,表示Instant時間戳沒有時區資訊,賦予默認時區
return TemporalAccessorUtil.format(date.toInstant(), format);
}
更換了新的呼叫方法TemporalAccessorUtil.format(date.toInstant(), format),date.toInstant()回傳Instant物件,則變成了TemporalAccessorUtil.format(Instant, format)
//TemporalAccessorUtil
/**
* 格式化日期時間為指定格式
*
* @param time {@link TemporalAccessor}
* @param formatter 日期格式化器,預定義的格式見:{@link DateTimeFormatter}
* @return 格式化后的字串
* @since 5.3.10
*/
public static String format(TemporalAccessor time, DateTimeFormatter formatter) {
if (null == time) {
return null;
}
if(null == formatter){
formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
}
try {
return formatter.format(time);
} catch (UnsupportedTemporalTypeException e){
if(time instanceof LocalDate && e.getMessage().contains("HourOfDay")){
// 用戶傳入LocalDate,但是要求格式化帶有時間部分,轉換為LocalDateTime重試
return formatter.format(((LocalDate) time).atStartOfDay());
}else if(time instanceof LocalTime && e.getMessage().contains("YearOfEra")){
// 用戶傳入LocalTime,但是要求格式化帶有日期部分,轉換為LocalDateTime重試
return formatter.format(((LocalTime) time).atDate(LocalDate.now()));
} else if(time instanceof Instant){
// 時間戳沒有時區資訊,賦予默認時區
return formatter.format(((Instant) time).atZone(ZoneId.systemDefault()));
}
throw e;
}
}
對比了下跟5.6.5版本的差異,新增了當time是Instant時,給一個默認的時區
else if(time instanceof Instant){
// 時間戳沒有時區資訊,賦予默認時區
return formatter.format(((Instant) time).atZone(ZoneId.systemDefault()));
}

方法名稱:DateUtil.formatDateTime(java.util.Date)
方法描述
格式化日期時間<br>
格式 yyyy-MM-dd HH:mm:ss
原始碼分析一
/**
* 格式化日期時間<br>
* 格式 yyyy-MM-dd HH:mm:ss
*
* @param date 被格式化的日期
* @return 格式化后的日期
*/
public static String formatDateTime(Date date) {
if (null == date) {
return null;
}
return DatePattern.NORM_DATETIME_FORMAT.format(date);
}
首先好習慣,先對入參進行判空處理
然后呼叫DatePattern.NORM_DATETIME_FORMAT.format(date)回傳FastDateFormat物件
針對只支持java8之前的程式,可以使用FastDateFormat執行緒安全替換SimpleDateFormat執行緒不安全–》原始碼分析
FastDateFormat 是一個執行緒安全的實作
來源 Apache Commons Lang 3.5
DatePattern.NORM_DATETIME_FORMAT-->
/**
* 標準日期時間格式,精確到秒 {@link FastDateFormat}:yyyy-MM-dd HH:mm:ss
*/
public static final FastDateFormat NORM_DATETIME_FORMAT = FastDateFormat.getInstance(NORM_DATETIME_PATTERN);
則轉化為了FastDateFormat.format(date)
//FastDateFormat
@Override
public String format(final Date date) {
return printer.format(date);
}
//FastDatePrinter
@Override
public String format(final Date date) {
final Calendar c = Calendar.getInstance(timeZone, locale);
c.setTime(date);
return applyRulesToString(c);
}
先把date轉化為Calendar,方便獲取日期和時間
private String applyRulesToString(final Calendar c) {
return applyRules(c, new StringBuilder(mMaxLengthEstimate)).toString();
}
private <B extends Appendable> B applyRules(final Calendar calendar, final B buf) {
try {
for (final Rule rule : this.rules) {
rule.appendTo(buf, calendar);
}
} catch (final IOException e) {
throw new DateException(e);
}
return buf;
}
核心的代碼是這塊
for (final Rule rule : this.rules) {
rule.appendTo(buf, calendar);
}
測驗demo
String dateStr = "2017-03-01";
Date date = DateUtil.parse(dateStr);
String formatDateTime = DateUtil.formatDateTime(date);
Assert.assertEquals("2017-03-01 00:00:00", formatDateTime);
斷點跟進代碼:

往下跟代碼

//FastDatePrinter
private static class PaddedNumberField implements NumberRule {
...
@Override
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
appendTo(buffer, calendar.get(mField));
}
/**
* {@inheritDoc}
*/
@Override
public final void appendTo(final Appendable buffer, final int value) throws IOException {
appendFullDigits(buffer, value, mSize);
}
...}
已經取到年份了:2017

往下跟代碼:

//FastDatePrinter
private static class CharacterLiteral implements Rule {
...
@Override
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
buffer.append(mValue);
}
...
}
就是把’-'字串直接拼接上去,
下一個獲取月份:

//FastDatePrinter
private static class TwoDigitMonthField implements NumberRule {
...
/**
* {@inheritDoc}
*/
@Override
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
}
/**
* {@inheritDoc}
*/
@Override
public final void appendTo(final Appendable buffer, final int value) throws IOException {
appendDigits(buffer, value);
}
...
}
獲取了月份:3

然后下一個:把’-'字串直接拼接上去,

繼續往下跟

//FastDatePrinter
private static class TwoDigitNumberField implements NumberRule {
...
@Override
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
appendTo(buffer, calendar.get(mField));
}
/**
* {@inheritDoc}
*/
@Override
public final void appendTo(final Appendable buffer, final int value) throws IOException {
if (value < 100) {
appendDigits(buffer, value);
} else {
appendFullDigits(buffer, value, 2);
}
}
...
}

這樣獲取到的日期:2017-03-01
然后加了一個空格字串

時分秒的呼叫,都是呼叫一樣的FastDatePrinter#TwoDigitNumberField,
//FastDatePrinter
private static class TwoDigitNumberField implements NumberRule {
...
@Override
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
appendTo(buffer, calendar.get(mField));
}
/**
* {@inheritDoc}
*/
@Override
public final void appendTo(final Appendable buffer, final int value) throws IOException {
if (value < 100) {
appendDigits(buffer, value);
} else {
appendFullDigits(buffer, value, 2);
}
}
...
}

這樣就得到了"2017-03-01 00:00:00"
方法名稱:DateUtil.formatDate(java.util.Date)
方法描述
格式化日期部分(不包括時間)<br>
格式 yyyy-MM-dd
原始碼分析一
/**
* 格式化日期部分(不包括時間)<br>
* 格式 yyyy-MM-dd
*
* @param date 被格式化的日期
* @return 格式化后的字串
*/
public static String formatDate(Date date) {
if (null == date) {
return null;
}
return DatePattern.NORM_DATE_FORMAT.format(date);
}
首先好習慣,先對入參進行判空處理
然后呼叫DatePattern.NORM_DATE_FORMAT.format(date)回傳FastDateFormat物件
針對只支持java8之前的程式,可以使用FastDateFormat執行緒安全替換SimpleDateFormat執行緒不安全–》原始碼分析
FastDateFormat 是一個執行緒安全的實作
來源 Apache Commons Lang 3.5
/**
* 標準日期格式 {@link FastDateFormat}:yyyy-MM-dd
*/
public static final FastDateFormat NORM_DATE_FORMAT = FastDateFormat.getInstance(NORM_DATE_PATTERN);
則轉化為了FastDateFormat.format(date),原始碼分析看上面
方法名稱:DateUtil.formatTime(java.util.Date)
方法描述
格式化時間<br>
格式 HH:mm:ss
原始碼分析一
/**
* 格式化時間<br>
* 格式 HH:mm:ss
*
* @param date 被格式化的日期
* @return 格式化后的字串
* @since 3.0.1
*/
public static String formatTime(Date date) {
if (null == date) {
return null;
}
return DatePattern.NORM_TIME_FORMAT.format(date);
}
首先好習慣,先對入參進行判空處理
然后呼叫DatePattern.NORM_TIME_FORMAT.format(date)回傳FastDateFormat物件
針對只支持java8之前的程式,可以使用FastDateFormat執行緒安全替換SimpleDateFormat執行緒不安全–》原始碼分析
FastDateFormat 是一個執行緒安全的實作
來源 Apache Commons Lang 3.5
/**
* 標準時間格式 {@link FastDateFormat}:HH:mm:ss
*/
public static final FastDateFormat NORM_TIME_FORMAT = FastDateFormat.getInstance(NORM_TIME_PATTERN);
則轉化為了FastDateFormat.format(date),原始碼分析看上面
方法名稱:DateUtil.formatHttpDate(java.util.Date)
方法描述
格式化為Http的標準日期格式<br>
標準日期格式遵循RFC 1123規范,格式類似于:Fri, 31 Dec 1999 23:59:59 GMT
原始碼分析一
/**
* 格式化為Http的標準日期格式<br>
* 標準日期格式遵循RFC 1123規范,格式類似于:Fri, 31 Dec 1999 23:59:59 GMT
*
* @param date 被格式化的日期
* @return HTTP標準形式日期字串
*/
public static String formatHttpDate(Date date) {
if (null == date) {
return null;
}
return DatePattern.HTTP_DATETIME_FORMAT.format(date);
}
首先好習慣,先對入參進行判空處理
然后呼叫DatePattern.HTTP_DATETIME_FORMAT.format(date)回傳FastDateFormat物件
針對只支持java8之前的程式,可以使用FastDateFormat執行緒安全替換SimpleDateFormat執行緒不安全–》原始碼分析
FastDateFormat 是一個執行緒安全的實作
來源 Apache Commons Lang 3.5
/**
* HTTP頭中日期時間格式 {@link FastDateFormat}:EEE, dd MMM yyyy HH:mm:ss z
*/
public static final FastDateFormat HTTP_DATETIME_FORMAT = FastDateFormat.getInstance(HTTP_DATETIME_PATTERN, TimeZone.getTimeZone("GMT"), Locale.US);
則轉化為了FastDateFormat.format(date),原始碼分析看上面
方法名稱:DateUtil.formatChineseDate(java.util.Date, boolean, boolean)
方法描述
格式化為中文日期格式,如果isUppercase為false,則回傳類似:2018年10月24日,否則回傳二〇一八年十月二十四日
原始碼分析一
/**
* 格式化為中文日期格式,如果isUppercase為false,則回傳類似:2018年10月24日,否則回傳二〇一八年十月二十四日
*
* @param date 被格式化的日期
* @param isUppercase 是否采用大寫形式
* @param withTime 是否包含時間部分
* @return 中文日期字串
* @since 5.3.9
*/
public static String formatChineseDate(Date date, boolean isUppercase, boolean withTime) {
if (null == date) {
return null;
}
if (false == isUppercase) {
return (withTime ? DatePattern.CHINESE_DATE_TIME_FORMAT : DatePattern.CHINESE_DATE_FORMAT).format(date);
}
return CalendarUtil.formatChineseDate(CalendarUtil.calendar(date), withTime);
}
首先好習慣,先對入參進行判空處理
當不采用大寫形式時
執行**(withTime ? DatePattern.CHINESE_DATE_TIME_FORMAT : DatePattern.CHINESE_DATE_FORMAT).format(date)**
這里用了一個多目運算,包含時間部分時,呼叫DatePattern.CHINESE_DATE_TIME_FORMAT .format(date), 不包含時間部分時,呼叫DatePattern.CHINESE_DATE_FORMAT.format(date)
/**
* 標準日期格式 {@link FastDateFormat}:yyyy年MM月dd日HH時mm分ss秒
*/
public static final FastDateFormat CHINESE_DATE_TIME_FORMAT = FastDateFormat.getInstance(CHINESE_DATE_TIME_PATTERN);
/**
* 標準日期格式 {@link FastDateFormat}:yyyy年MM月dd日
*/
public static final FastDateFormat CHINESE_DATE_FORMAT = FastDateFormat.getInstance(CHINESE_DATE_PATTERN);
由上面原始碼可知,兩個都是回傳FastDateFormat物件,
針對只支持java8之前的程式,可以使用FastDateFormat執行緒安全替換SimpleDateFormat執行緒不安全–》原始碼分析
FastDateFormat 是一個執行緒安全的實作
來源 Apache Commons Lang 3.5
/**
* HTTP頭中日期時間格式 {@link FastDateFormat}:EEE, dd MMM yyyy HH:mm:ss z
*/
public static final FastDateFormat HTTP_DATETIME_FORMAT = FastDateFormat.getInstance(HTTP_DATETIME_PATTERN, TimeZone.getTimeZone("GMT"), Locale.US);
則轉化為了FastDateFormat.format(date),原始碼分析看上面
當采用大寫形式時
執行CalendarUtil.formatChineseDate(CalendarUtil.calendar(date), withTime);
可以拆解成兩部分:
-
CalendarUtil.calendar(date):把date物件轉換為Calendar物件
-
CalendarUtil.formatChineseDate(Calendar calendar, boolean withTime):將指定Calendar時間格式化為純中文形式,例如
2018-02-24 12:13:14轉換為 二〇一八年二月二十四日(withTime為false) 2018-02-24 12:13:14 轉換為 二〇一八年二月二十四日一十二時一十三分一十四秒(withTime為true)
CalendarUtil.calendar(date)原始碼分析:
/**
* 轉換為Calendar物件
*
* @param date 日期物件
* @return Calendar物件
*/
public static Calendar calendar(Date date) {
if (date instanceof DateTime) {
return ((DateTime) date).toCalendar();
} else {
return calendar(date.getTime());
}
}
如果是hutool提供的DateTime型別,可以直接轉化獲取,
如果是date型別,則通過*cal.setTimeInMillis轉化為Calendar物件,
/**
* 轉換為Calendar物件
*
* @param millis 時間戳
* @return Calendar物件
*/
public static Calendar calendar(long millis) {
final Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(millis);
return cal;
}
有同學會發現,這個跟我們常用的Calendar.setTime(Date date),不一樣,看了下原始碼
//Calendar
public final void setTime(Date date) {
setTimeInMillis(date.getTime());
}
本質上是一樣的,,setTime方法里也是會呼叫setTimeInMillis方法,
CalendarUtil.formatChineseDate(Calendar calendar, boolean withTime)原始碼分析:
/**
* 將指定Calendar時間格式化為純中文形式,比如:
*
* <pre>
* 2018-02-24 12:13:14轉換為 二〇一八年二月二十四日(withTime為false)
* 2018-02-24 12:13:14 轉換為 二〇一八年二月二十四日一十二時一十三分一十四秒(withTime為true)
* </pre>
*
* @param calendar {@link Calendar}
* @param withTime 是否包含時間部分
* @return 格式化后的字串
* @since 5.3.9
*/
public static String formatChineseDate(Calendar calendar, boolean withTime) {
final StringBuilder result = StrUtil.builder();
// 年
String year = String.valueOf(calendar.get(Calendar.YEAR));
final int length = year.length();
for (int i = 0; i < length; i++) {
result.append(NumberChineseFormatter.numberCharToChinese(year.charAt(i), false));
}
result.append('年');
// 月
int month = calendar.get(Calendar.MONTH) + 1;
result.append(NumberChineseFormatter.format(month, false));
result.append('月');
// 日
int day = calendar.get(Calendar.DAY_OF_MONTH);
result.append(NumberChineseFormatter.format(day, false));
result.append('日');
if (withTime) {
// 時
int hour = calendar.get(Calendar.HOUR_OF_DAY);
result.append(NumberChineseFormatter.format(hour, false));
result.append('時');
// 分
int minute = calendar.get(Calendar.MINUTE);
result.append(NumberChineseFormatter.format(minute, false));
result.append('分');
// 秒
int second = calendar.get(Calendar.SECOND);
result.append(NumberChineseFormatter.format(second, false));
result.append('秒');
}
return result.toString().replace('零', '〇');
}
由原始碼可知,是按年月日時分秒,每個欄位進行轉化,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/291020.html
標籤:java
