将一个带有日期和时间的字符串解析为一个特定的时间点(Java称其为"
Instant
")是相当复杂的。Java已经在几个迭代中解决了这个问题。最新的
java.time
和
java.time.chrono
,几乎涵盖了所有的需求(除了
时间扩张
:) ).
然而,这种复杂性带来了很多困惑。
理解日期解析的关键是。
Why does Java have so many ways to parse a date?
有several systems to measure a time. For instance, the historical Japanese calendars were derived from the time ranges of the reign of the respective emperor or dynasty. Then there is, e.g., the
Unix 时间戳
.
Fortunately, the whole (business) world managed to use the same.
Historically, the systems were being switched from/to, for
various reasons
. E.g., from the
Julian calendar
to the
Gregorian calendar
in 1582; so, the 'western' dates before that need to be treated differently.
And, of course, the change did not happen at once. Because the calendar came from the headquarters of some religion and other parts of Europe believed in other deities, for instance Germany did not switch until the year 1700.
...and why is the
LocalDateTime
,
ZonedDateTime
et al. so complicated
有
时区
.
一个时区基本上是一个 "条纹 "*。
[3]
地球表面的当局遵循相同的规则,即什么时候有哪个时间偏移。这包括夏季时间规则。
The 时区 随时间变化 for various areas, mostly based on who conquers whom. And one time zone's rules
随时间变化
也是如此。
有time offsets. That is not the same as 时区, because a time zone may be, e.g., "Prague", but that has summer time offset and winter time offset.
如果你得到一个带有时区的时间戳,其偏移量可能会有所不同,这取决于它在一年中的哪个部分。在闰时期间,时间戳可能意味着两个不同的时间,所以如果没有额外的信息,就不能可靠地转换。
Note: By
时间戳
我的意思是 "一个包含日期和/或时间的字符串,可以选择带有时区和/或时间偏移"。
Several 时区 may share the same time offset for certain periods. For instance, the GMT/UTC time zone is the same as the "London" time zone when the summer time offset is not in effect.
让它变得更复杂一些(但这对你的用例不是太重要)。
科学家们观察地球的动态,它随着时间的推移而变化;在此基础上,他们在个别年份的末尾增加秒。(所以
2040-12-31 24:00:00
可能是一个有效的日期-时间)。这需要定期更新系统使用的元数据,以使日期转换正确。例如,在Linux上,你会得到包括这些新数据的Java包的定期更新。
The updates do not always keep the previous behavior for both historical and future 时间戳s. So it may happen that parsing of the two 时间戳s around some time zone's change comparing them
可能会产生不同的结果
当在不同版本的软件上运行时。这也适用于在受影响的时区和其他时区之间进行比较。
Should this cause a bug in your software, consider using some 时间戳 that does not have such complicated rules, like
Unix 时间戳
.
因为7,对于未来的日期,我们不能确切地转换日期,有把握。因此,举例来说,当前解析的
8524-02-17 12:00:00
可能会与未来的解析结果相差几秒。
JDK's APIs for this evolved with the contemporary needs
The early Java releases had just
java.util.Date
which had a bit naive approach, assuming that there's just the year, month, day, and time. This quickly did not suffice.
Also, the needs of the databases were different, so quite early,
java.sql.Date
was introduced, with its own limitations.
Because neither covered different calendars and 时区 well, the
Calendar
API was introduced.
This still did not cover the complexity of the 时区. And yet, the mix of the above APIs was really a pain to work with. So as Java developers started working on global web applications, libraries that targeted most use cases, like JodaTime, got quickly popular. JodaTime was the de facto standard for about a decade.
But the JDK did not integrate with JodaTime, so working with it was a bit cumbersome. So, after a very long discussion on how to approach the matter,
JSR-310
was created
mainly based on JodaTime
.
How to deal with it in Java's
java.time
Determine what type to parse a 时间戳 to
When you are consuming a 时间戳 string, you need to know what information it contains.
这是关键的一点。
如果你没有弄清楚这一点,你就会出现 "无法创建即时"、"区域偏移丢失"、"未知区域ID "等令人费解的异常。
Unable to obtain OffsetDateTime from TemporalAccessor
Unable to obtain ZonedDateTime from TemporalAccessor
Unable to obtain LocalDateTime from TemporalAccessor
Unable to obtain Instant from TemporalAccessor
它是否包含日期和时间?
它是否有一个时间偏移?
时间偏移是指
+hh:mm
部分。有时。
+00:00
可以用
Z
称为 "祖鲁时间",
UTC
称为世界时间协调,或
GMT
as Greenwich Mean Time. These also set the time zone.
For these 时间戳s, you use
OffsetDateTime
.
它有一个时区吗?
For these 时间戳s, you use
ZonedDateTime
.
区是由以下两种方式指定的
name ("Prague", "Pacific Standard Time", "PST"), or
"zone ID" ("America/Los_Angeles", "Europe/London"), represented by
java.time.ZoneId
.
The list of 时区 is compiled by a
"TZ数据库"
撑腰的是
ICAAN
.
根据
ZoneId
的javadoc,区域ID也可以以某种方式被指定为
Z
和偏移。我不确定这如何映射到实际区域。
If the 时间戳, which only has a TZ, falls into a leap hour of time offset change, then it is ambiguous, and the interpretation is subject of
ResolverStyle
, see below.
如果它既没有
那么,缺失的背景就会被假定或忽略掉。而消费者必须决定。因此,它需要被解析为
LocalDateTime
并通过添加缺失的信息转换为
OffsetDateTime
。
You can
assume
that it is a UTC time. Add the UTC offset of 0 hours.
You can
assume
that it is a time of the place where the conversion is happening. Convert it by adding the system's time zone.
You can
neglect
and just use it as is. That is useful e.g. to compare or subtract two times (see
Duration
), or when you don't know and it doesn't really matter (e.g., local bus schedule).
部分时间信息
Based on what the 时间戳 contains, you can take
LocalDate
,
LocalTime
,
OffsetTime
,
MonthDay
,
Year
, or
YearMonth
out of it.
如果你有完整的信息,你可以得到一个
java.time.Instant
.这也是内部用来在
OffsetDateTime
和
ZonedDateTime
之间的转换。
Figure out how to parse it
有大量关于
DateTimeFormatter
which can both parse a 时间戳 string and format to string.
The
pre-created
DateTimeFormatter
s
should cover more or less all standard 时间戳 formats. For instance,
ISO_INSTANT
can parse
2011-12-03T10:15:30.123457Z
.
如果你有一些特殊的格式,那么你可以
创建你自己的DateTimeFormatter
(这也是一个分析器)。
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
。
// No need for this!
TemporalAccessor ta = TIMESTAMP_PARSER.parse("2011-... etc");
但是,配备了上一节的知识,你可以方便地解析到你需要的类型。
OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z", TIMESTAMP_PARSER);
你实际上也不需要DateTimeFormatter
。你想解析的类型有parse(String)
的方法。
OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z");
关于TemporalAccessor
,如果你对字符串中的信息有一个模糊的概念,并且想在运行时决定,你可以使用它。
我希望我给你的灵魂带来一些理解之光 :)
注意:java.time
有一个回传到Java 6和7。三联书店-Backport.对于安卓系统,它有三田ABP.
[3]不仅仅是它们没有条纹,而且还有一些奇怪的极端情况。例如,有些邻近的太平洋岛屿 have +14:00 and -11:00 时区. That means, that while on one island, there is 1st May 3 PM, on another island not so far, it is still 30 April 12 PM (if I counted correctly :) )