DOIFOR技术Java8中的时间问题
DOIFOR技术Java8中的时间问题

Java8中的时间问题

技术

问题

将时间字符串“Tue Aug 15 08:38:47 CST 2023”转换为对象
看到这个字符串,首先咱们可以确定时间表达式为

EEE MMM d HH:mm:ss zzz yyyy

其实这就是java.util.DatetoString方法的标准输出

使用SimpleDateFormat转换

String timeStr ="Tue Aug 15 08:38:47 CST 2023";
SimpleDateFormat dateFormat = new SimpleDateFormat("EEE MMM d HH:mm:ss zzzyyyy");
System.out.println("当前时间:"dateFormat.format(new Date()));
final Date time=dateFormat.parse(timeStr);
System.out.printn("转换时间:"time);

由于本地电脑是win10的中文系统,需要将Locale设置为英文,否则转换过程中星期、月份、时区等可能会变成中文,并且在将文中给定的字符串时会报错,输出如下:

当前时间:周二 8月 15 15:00:41 CST 2023
Exception in thread "main" java.text.ParseException:Unparseable date:"Tue Aug 15 08:38:47 CST 2023'
    at java.base/java.text.DateFormat.parse(DateFormat.java:395)
    at x.x.ProjectStorageOverTimeService.main(ProjectStorageOverTimeService.java:61)

因此,咱需要给SimpleDateFormat指定语言,就是在初始化对象的时候传入Locale.forLanguageTag("en")值,如下:

javaSimpleDateFormat dateFormat = 
        new SimpleDateFormat("EEE MMM d HH:mm:ss zzz yyyy", Locale.forLanguageTag("en"));

输出:

当前时间:Tue Aug 15 14:56:50 CST 2023
转换时间:Tue Aug 15 08:38:47 CST 2023

大功告成\~\~

但是,都知道SimpleDateFormat是线程不安全的那么有没有安全的呢?iava8以上的版本idk提供了DateTimeFormatter类,是一个线程安全的日期-字符串转换工具类。

DateTimeFormatter转换方法

按理说,咱们可以直接将SimpleDateFormat换成 DateTlmeFormatter的,如下

String timeStr ="Tue Aug 15 08:38:47 CST 2023";
final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE MMM dHH:mm:ss zzz yyyy", Locale.forLanguageTag("en"));
final String nowTimeStr = formatter.format(LocalDateTime.now());
System.out.println(nowTimeStr);
final TemporalAccessor parse = formatter.parse(timeStr);
System.out.println(ZonedDateTime.from(parse));

不幸的是,代码没有正常运行:

Exception in thread "main"java.time.DateTimeException: Unableto extract Zoneld from temporal2023-08-15T15:29:04.003822900
    at java.base/java.time.format.DateTimePrintContext.getValue(DateTimePrintContext.java:289)
    at java.base/java.time.format.DateTimeFormatterBuilderZoneTextPrinterParser.format(DateTimeFormatterBuilder.java:4066)
    at java.base/java.time.format.DateTimeFormatterBuilderCompositePrinterParser.format(DateTimeFormatterBuilder.java:2335)
    at java.base/java.time.format.DateTimeFormatter.formatTo(DateTimeFormatter.java:1843)
    at java.base/java.time.format.DateTimeFormatter.format(DateTimeFormatter.java:1817)
    at x.x.ProjectStorageOverTimeService.main(ProjectStorageOverTimeService.iava:64)

究其原因就是LocalDateTime没有时区信息,咱得把时区信息给加上。java提供了ZonedDateTime类来创建-个带时区的时间实例。新代码如下

String timeStr ="Tue Aug 15 08:38:47 CST 2023";
final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE MMM d HH:mm:ss zzz yyyy", Locale.forLanguageTag("en"));
final String nowTimeStr =formatter.format(ZonedDateTime.now());
System.out.println("当前时间:"+nowTimeStr);
final TemporalAccessor parse =formatter.parse(timeStr);
System.out.println("转换后时间:"+ZonedDateTime.from(parse));

可以运行了,结果依然不是我们想要的

当前时间:Tue Aug 15 15:46:26 CST 2023
转换后时间:2023-08-15T08:38:47-05:00[America/Chicago]

可以看出时间转字符串已经正常了,但是字符串转成时间对象后时区不正确。
为什么会出现这种情况呢?

这是因为字符串中的时区名称CST本身就有歧义,从TimeZone类的注释中可以看到:

