高级JAVA开发必备技能:java8 新日期时间API((三)JSR-310:格式化和解析)(JAVA 小虚竹

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));

image-202107209416958

解析
		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);

image-2021082291306050

大家有没有注意到,parse方法 是放在LocalDateTime类中 的,而不是DateTimeFormatter类中 。这样的设计符合正常的思路想法,想解析出LocalDateTime 的日期时间,那就用LocalDateTime 。想解析其他的JSR-310的日期时间对象,那就用对应的日期时间对象去解析。

博主把常用的日期时间API都看了,这些里面除了Clock (时钟不需要解析的),其他都有实现parse方法

image-20210824903956

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);

会报错:

image-202107208183

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);

image-20210720504067

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

image-202107208183

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年

image-2021072059555

 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);

image-20210725195348793

到这里已经是jdk的源码了DateTimeFormatter.format

image-20210725195424950

image-20210725195522610

image-20210725195636339

从上面可知,会调用 NumberPrinterParser.format() NumberPrinterParser是在DateTimeFormatterBuilder类中的。

image-20210725195947802

到这一步会报错

image-20210725200153850

为什么会报错呢,我们来看下context.getValue(field)发生了什么:

image-20210725200349650

从上面代码可行,temporal实际上是Instant对象,Instant.getLong只支持四种字段类型。。

NANO_OF_SECOND
MICRO_OF_SECOND
MILLI_OF_SECOND
INSTANT_SECONDS

image-20210725200551164

如果不是上面这几种字段类型,则抛出异常

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);

image-20210725201444728

DateTimeFormatterBuilder

DateTimeFormatterBuilder类说明

DateTimeFormatter 的所有格式化器都是用DateTimeFormatterBuilder 建造器类创建的。

看下面两个ofPattern 源码:

在这里插入图片描述

感谢每一个认真阅读我文章的人,看着粉丝一路的上涨和关注,礼尚往来总是要有的:

① 2000多本Python电子书(主流和经典的书籍应该都有了)

② Python标准库资料(最全中文版)

③ 项目源码(四五十个有趣且经典的练手项目及源码)

④ Python基础入门、爬虫、web开发、大数据分析方面的视频(适合小白学习)

⑤ Python学习路线图(告别不入流的学习)

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里无偿获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 29
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值