本文翻译自:How to parse/format dates with LocalDateTime? (Java 8)
Java 8 added a new java.time API for working with dates and times ( JSR 310 ). Java 8添加了新的java.time API,用于处理日期和时间( JSR 310 )。
I have date and time as string (eg "2014-04-08 12:30"
). 我将日期和时间作为字符串(例如"2014-04-08 12:30"
)。 How can I obtain a LocalDateTime
instance from the given string? 如何从给定的字符串中获取LocalDateTime
实例?
After I finished working with the LocalDateTime
object: How can I then convert the LocalDateTime
instance back to a string with the same format as shown above? 在完成LocalDateTime
对象的工作后:如何将LocalDateTime
实例转换回具有上述相同格式的字符串?
参考:https://stackoom.com/question/1WFfS/如何使用LocalDateTime解析-格式化日期-Java
Parsing date and time 解析日期和时间
To create a LocalDateTime
object from a string you can use the static LocalDateTime.parse()
method. 要从字符串创建LocalDateTime
对象,可以使用静态LocalDateTime.parse()
方法。 It takes a string and a DateTimeFormatter
as parameter. 它需要一个字符串和一个DateTimeFormatter
作为参数。 The DateTimeFormatter
is used to specify the date/time pattern. DateTimeFormatter
用于指定日期/时间模式。
String str = "1986-04-08 12:30";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime dateTime = LocalDateTime.parse(str, formatter);
Formatting date and time 格式化日期和时间
To create a formatted string out a LocalDateTime
object you can use the format()
method. 要在LocalDateTime
对象中创建格式化的字符串,可以使用format()
方法。
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"
Note that there are some commonly used date/time formats predefined as constants in DateTimeFormatter
. 请注意,在DateTimeFormatter
中有一些预定义为常量的常用日期/时间格式。 For example: Using DateTimeFormatter.ISO_DATE_TIME
to format the LocalDateTime
instance from above would result in the string "1986-04-08T12:30:00"
. 例如:使用DateTimeFormatter.ISO_DATE_TIME
从上面格式化LocalDateTime
实例,将导致字符串"1986-04-08T12:30:00"
。
The parse()
and format()
methods are available for all date/time related objects (eg LocalDate
or ZonedDateTime
) parse()
和format()
方法可用于所有与日期/时间相关的对象(例如LocalDate
或ZonedDateTime
)
You can also use LocalDate.parse()
or LocalDateTime.parse()
on a String
without providing it with a pattern, if the String
is in ISO-8601 format . 您还可以使用LocalDate.parse()
或LocalDateTime.parse()
在String
没有为它提供一个模式,如果String
是ISO-8601格式 。
for example, 例如,
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);
Output , 输出 ,
Date: 2015-08-04
Date with Time: 2015-08-04T10:11:30
and use DateTimeFormatter
only if you have to deal with other date patterns, For example, dd MMM uuuu represents the day of the month (two digits), three letters of the name of the month (Jan, Feb, Mar,...), and a four-digit year: 并仅在必须处理其他日期模式时使用DateTimeFormatter
,例如, dd MMM uuuu表示月份中的日期(两位数字),表示月份名称的三个字母(Jan,Feb,Mar,...) ,以及四位数的年份:
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);
Output 输出量
04 Aug 2015 parses to 2015-08-04
also remember that the DateTimeFormatter
object is bidirectional; 还记得DateTimeFormatter
对象是双向的; it can both parse input and format output. 它可以解析输入和格式化输出。
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));
Output 输出量
2015-08-04 formats as 04 Aug 2015
(see complete list of Patterns for Formatting and Parsing DateFormatter ) (请参阅用于格式化和解析DateFormatter的模式的完整列表 )
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
Both answers above explain very well the question regarding string patterns. 上面的两个答案都很好地说明了有关字符串模式的问题。 However, just in case you are working with ISO 8601 there is no need to apply DateTimeFormatter
since LocalDateTime is already prepared for it: 但是,如果您正在使用ISO 8601 ,则无需应用DateTimeFormatter
因为已经为它准备了LocalDateTime:
Convert LocalDateTime to Time Zone ISO8601 String 将LocalDateTime转换为时区ISO8601字符串
LocalDateTime ldt = LocalDateTime.now();
ZonedDateTime zdt = ldt.atZone(ZoneOffset.UTC); //you might use a different zone
String iso8601 = zdt.toString();
Convert from ISO8601 String back to a LocalDateTime 从ISO8601字符串转换回LocalDateTime
String iso8601 = "2016-02-14T18:32:04.150Z";
ZonedDateTime zdt = ZonedDateTime.parse(iso8601);
LocalDateTime ldt = zdt.toLocalDateTime();
Parsing a string with date and time into a particular point in time (Java calls it an " Instant
") is quite complicated. 将带有日期和时间的字符串解析为特定的时间点(Java将其称为“ Instant
”)非常复杂。 Java has been tackling this in several iterations. Java已经通过多次迭代解决了这个问题。 The latest one, java.time
and java.time.chrono
, covers almost all needs (except Time Dilation :) ). 最新的java.time
和java.time.chrono
满足了几乎所有需求( Time java.time.chrono
:除外)。
However, that complexity brings a lot of confusion. 但是,这种复杂性带来了很多混乱。
The key to understand date parsing is: 了解日期解析的关键是:
LocalDateTime
, ZonedDateTime
et al. ...以及为什么是LocalDateTime
, ZonedDateTime
等。 so complicated 太复杂了 There are time zones . 有时区 。 A time zone is basically a "stripe" *[1] of the Earth's surface whose authorities follow the same rules of when does it have which time offset. 时区基本上是地球表面的“条带” * [1] ,其权限遵循何时具有哪个时间偏移的相同规则。 This includes summer time rules. 这包括夏季时间规则。
The time zones change over time for various areas, mostly based on who conquers whom. 时区会随时间变化,各个地区的变化主要取决于谁征服谁。 And one time zone's rules change over time as well. 一个时区的规则也会随着时间而改变 。
There are time offsets. 有时间偏移。 That is not the same as time zones, because a time zone may be eg "Prague", but that has summer time offset and winter time offset. 这与时区不同,因为时区可能是“ Prague”(布拉格),但是具有夏令时和冬令时。
If you get a timestamp with a time zone, the offset may vary, depending on what part of the year it is in. During the leap hour, the timestamp may mean 2 different times, so without additional information, it can't be reliably converted. 如果获得带时区的时间戳,则偏移量可能会有所不同,具体取决于所在的年份。在the年期间,时间戳可能表示2个不同的时间,因此如果没有其他信息,它就不可能可靠转换。
Note: By timestamp I mean "a string that contains a date and/or time, optionally with a time zone and/or time offset." 注意:通过时间戳记,我的意思是“包含日期和/或时间,以及时区和/或时间偏移的字符串。”
Several time zones may share the same time offset for certain periods. 某些时区在某些时段可能共享相同的时间偏移。 For instance, GMT/UTC time zone is the same as "London" time zone when the summer time offset is not in effect. 例如,当夏令时未生效时,GMT / UTC时区与“伦敦”时区相同。
To make it a bit more complicated (but that's not too important for your use case) : 使它更加复杂(但这对您的用例而言并不重要):
2040-12-31 24:00:00
may be a valid date-time.) This needs regular updates of the metadata that systems use to have the date conversions right. (因此2040-12-31 24:00:00
可能是有效的日期时间。)这需要定期更新元数据,系统使用这些元数据来正确地进行日期转换。 Eg on Linux, you get regular updates to the Java packages including these new data. 例如,在Linux上,您会定期更新Java软件包,包括这些新数据。 The updates do not always keep the previous behavior for both historical and future timestamps. 对于历史和将来的时间戳,更新并不总是保留以前的行为。 So it may happen that parsing of the two timestamps around some time zone's change comparing them may give different results when running on different versions of the software. 因此,在不同版本的软件上运行时,围绕某个时区变化进行分析的两个时间戳进行比较可能会产生不同的结果 。 That also applies to comparing between the affected time zone and other time zone. 这也适用于在受影响的时区和其他时区之间进行比较。
Should this cause a bug in your software, consider using some timestamp that does not have such complicated rules, like UNIX timestamp . 如果这会导致软件中的错误,请考虑使用一些没有复杂规则的时间戳 ,例如UNIX timestamp 。
Because of 7, for the future dates, we can't convert dates exactly with certainty. 由于7,对于将来的日期,我们无法完全确定地转换日期。 So, for instance, current parsing of 8524-02-17 12:00:00
may be off a couple of seconds from the future parsing. 因此,例如,当前的8524-02-17 12:00:00
解析可能与将来的解析8524-02-17 12:00:00
几秒钟。
java.util.Date
which had a bit naive approach, assuming that there's just the year, month, day, and time. Java的早期发行版中只有java.util.Date
,它采用了一些天真的方法,假设只有年,月,日和时间。 This quickly did not suffice. 很快这还不够。 java.sql.Date
was introduced, with it's own limitations. 而且,数据库的需求是不同的,因此很早就引入了java.sql.Date
,但它有其自身的局限性。 Calendar
API was introduced. 由于两者都无法很好地涵盖不同的日历和时区,因此引入了Calendar
API。 java.time
如何在Java的java.time
处理它 When you are consuming a timestamp string, you need to know what information it contains. 使用时间戳字符串时,需要知道它包含哪些信息。 This is the crucial point. 这是关键点。 If you don't get this right, you end up with a cryptic exceptions like "Can't create Instant" or "Zone offset missing" or "unknown zone id" etc. 如果您做不到这一点,您将遇到一个神秘的异常,例如“无法创建即时”或“缺少区域偏移”或“未知区域ID”等。
Does it contain the date and the time? 是否包含日期和时间?
Does it have a time offset? 它有时间偏移吗?
A time offset is the +hh:mm
part. 时间偏移是+hh:mm
一部分。 Sometimes, +00:00
may be substituted with Z
as 'Zulu time', UTC
as Universal Time Coordinated, or GMT
as Greenwich Mean Time. 有时,+ +00:00
可以用Z
代替“ Zulu时间”,用UTC
代替世界标准时间,或者用GMT
代替格林威治标准时间。 These also set the time zone. 这些也设置了时区。
For these timestamps, you use OffsetDateTime
. 对于这些时间戳,请使用OffsetDateTime
。
Does it have a time zone? 是否有时区?
For these timestamps, you use ZonedDateTime
. 对于这些时间戳,请使用ZonedDateTime
。
Zone is specified either by 区域由以下任一指定
The list of time zones is compiled by a "TZ database" , backed by ICAAN. 时区列表由ICAAN支持的“ TZ数据库”进行编译。
According to ZoneId
's javadoc, The zone id's can also somehow be specified as Z
and offset. 根据ZoneId
的javadoc,区域ID也可以以某种方式指定为Z
和offset。 I'm not sure how this maps to real zones. 我不确定这是如何映射到真实区域的。 If the timestamp, 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. 如果只有TZ的时间戳落入时间偏移量变化的a时间,则表示该模棱两可,并且解释受ResolverStyle
约束,请参见下文。
If it has neither , then the missing context is assumed or neglected. 如果两者都没有 ,则假定缺少的上下文或忽略该上下文。 And the consumer has to decide. 消费者必须决定。 So it needs to be parsed as LocalDateTime
and converted to OffsetDateTime
by adding the missing info: 因此,需要将其解析为LocalDateTime
并通过添加缺少的信息将其转换为OffsetDateTime
:
Duration
), or when you don't know and it doesn't really matter (eg local bus schedule). 这很有用,例如比较或减去两次(请参见Duration
),或者在您不知道且并不重要的时候(例如,本地公交车时刻表)。 Partial time information 部分时间信息
LocalDate
, LocalTime
, OffsetTime
, MonthDay
, Year
, or YearMonth
out of it. 基于什么样的时间戳包含,你可以采取LocalDate
, LocalTime
, OffsetTime
, MonthDay
, Year
,或YearMonth
出来。 If you have the full information, you can get a java.time.Instant
. 如果您有完整的信息,则可以获取java.time.Instant
。 This is also internally used to convert between OffsetDateTime
and ZonedDateTime
. 这在内部也用于在OffsetDateTime
和ZonedDateTime
之间进行转换。
There is an extensive documentation on DateTimeFormatter
which can both parse a timestamp string and format to string. 有关DateTimeFormatter
的大量文档,可以解析时间戳字符串并可以将其格式化为字符串。
The pre-created DateTimeFormatter
s should cover moreless all standard timestamp formats. 预先创建的DateTimeFormatter
应当涵盖所有标准时间戳格式。 For instance, ISO_INSTANT
can parse 2011-12-03T10:15:30.123457Z
. 例如, ISO_INSTANT
可以解析2011-12-03T10:15:30.123457Z
。
If you have some special format, then you can create your own DateTimeFormatter (which is also a parser). 如果您有某种特殊格式,则可以创建自己的DateTimeFormatter (它也是解析器)。
private static final DateTimeFormatter TIMESTAMP_PARSER = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SX"))
.toFormatter();
I recommend to look at the source code of DateTimeFormatter
and get inspired on how to build one using DateTimeFormatterBuilder
. 我建议看一下DateTimeFormatter
的源代码,并从如何使用DateTimeFormatterBuilder
构建一个源代码中获得启发。 While you're there, also have a look at ResolverStyle
which controls whether the parser is LENIENT, SMART or STRICT for the formats and ambiguous information. 当您在那里时,还可以查看ResolverStyle
,它控制解析器是LENIENT,SMART还是STRICT来获取格式和不明确的信息。
Now, the frequent mistake is to go into the complexity of TemporalAccessor
. 现在,常见的错误是进入TemporalAccessor
的复杂性。 This comes from how the developers were used to work with SimpleDateFormatter.parse(String)
. 这来自于开发人员如何与SimpleDateFormatter.parse(String)
。 Right, DateTimeFormatter.parse("...")
gives you TemporalAccessor
. 正确, DateTimeFormatter.parse("...")
给您TemporalAccessor
。
// No need for this!
TemporalAccessor ta = TIMESTAMP_PARSER.parse("2011-... etc");
But, equiped with the knowledge from the previous section, you can conveniently parse into the type you need: 但是,有了上一节的知识,您可以方便地解析为所需的类型:
OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z", TIMESTAMP_PARSER);
You do not actually need to the DateTimeFormatter
either. 您实际上也不需要DateTimeFormatter
。 The types you want to parse have the parse(String)
methods. 您要解析的类型具有parse(String)
方法。
OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z");
Regarding TemporalAccessor
, you can use it if you have a vague idea of what information there is in the string, and want to decide at runtime. 关于TemporalAccessor
,如果您不清楚字符串中包含哪些信息,并且想在运行时决定,则可以使用它。
I hope I shed some light of understanding onto your soul :) 我希望我对你的灵魂有所了解:)
Note: There's a backport of java.time
to Java 6 and 7: ThreeTen-Backport . 注意: java.time
有一个向Java 6和7的java.time
移植: ThreeTen-Backport 。 For Android it has ThreeTenABP . 对于Android,它具有ThreeTenABP 。
[1] Not just that they are not stripes, but there also some weird extremes. [1]不仅因为它们不是条纹,而且还有一些怪异的极端。 For instance, some neighboring pacific islands have +14:00 and -11:00 time zones. 例如, 某些邻近的太平洋岛屿具有+14:00和-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 :) ) 这就是说,在一个岛上,是5月1日下午3点,而在另一个岛上,则是4月30日,下午12点(如果我算错了:))