关于datetime:如何使用LocalDateTime解析/格式化日期? (Java 8)

How to parse/format dates with LocalDateTime? (Java 8)

Java 8添加了一个新的java.time API来处理日期和时间(JSR 310)。

我有日期和时间作为字符串(例如"2014-04-08 12:30")。 如何从给定的字符串中获取LocalDateTime实例?

在我使用LocalDateTime对象完成后:如何将LocalDateTime实例转换回具有与上面所示相同格式的字符串?


解析日期和时间

要从字符串创建LocalDateTime对象,可以使用静态LocalDateTime.parse()方法。它需要一个字符串和一个DateTimeFormatter作为参数。 DateTimeFormatter用于指定日期/时间模式。

1
2
3
String str ="1986-04-08 12:30";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime dateTime = LocalDateTime.parse(str, formatter);

格式化日期和时间

要从LocalDateTime对象创建格式化字符串,可以使用format()方法。

1
2
3
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime dateTime = LocalDateTime.of(1986, Month.APRIL, 8, 12, 30);
String formattedDateTime = dateTime.format(formatter); //"1986-04-08 12:30"

请注意,在DateTimeFormatter中有一些常用的日期/时间格式预定义为常量。例如:使用DateTimeFormatter.ISO_DATE_TIME从上面格式化LocalDateTime实例将导致字符串"1986-04-08T12:30:00"

parse()format()方法适用于所有与日期/时间相关的对象(例如LocalDateZonedDateTime)


如果String是ISO-8601格式,您也可以在String上使用LocalDate.parse()LocalDateTime.parse()而不为其提供图案。

例如,

1
2
3
4
5
6
7
String strDate ="2015-08-04";
LocalDate aLD = LocalDate.parse(strDate);
System.out.println("Date:" + aLD);

String strDatewithTime ="2015-08-04T10:11:30";
LocalDateTime aLDT = LocalDateTime.parse(strDatewithTime);
System.out.println("Date with Time:" + aLDT);

输出,

1
2
Date: 2015-08-04
Date with Time: 2015-08-04T10:11:30

只有在必须处理其他日期模式时才使用DateTimeFormatter
例如,dd MMM uuuu表示月份的日期(两位数),月份名称的三个字母(Jan,Feb,Mar,...)和四位数年份:

1
2
3
4
DateTimeFormatter dTF = DateTimeFormatter.ofPattern("dd MMM uuuu");
String anotherDate ="04 Aug 2015";
LocalDate lds = LocalDate.parse(anotherDate, dTF);
System.out.println(anotherDate +" parses to" + lds);

产量

1
04 Aug 2015 parses to 2015-08-04

还记得DateTimeFormatter对象是双向的;它既可以解析输入也可以格式化输出。

1
2
3
4
String strDate ="2015-08-04";
LocalDate aLD = LocalDate.parse(strDate);
DateTimeFormatter dTF = DateTimeFormatter.ofPattern("dd MMM uuuu");
System.out.println(aLD +" formats as" + dTF.format(aLD));

产量

1
2015-08-04 formats as 04 Aug 2015

(请参阅格式化和解析DateFormatter的模式的完整列表)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
  Symbol  Meaning                     Presentation      Examples
  ------  -------                     ------------      -------
   G       era                         text              AD; Anno Domini; A
   u       year                        year              2004; 04
   y       year-of-era                 year              2004; 04
   D       day-of-year                 number            189
   M/L     month-of-year               number/text       7; 07; Jul; July; J
   d       day-of-month                number            10

   Q/q     quarter-of-year             number/text       3; 03; Q3; 3rd quarter
   Y       week-based-year             year              1996; 96
   w       week-of-week-based-year     number            27
   W       week-of-month               number            4
   E       day-of-week                 text              Tue; Tuesday; T
   e/c     localized day-of-week       number/text       2; 02; Tue; Tuesday; T
   F       week-of-month               number            3

   a       am-pm-of-day                text              PM
   h       clock-hour-of-am-pm (1-12)  number            12
   K       hour-of-am-pm (0-11)        number            0
   k       clock-hour-of-am-pm (1-24)  number            0

   H       hour-of-day (0-23)          number            0
   m       minute-of-hour              number            30
   s       second-of-minute            number            55
   S       fraction-of-second          fraction          978
   A       milli-of-day                number            1234
   n       nano-of-second              number            987654321
   N       nano-of-day                 number            1234000000

   V       time-zone ID                zone-id           America/Los_Angeles; Z; -08:30
   z       time-zone name              zone-name         Pacific Standard Time; PST
   O       localized zone-offset       offset-O          GMT+8; GMT+08:00; UTC-08:00;
   X       zone-offset 'Z' for zero    offset-X          Z; -08; -0830; -08:30; -083015; -08:30:15;
   x       zone-offset                 offset-x          +0000; -08; -0830; -08:30; -083015; -08:30:15;
   Z       zone-offset                 offset-Z          +0000; -0800; -08:00;

   p       pad next                    pad modifier      1

   '       escape for text             delimiter
   '
