技术活,该赏
点赞,收藏再看,养成习惯
大家好,我是小虚竹。之前有粉丝私聊我,问能不能把JAVA8 新的日期时间API(JSR-310)知识点梳理出来。答案是肯定的,谁让我宠粉呢。由于内容偏多(超十万字了),会拆成多篇来写。
闲话就聊到这,请看下面的正文。
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 天。 可以关注 我,点赞 我、评论 我、收藏 我啦。