技識訓,該賞
點贊,收藏再看,養成習慣
大家好,我是小虛竹,之前有粉絲私聊我,問能不能把JAVA8 新的日期時間API(JSR-310)知識點梳理出來,答案是肯定的,誰讓我寵粉呢,由于內容偏多(超十萬字了),會拆成多篇來寫,
閑話就聊到這,請看下面的正文,
文章目錄
- DateTimeFormatter
- DateTimeFormatter類說明
- DateTimeFormatter常用的用法
- 格式化
- 決議
- DateTimeFormatter的坑
- 1、在正常配置按照標準格式的字串日期,是能夠正常轉換的,如果月,日,時,分,秒在不足兩位的情況需要補0,否則的話會轉換失敗,拋出例外,
- 2、YYYY和DD謹慎使用
- 3、DateTimeFormatter.format(Instant)會報錯
- 報錯資訊:
- 分析原因:
- 解決方案:
- DateTimeFormatterBuilder
- DateTimeFormatterBuilder類說明
- 決議風格配置
- 推薦相關文章
- hutool日期時間系列文章
- 其他
DateTimeFormatter
DateTimeFormatter類說明
DateTimeFormatter的作用是進行格式化日期時間顯示,且DateTimeFormatter是不可變類且是執行緒安全的,
public final class DateTimeFormatter {
...
}
說到時間的格式化顯示,就要說老朋友SimpleDateFormat了,之前格式化Date就要用上,但是我們知道SimpleDateFormat是執行緒不安全的,還不清楚的,請看這篇文章java的SimpleDateFormat執行緒不安全出問題了,虛竹教你多種解決方案
DateTimeFormatter常用的用法
格式化
ZonedDateTime zonedDateTime = ZonedDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm ZZZZ");
System.out.println(formatter.format(zonedDateTime));
DateTimeFormatter usFormatter = DateTimeFormatter.ofPattern("E, MMMM/dd/yyyy HH:mm", Locale.US);
System.out.println(usFormatter.format(zonedDateTime));
DateTimeFormatter chinaFormatter = DateTimeFormatter.ofPattern("yyyy MMM dd EE HH:mm", Locale.CHINA);
System.out.println(chinaFormatter.format(zonedDateTime));

決議
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH時mm分ss秒");
String dateTime = "2021年08月22日 13時14分20秒";
LocalDateTime localDateTime = LocalDateTime.parse(dateTime, formatter);
System.out.println(localDateTime);

大家有沒有注意到,parse方法 是放在LocalDateTime類中 的,而不是DateTimeFormatter類中 ,這樣的設計符合正常的思路想法,想決議出LocalDateTime 的日期時間,那就用LocalDateTime ,想決議其他的JSR-310的日期時間物件,那就用對應的日期時間物件去決議,
博主把常用的日期時間API都看了,這些里面除了Clock (時鐘不需要決議的),其他都有實作parse方法 ,

DateTimeFormatter的坑
1、在正常配置按照標準格式的字串日期,是能夠正常轉換的,如果月,日,時,分,秒在不足兩位的情況需要補0,否則的話會轉換失敗,拋出例外,
DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
LocalDateTime dt1 = LocalDateTime.parse("2021-7-20 23:46:43.946", DATE_TIME_FORMATTER);
System.out.println(dt1);
會報錯:

java.time.format.DateTimeParseException: Text '2021-7-20 23:46:43.946' could not be parsed at index 5
分析原因:是格式字串與實際的時間不匹配
“yyyy-MM-dd HH:mm:ss.SSS”
“2021-7-20 23:46:43.946”
中間的月份格式是MM,實際時間是7
解決方案:保持格式字串與實際的時間匹配
DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
LocalDateTime dt1 = LocalDateTime.parse("2021-07-20 23:46:43.946", DATE_TIME_FORMATTER);
System.out.println(dt1);

2、YYYY和DD謹慎使用
LocalDate date = LocalDate.of(2020,12,31);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("YYYYMM");
// 結果是 202112
System.out.println( formatter.format(date));

Java’s DateTimeFormatter pattern “YYYY” gives you the week-based-year, (by default, ISO-8601 standard) the year of the Thursday of that week.
YYYY是取的當前周所在的年份,week-based year 是 ISO 8601 規定的,2020年12月31號,周算年份,就是2021年

