Java 8 added a new java.time API for working with dates and times (JSR 310).
I have date and time as string (e.g. "2014-04-08 12:30"
). How can I obtain a LocalDateTime
instance from the given string?
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?
I found the it wonderful to cover multiple variants of date time format like this:
final DateTimeFormatterBuilder dtfb = new DateTimeFormatterBuilder();
dtfb.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S"))
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0);
GET CURRENT UTC TIME IN REQUIRED FORMAT
// Current UTC time
OffsetDateTime utc = OffsetDateTime.now(ZoneOffset.UTC);
// GET LocalDateTime
LocalDateTime localDateTime = utc.toLocalDateTime();
System.out.println("*************" + localDateTime);
// formated UTC time
DateTimeFormatter dTF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
System.out.println(" formats as " + dTF.format(localDateTime));
//GET UTC time for current date
Date now= new Date();
LocalDateTime utcDateTimeForCurrentDateTime = Instant.ofEpochMilli(now.getTime()).atZone(ZoneId.of("UTC")).toLocalDateTime();
DateTimeFormatter dTF2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
System.out.println(" formats as " + dTF2.format(utcDateTimeForCurrentDateTime));
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.
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 instance, in the following 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 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; 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)
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
Let's take two questions, example string "2014-04-08 12:30"
How can I obtain a LocalDateTime instance from the given string?
import java.time.format.DateTimeFormatter
import java.time.LocalDateTime
final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
// Parsing or conversion
final LocalDateTime dt = LocalDateTime.parse("2014-04-08 12:30", formatter)
dt
should allow you to all date-time related operations
How can I then convert the LocalDateTime instance back to a string with the same format?
final String date = dt.format(formatter)
All the answers are good. The java8+ have these patterns for parsing and formatting timezone: V
, z
, O
, X
, x
, Z
.
Here's they are, for parsing, according to rules from the documentation :
Symbol Meaning Presentation Examples
------ ------- ------------ -------
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;
But how about formatting?
Here's a sample for a date (assuming ZonedDateTime
) that show these patters behavior for different formatting patters:
// The helper function:
static void printInPattern(ZonedDateTime dt, String pattern) {
System.out.println(pattern + ": " + dt.format(DateTimeFormatter.ofPattern(pattern)));
}
// The date:
String strDate = "2020-11-03 16:40:44 America/Los_Angeles";
DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss zzzz");
ZonedDateTime dt = ZonedDateTime.parse(strDate, format);
// 2020-11-03T16:40:44-08:00[America/Los_Angeles]
// Rules:
// printInPattern(dt, "V"); // exception!
printInPattern(dt, "VV"); // America/Los_Angeles
// printInPattern(dt, "VVV"); // exception!
// printInPattern(dt, "VVVV"); // exception!
printInPattern(dt, "z"); // PST
printInPattern(dt, "zz"); // PST
printInPattern(dt, "zzz"); // PST
printInPattern(dt, "zzzz"); // Pacific Standard Time
printInPattern(dt, "O"); // GMT-8
// printInPattern(dt, "OO"); // exception!
// printInPattern(dt, "OO0"); // exception!
printInPattern(dt, "OOOO"); // GMT-08:00
printInPattern(dt, "X"); // -08
printInPattern(dt, "XX"); // -0800
printInPattern(dt, "XXX"); // -08:00
printInPattern(dt, "XXXX"); // -0800
printInPattern(dt, "XXXXX"); // -08:00
printInPattern(dt, "x"); // -08
printInPattern(dt, "xx"); // -0800
printInPattern(dt, "xxx"); // -08:00
printInPattern(dt, "xxxx"); // -0800
printInPattern(dt, "xxxxx"); // -08:00
printInPattern(dt, "Z"); // -0800
printInPattern(dt, "ZZ"); // -0800
printInPattern(dt, "ZZZ"); // -0800
printInPattern(dt, "ZZZZ"); // GMT-08:00
printInPattern(dt, "ZZZZZ"); // -08:00
In the case of positive offset the +
sign character is used everywhere(where there is -
now) and never omitted.
This well works for new java.time
types. If you're about to use these for java.util.Date
or java.util.Calendar
- not all going to work as those types are broken(and so marked as deprecated, please don't use them)
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:
Convert LocalDateTime to Time Zone ISO8601 String
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
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 has been tackling this in several iterations. The latest one, java.time
and java.time.chrono
, covers almost all needs (except Time Dilation :) ).
However, that complexity brings a lot of confusion.
The key to understand date parsing is:
LocalDateTime
, ZonedDateTime
et al. so complicatedThere 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. 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 e.g. "Prague", but that has summer time offset and winter time offset.
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.
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.
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. E.g. on Linux, you get regular updates to the Java packages including these new data.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.
Because of 7, for the future dates, we can't convert dates exactly with certainty. So, for instance, current parsing of 8524-02-17 12:00:00
may be off a couple of seconds from the future parsing.
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.java.sql.Date
was introduced, with it's own limitations.Calendar
API was introduced.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.
Does it contain the date and the time?
Does it have a time offset?
A time offset is the +hh:mm
part. Sometimes, +00:00
may be substituted with Z
as 'Zulu time', UTC
as Universal Time Coordinated, or GMT
as Greenwich Mean Time. These also set the time zone.
For these timestamps, you use OffsetDateTime
.
Does it have a time zone?
For these timestamps, you use ZonedDateTime
.
Zone is specified either by
The list of time zones is compiled by a "TZ database", backed by ICAAN.
According to ZoneId
's javadoc, The zone id's can also somehow be specified as Z
and 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.
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:
Duration
), or when you don't know and it doesn't really matter (e.g. local bus schedule).Partial time information
LocalDate
, LocalTime
, OffsetTime
, MonthDay
, Year
, or YearMonth
out of it.If you have the full information, you can get a java.time.Instant
. This is also internally used to convert between OffsetDateTime
and ZonedDateTime
.
There is an extensive documentation on DateTimeFormatter
which can both parse a timestamp string and format to string.
The pre-created DateTimeFormatter
s should cover moreless all standard timestamp formats. For instance, ISO_INSTANT
can parse 2011-12-03T10:15:30.123457Z
.
If you have some special format, then you can create your own DateTimeFormatter (which is also a parser).
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
. 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.
Now, the frequent mistake is to go into the complexity of TemporalAccessor
. This comes from how the developers were used to work with SimpleDateFormatter.parse(String)
. Right, DateTimeFormatter.parse("...")
gives you 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. The types you want to parse have the parse(String)
methods.
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.
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. For Android it has ThreeTenABP.
[1] Not just that they are not stripes, but there also some weird extremes. For instance, some neighboring pacific islands have +14:00 and -11:00 time zones. 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 :) )
Source: Stackoverflow.com