'      single quote                literal           '
   [       optional section start
   ]       optional section end
   #       reserved for future use
   {       reserved for future use
   }       reserved for future use


上面的两个答案都很好地解释了关于字符串模式的问题但是,如果您正在使用ISO 8601,则无需应用DateTimeFormatter,因为LocalDateTime已经为它准备好了:

将LocalDateTime转换为时区ISO8601字符串

1
2
3
LocalDateTime ldt = LocalDateTime.now();
ZonedDateTime zdt = ldt.atZone(ZoneOffset.UTC); //you might use a different zone
String iso8601 = zdt.toString();

从ISO8601 String转换回LocalDateTime

1
2
3
String iso8601 ="2016-02-14T18:32:04.150Z";
ZonedDateTime zdt = ZonedDateTime.parse(iso8601);
LocalDateTime ldt = zdt.toLocalDateTime();

将具有日期和时间的字符串解析为特定时间点(Java将其称为"Instant")非常复杂。 Java已经在几次迭代中解决了这个问题。最新的一个java.timejava.time.chrono几乎涵盖了所有需求(Time Dilation :)除外。

然而,这种复杂性带来了很多困惑。

理解日期解析的关键是:

为什么Java有很多方法来解析日期

  • 有几种系统可以测量时间。例如,历史上的日本历法来自各自皇帝或王朝统治的时间范围。然后有例如UNIX时间戳。
    幸运的是,整个(商业)世界设法使用相同的。
  • 从历史上看,由于各种原因,系统正在从/向切换。例如。从朱利安历法到公元1582年的格里高利历。所以在此之前的"西方"日期需要区别对待。
  • 当然,这种变化并非立即发生。因为日历来自一些宗教的总部和欧洲其他地方相信其他食谱,例如德国直到1700年才转换。
  • ......为什么LocalDateTimeZonedDateTime等。太复杂了

  • 有时区。
    时区基本上是地球表面的"条纹"* [1],其权限遵循相同的规则,它何时具有时间偏移。这包括夏令时规则。
    各个地区的时区随时间而变化,主要取决于谁征服了谁。一个时区的规则也随着时间的推移而变化。

  • 有时间抵消。这与时区不同,因为时区可以是例如时区。"布拉格",但有夏季时间抵消和冬季时间抵消。
    如果您获得带有时区的时间戳,则偏移量可能会有所不同,具体取决于它所在年份的哪一部分。在闰日期间,时间戳可能意味着2个不同的时间,因此如果没有其他信息,则无法可靠地转换。
    注意:按时间戳我的意思是"包含日期和/或时间的字符串,可选择带有时区和/或时间偏移量。"

  • 多个时区可以在某些时段共享相同的时间偏移。例如,当夏令时偏移无效时,GMT / UTC时区与"伦敦"时区相同。

  • 为了使它更复杂(但这对你的用例来说并不重要):

  • 科学家观察地球的动态,随着时间的推移而变化;基于此,他们在个别年末增加秒数。 (因此2040-12-31 24:00:00可能是有效的日期时间。)这需要定期更新系统用于使日期转换正确的元数据。例如。在Linux上,您可以定期更新Java包,包括这些新数据。
  • 更新并不总是保留历史和未来时间戳的先前行为。因此,当在不同版本的软件上运行时,对比某些时区的两个时间戳进行解析可能会产生不同的结果。这也适用于比较受影响的时区和其他时区。

    如果这会导致软件出现错误,请考虑使用一些没有复杂规则的时间戳,例如UNIX时间戳。

  • 由于7,对于未来的日期,我们无法准确地转换日期。因此,例如,当前解析8524-02-17 12:00:00可能会在未来解析后的几秒内完成。

  • JDK的API随着当代需求而发展

  • 早期的Java版本只有java.util.Date,它有点天真的方法,假设只有年,月,日和时间。这很快就不够了。
  • 此外,数据库的需求是不同的,所以很早就引入了java.sql.Date,它有自己的局限性。
  • 由于两者都没有很好地涵盖不同的日历和时区,因此引入了Calendar API。
  • 这仍然没有涵盖时区的复杂性。然而,上述API的混合使用真的很痛苦。因此,当Java开发人员开始研究全球Web应用程序时,针对大多数用例的库(如JodaTime)很快就会受到欢迎。 JodaTime是大约十年的事实标准。
  • 但JDK没有与JodaTime集成,因此使用它有点麻烦。因此,经过长时间讨论如何解决问题后,JSR-310主要基于JodaTime创建。
  • 如何在Java的java.time中处理它

    确定要解析时间戳的类型

    在使用时间戳字符串时,您需要知道它包含哪些信息。这是至关重要的一点。如果你没有做到这一点,你最终会得到一个神秘的例外,例如"无法创建即时"或"区域偏移丢失"或"未知区域ID"等。

  • 无法从TemporalAccessor获取OffsetDateTime
  • 无法从TemporalAccessor获取ZonedDateTime
  • 无法从TemporalAccessor获取LocalDateTime
  • 无法从TemporalAccessor获取Instant
  • 它包含日期和时间吗?

  • 它有时间偏移吗?
    时间偏移是+hh:mm部分。有时,+00:00可以用Z代替'祖鲁时间',UTC代替世界时间协调,或GMT代替格林威治标准时间。这些也设置了时区。
    对于这些时间戳,您使用OffsetDateTime

  • 它有时区吗?
    对于这些时间戳,您使用ZonedDateTime
    区域由。指定

  • 名称("布拉格","太平洋标准时间","太平洋标准时间"),或
  • "区域ID"("America / Los_Angeles","Europe / London"),由java.time.ZoneId表示。
  • 时区列表由ICAAN支持的"TZ数据库"编译。

    根据ZoneId的javadoc,区域id也可以某种方式指定为Z和offset。我不确定这是如何映射到真实区域的。
    如果只有TZ的时间戳落入时间偏移变化的闰日,那么它是不明确的,并且解释是ResolverStyle的主题,见下文。

  • 如果两者都没有,则假设或忽略缺失的上下文。消费者必须做出决定。因此需要将其解析为LocalDateTime并通过添加缺少的信息转换为OffsetDateTime

  • 您可以假设它是UTC时间。添加0小时的UTC偏移量。
  • 您可以假设它是转换发生地点的时间。通过添加系统的时区来转换它。
  • 您可以忽略并按原样使用它。这很有用,例如比较或减去两次(见Duration),或者当你不知道并且它并不重要时(例如当地的公交时刻表)。
  • 部分时间信息

  • 根据时间戳包含的内容,您可以从中取出LocalDateLocalTimeOffsetTimeMonthDayYearYearMonth
  • 如果您有完整的信息,可以获得java.time.Instant。这也在内部用于在OffsetDateTimeZonedDateTime之间进行转换。

    弄清楚如何解析它

    有关DateTimeFormatter的大量文档,它们既可以解析时间戳字符串,也可以格式化为字符串。

    预先创建的DateTimeFormatter应该涵盖所有标准时间戳格式。例如,ISO_INSTANT可以解析2011-12-03T10:15:30.123457Z

    如果你有一些特殊的格式,那么你可以创建自己的DateTimeFormatter(它也是一个解析器)。

    1
    2
    3
    4
    private static final DateTimeFormatter TIMESTAMP_PARSER = new DateTimeFormatterBuilder()
       .parseCaseInsensitive()
       .append(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SX"))
       .toFormatter();

    我建议查看DateTimeFormatter的源代码,并从如何使用DateTimeFormatterBuilder构建一个来获得灵感。当你在那里时,还要看看ResolverStyle,它控制格式和模糊信息的解析器是LENIENT,SMART还是STRICT。

    TemporalAccessor

    现在,经常出现的错误是进入TemporalAccessor的复杂性。这来自于开发人员如何使用SimpleDateFormatter.parse(String)。对,DateTimeFormatter.parse("...")给你TemporalAccessor

    1
    2
    // No need for this!
    TemporalAccessor ta = TIMESTAMP_PARSER.parse("2011-... etc");

    但是,配备上一节的知识,您可以方便地解析您需要的类型:

    1
    OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z", TIMESTAMP_PARSER);

    你实际上并不需要DateTimeFormatter。 要解析的类型具有parse(String)方法。

    1
    OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z");

    关于TemporalAccessor,如果您对字符串中的信息有一个模糊的概念,并且想要在运行时决定,则可以使用它。

    我希望我能理解你的灵魂:)

    注意:Java 3和7有一个java.time的backport:ThreeTen-Backport。 对于Android,它有ThreeTenABP。

    [1]不仅仅是它们不是条纹,还有一些奇怪的极端。 例如,一些邻近的太平洋岛屿有+14:00和-11:00时区。 这意味着,虽然在一个岛上,有一个5月1日下午,在另一个岛上,到目前为止,仍然是4月30日下午12点(如果我算得正确:))

    好。