private static void tryit(int Y, int M, int D, String pat) {
DateTimeFormatter fmt = DateTimeFormatter.ofPattern(pat);
LocalDate dat = LocalDate.of(Y,M,D);
String str = fmt.format(dat);
System.out.printf("Y=%04d M=%02d D=%02d " +
"formatted with " +
"\"%s\" -> %s\n",Y,M,D,pat,str);
}
public static void main(String[] args){
tryit(2020,01,20,"MM/DD/YYYY");
tryit(2020,01,21,"DD/MM/YYYY");
tryit(2020,01,22,"YYYY-MM-DD");
tryit(2020,03,17,"MM/DD/YYYY");
tryit(2020,03,18,"DD/MM/YYYY");
tryit(2020,03,19,"YYYY-MM-DD");
}
Y=2020 M=01 D=20 formatted with "MM/DD/YYYY" -> 01/20/2020
Y=2020 M=01 D=21 formatted with "DD/MM/YYYY" -> 21/01/2020
Y=2020 M=01 D=22 formatted with "YYYY-MM-DD" -> 2020-01-22
Y=2020 M=03 D=17 formatted with "MM/DD/YYYY" -> 03/77/2020
Y=2020 M=03 D=18 formatted with "DD/MM/YYYY" -> 78/03/2020
Y=2020 M=03 D=19 formatted with "YYYY-MM-DD" -> 2020-03-79
最后三個日期是有問題的,因為大寫的DD代表的是處于這一年中那一天,不是處于這個月的那一天,但是dd就沒有問題,
例子參考于:https://www.cnblogs.com/tonyY/p/12153335.html
所以建議使用yyyy和dd,
3、DateTimeFormatter.format(Instant)會報錯
報錯資訊:
java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: YearOfEra
分析原因:
代碼**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
* @return 格式化后的字串
* @since 5.0.0
*/
public static String format(Date date, DateTimeFormatter format) {
if (null == format || null == date) {
return null;
}
Instant instant = date.toInstant();
ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());
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);