For compatibility with JDK 1.1.x, some otherthree-letter time zone lDs (such as "PST", "CTT’"AST") are also supported. However, their use isdeprecated because the same abbreviation is often used for multiple time zones (for example, "CST"could be U.S. "Central Standard Time" and "China Standard Time"), and the Java platform can then only recognize one of them.

意思就是这种用法是过时的(deprecated)。然后CST在美国的时候可以被认为是”中央标准时间“,在中国可以被用于表示”中国标准时间"

为了解决这个,咱们需要在转换根据当前所在时区转换一下时区:

String timeStr="Tue Aug 15 08:38:47 CST 2023";
final DateTimeFormatter formatter =DateTimeFormatter.ofPattern("EEE MMM dHH:mm:ss zzz yyyy", Locale.forLanguageTag("en"));
final String nowTimeStr = formatter.format(ZonedDateTime.now());
System.out.println("当前时间:"+nowTimeStr);
final TemporalAccessor parse =formatter.parse(timeStr);
System.out.println("转换后时间" + ZonedDateTime.from(parse));
System.out.println("格式化输出:"+ formatter.format(parse));
final ZonedDateTime dateTime =ZonedDateTime.from(parse).withZoneSameLocal(Zoneld.systemDefault());
System.out.println("转换时区后时间:"+ dateTime);
System.out.println("格式化输出:"+formatter.format(dateTime));

此时输出结果就正常了

当前时间:Tue Aug 15 16:01:56 CST 2023
转换后时间2023-08-15T08:38:47-05:00[America/Chicago]
格式化输出:Tue Aug 15 08:38:47 CDT 2023
转换时区后时间:2023-08-15T08:38:47+08:00[Asia/Shanghai]
格式化输出:Tue Aug 15 08:38:47 CST 2023

另外,在处理这个时区错误的问题时,除了使用ZonedDateTime的方法以外,如果咱们业务本身不涉及到时区的情况下,可以直接忽略时区信息,直接调用LocalDateTime.from方法即可。不过这种方法有一个问题就是无法将该对象逆向转换成字符串,除非手动加上时区

String timeStr="Tue Aug 15 08:38:47 CST 2023";
final DateTimeFormatter formatter =DateTimeFormatter.ofPattern("EEE MMM dHH:mm:ss zzz yyyy", Locale.forLanguageTag("en"));
final String nowTimeStr =formatter.format(ZonedDateTime.now());
System.out.println("当前时间:"+nowTimeStr);
final TemporalAccessor parse =formatter.parse(timeStr);
System.out.println("转换后时间:"+ZonedDateTime.from(parse));
System.out.printn("格式化输出:"+formatter.format(parse));
System.out.println("忽略时区:"+LocalDateTime.from(parse));

输出:

当前时间:Tue Aug 15 16:14:14 CST 2023
转换后时间:2023-08-15T08:38:47-05:00[America/Chicago]
格式化输出:Tue Aug 15 08:38:47 CDT 2023
忽略时区:2023-08-15T08:38:47

此时,这个转换过程就大功告成了!

结语

如果仅仅是希望将String转换为Date对象,使用SimpleDateFormat转换是最简单也是最快的,这种方法的弊端就是线程不安全,在并发较低的场景中,该方法依然是首选。

如果使用Java8及以上的版本,优先考虑的还是使用DateTimeFormatter类来实现时间格式化输出和转换,这是一个线程安全的工具类,同时提供了需要预置的实例。使我们在开发时间类业务时安全性更高、代码更加优雅。另一方面java8的time设定了更多的概念,需要咱们在使用的时候进行相应的学习,入门较为陡峭,因此在一些简单业务中还是推荐使用SimpledateFormat来实现相关功能。具体情况还是得根据实际情况进行考量。

后续来了

在实际解析文件的过程中,出现了一种情况会转换失败

Exception in thread "main" java.time.format.DateTimeParseException: Text 'SatJul 1 06:28:03 CST 2023' could not be parsed atindex 8
    at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2046)
    at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1874)

问题分析:表达式中有个dayOfMonth的字段,用一个d进行匹配,这种方式在小于10的时候,dayOfMonth只会占一位。但是异常中是占了两位,高位空格补齐。占两位可以很容易得想到直接写成dd,不过这样不大大能实现高位空格补齐。那么需要怎么才能做到空格补齐呢?
经历了一系列的尝试和阅读文档,找到了关键字p`,在格式化表达式中用于占位。具体使用方法如下:

DateTimeFormatter FORMATTER =DateTimeFormatter.ofPattern("EEE MMM ppdHH:mm:ss zzz yyyy", Locale.forLanguageTag("en"));
final ZonedDateTime lessTen =ZonedDateTime.of(LocalDate.of(2023,8, 1), LocalTime.MAX, Zoneld.systemDefault());
String formatStr=FORMATTER.format(lessTen);
System.out.println("格式化输出:"+formatStr);
final TemporalAccessor parse =FORMATTER.parse(formatStr);
final ZonedDateTime time=ZonedDateTime.from(parse).withZoneSameLocal(Zoneld.systemDefault());
System.out.println("逆向转换:"+time);
final ZonedDatelme moreTen = ZonedDateTime.of(LocalDate.of(2023, 8, 15), LocalTime.MAX, Zoneld.systemDefault());
String formatStrMoreTen =FORMATTER.format(moreTen);
System.out.println("格式化输出:"+formatStrMoreTen);
final TemporalAccessor parseMoreTen =FORMATTER.parse(formatStrMoreTen);
final ZonedDateTime timeMoreTen =ZonedDateTime.from(parseMoreTen).withZoneSameLocal(Zoneld.systemDefault());
System.out.println("逆向转换:"+timeMoreTen);

结果输出:

格式化输出:Tue Aug 1 23:59:59 CST 2023
逆向转换:2023-08-01T23:59:59+08:00[Asia/Shanghai]
格式化输出:Tue Aug 15 23:59:59 CST 2023
逆向转换:2023-08-15T23:59:59+08:00[Asia/Shanghail

上述表达式中ppd表示dayOfMonth占用两个字符,当小于10的时候,高位空格补齐,当大于等于10的时候,直接全部展示。p仅用于占位,表明最少占用多少个字符。
在尝试的过程中还发现了一下种较为复杂的方案:使用DateTimeFormatterBuilder自行构造一个Formatter出来。不过这种方法较为复杂,builder中各方法的参数较多还未来得及进行尝试。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注