DateTimeFormatterBuilder
DateTimeFormatterBuilder類說明
DateTimeFormatter 的所有格式化器都是用DateTimeFormatterBuilder 建造器類創建的,
看下面兩個ofPattern 原始碼:
//DateTimeFormatter
public static DateTimeFormatter ofPattern(String pattern) {
return new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter();
}
public static DateTimeFormatter ofPattern(String pattern, Locale locale) {
return new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter(locale);
}
決議風格配置
官方提供了四種決議風格的配置,如下列舉 SettingsParser :
static enum SettingsParser implements DateTimePrinterParser {
// 大小寫敏感
SENSITIVE,
// 大小寫不敏感
INSENSITIVE,
//嚴格
STRICT,
//寬松
LENIENT;
...
}
對應DateTimeFormatterBuilder 類中的方法:
// 大小寫敏感
public DateTimeFormatterBuilder parseCaseSensitive()
// 大小寫不敏感
public DateTimeFormatterBuilder parseCaseInsensitive()
// 嚴格
public DateTimeFormatterBuilder parseStrict()
// 寬松
public DateTimeFormatterBuilder parseLenient()
這四個方法對應的原始碼如下:
// 大小寫敏感
public DateTimeFormatterBuilder parseCaseSensitive() {
appendInternal(SettingsParser.SENSITIVE);
return this;
}
// 大小寫不敏感
public DateTimeFormatterBuilder parseCaseInsensitive() {
appendInternal(SettingsParser.INSENSITIVE);
return this;
}
// 嚴格
public DateTimeFormatterBuilder parseStrict() {
appendInternal(SettingsParser.STRICT);
return this;
}
// 寬松
public DateTimeFormatterBuilder parseLenient() {
appendInternal(SettingsParser.LENIENT);
return this;
}
可以看出,都是呼叫appendInternal 方法,
接著往下看 appendInternal 原始碼:
private int appendInternal(DateTimePrinterParser pp) {
Objects.requireNonNull(pp, "pp");
if (active.padNextWidth > 0) {
if (pp != null) {
pp = new PadPrinterParserDecorator(pp, active.padNextWidth, active.padNextChar);
}
active.padNextWidth = 0;
active.padNextChar = 0;
}
active.printerParsers.add(pp);
active.valueParserIndex = -1;
return active.printerParsers.size() - 1;
}
其中active 是一個DateTimeFormatterBuilder 實體,且這個DateTimeFormatterBuilder 實體內部有一個串列 List< DateTimePrinterParser > ,看了原始碼可知,真正做決議作業的是DateTimePrinterParser 對應的實體來做的,
DateTimePrinterParser 的原始碼:
interface DateTimePrinterParser {
boolean format(DateTimePrintContext context, StringBuilder buf);
int parse(DateTimeParseContext context, CharSequence text, int position);
}
原始碼有一共有16個DateTimePrinterParser 的實體,
//1.Composite printer and parser.
static final class CompositePrinterParser implements DateTimePrinterParser {...}
//2.Pads the output to a fixed width.
static final class PadPrinterParserDecorator implements DateTimePrinterParser {...}
//3.Enumeration to apply simple parse settings.
static enum SettingsParser implements DateTimePrinterParser{...}
//4. Defaults a value into the parse if not currently present.
static class DefaultValueParser implements DateTimePrinterParser {...}
//5.Prints or parses a character literal.
static final class CharLiteralPrinterParser implements DateTimePrinterParser {...}
//6.Prints or parses a string literal.
static final class StringLiteralPrinterParser implements DateTimePrinterParser {...}
//7.Prints and parses a numeric date-time field with optional padding.
static class NumberPrinterParser implements DateTimePrinterParser {...}
//8.Prints and parses a numeric date-time field with optional padding.
static final class FractionPrinterParser implements DateTimePrinterParser {...}
//9.Prints or parses field text.
static final class TextPrinterParser implements DateTimePrinterParser {...}
//10.Prints or parses an ISO-8601 instant.
static final class InstantPrinterParser implements DateTimePrinterParser {...}
//11.Prints or parses an offset ID.
static final class OffsetIdPrinterParser implements DateTimePrinterParser {...}
//12.Prints or parses an offset ID.
static final class LocalizedOffsetIdPrinterParser implements DateTimePrinterParser {...}
//13.Prints or parses a zone ID.
static class ZoneIdPrinterParser implements DateTimePrinterParser {...}
//14. Prints or parses a chronology.
static final class ChronoPrinterParser implements DateTimePrinterParser {...}
//15.Prints or parses a localized pattern.
static final class LocalizedPrinterParser implements DateTimePrinterParser {...}
//16.Prints or parses a localized pattern from a localized field.
static final class WeekBasedFieldPrinterParser implements DateTimePrinterParser {...}
推薦相關文章
hutool日期時間系列文章
1DateUtil(時間工具類)-當前時間和當前時間戳
2DateUtil(時間工具類)-常用的時間型別Date,DateTime,Calendar和TemporalAccessor(LocalDateTime)轉換
3DateUtil(時間工具類)-獲取日期的各種內容
4DateUtil(時間工具類)-格式化時間
5DateUtil(時間工具類)-決議被格式化的時間
6DateUtil(時間工具類)-時間偏移量獲取
7DateUtil(時間工具類)-日期計算
8ChineseDate(農歷日期工具類)
9LocalDateTimeUtil(JDK8+中的{@link LocalDateTime} 工具類封裝)
10TemporalAccessorUtil{@link TemporalAccessor} 工具類封裝
其他
要探索JDK的核心底層原始碼,那必須掌握native用法
萬字博文教你搞懂java原始碼的日期和時間相關用法
java的SimpleDateFormat執行緒不安全出問題了,虛竹教你多種解決方案
原始碼分析:JDK獲取默認時區的風險和最佳實踐
高級JAVA開發必備技能:時區的規則發生變化時,如何同步JDK的時區規則
今天是持續寫作的第 8 / 100 天,
可以關注我,點贊我、評論我、收藏我啦,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/298652.html
標籤:java
上一篇:看完后讓你成為武松,手把手教你打死Java中的紙老虎-【泛型】
下一篇:淺析 DDD 領域驅動設計
