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?
Parsing date and time
To create a LocalDateTime object from a string you can use the static LocalDateTime.parse() method. It takes a string and a DateTimeFormatter as parameter. The DateTimeFormatter is used to specify the date/time pattern.
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.
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. For example: Using DateTimeFormatter.ISO_DATE_TIME to format the LocalDateTime instance from above would result in the string "1986-04-08T12:30:00".
The parse() and format() methods are available for all date/time related objects (e.g. LocalDate or 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.
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
Both Sufiyan Ghori's and micha's answer explain very well the question regarding string patterns. However, just in case you are working with ISO 8601, there isn't any need to apply DateTimeFormatter since LocalDateTime is already prepared for it:
Convert a LocalDateTime to a Time Zone ISO 8601 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:
Why does Java have so many ways to parse a date?
There are 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 timestamp.
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
There are time zones.
A time zone is basically a "stripe"*[3] 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 two 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, the GMT/UTC time zone is the same as the "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):
The scientists observe Earth's dynamic, which changes over time; based on that, they add seconds at the end of individual years. (So 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.
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 time zones well, the Calendar API was introduced.
This still did not cover the complexity of the time zones. 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 timestamp to
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", "Zone offset missing", "unknown zone id", etc.
Unable to obtain OffsetDateTime from TemporalAccessor
Unable to obtain ZonedDateTime from TemporalAccessor
Unable to obtain LocalDateTime from TemporalAccessor
Unable to obtain Instant from TemporalAccessor
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
name ("Prague", "Pacific Standard Time", "PST"), or
"zone ID" ("America/Los_Angeles", "Europe/London"), represented by java.time.ZoneId.
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:
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).
Partial time information
Based on what the timestamp contains, you can take 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.
Figure out how to parse it
There is an extensive documentation on DateTimeFormatter which can both parse a timestamp string and format to string.
The pre-created DateTimeFormatters should cover more or less 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.
TemporalAccessor
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, equipped 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.
[3] 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 :) )
Another thing to note with LocalDateTime.parse is that you cannot use it with a custom formatter with only date formatter characters, such as uuuuMMdd. In this case, you should use LocalDate.parse instead. For example:
String s = "20210223";
// ok
LocalDate.parse(s, DateTimeFormatter.ofPattern("uuuuMMdd"));
// java.time.format.DateTimeParseException
LocalDateTime.parse(s, DateTimeFormatter.ofPattern("uuuuMMdd"));
Get the current UTC time in the required format
// Current the UTC time
OffsetDateTime utc = OffsetDateTime.now(ZoneOffset.UTC);
// Get LocalDateTime
LocalDateTime localDateTime = utc.toLocalDateTime();
System.out.println("*************" + localDateTime);
// Formatted UTC time
DateTimeFormatter dTF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
System.out.println(" formats as " + dTF.format(localDateTime));
// Get the UTC time for the 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));
All the answers are good. The Java 8+ versions have these patterns for parsing and formatting time zones: 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).
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)
The universal method looks as below. It works for:
yyyy-MM-dd HH:mm:ss.SSS
yyyy-MM-dd HH:mm:ss.S
yyyy-MM-dd HH:mm:ss
yyyy-MM-dd HH:mm
yyyy-MM-dd HH
yyyy-MM-dd
public static final String DATE_FORMAT_YYYY_MM_DD_HH_MM_SS_SSS = "yyyy-MM-dd HH:mm:ss.SSS";
public LocalDateTime stringToLocalDateTime(String s){
return LocalDateTime.parse(s, DateTimeFormatter.ofPattern(DATE_FORMAT_YYYY_MM_DD_HH_MM_SS_SSS.substring(0, s.length())));
}
I found it wonderful to cover multiple variants of date time formats 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);
There are already many good answers to this question. This answer shows how to use predefined DateTimeFormatters to build a DateTimeFormatter which can parse the given date-time string.
However, formatting the obtained LocalDateTime using this DateTimeFormatter will return a string with time in HH:mm:ss format. To restrict the time string to HH:mm format, we still have to use the pattern uuuu-MM-dd HH:mm as other answers have done.
Demo:
class Main {
public static void main(String[] args) {
DateTimeFormatter dtf = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral(' ')
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.toFormatter(Locale.ENGLISH);
String strDateTime = "2014-04-08 12:30";
LocalDateTime ldt = LocalDateTime.parse(strDateTime, dtf);
System.out.println(ldt);
// However, formatting the obtained LocalDateTime using this DateTimeFormatter
// will return a string with time in HH:mm:ss format. To restrict the time
// string to HH:mm format, we still have to use the pattern, uuuu-MM-dd HH:mm as
// other answers have done.
String strDateTimeFormatted = ldt.format(dtf);
System.out.println(strDateTimeFormatted);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm", Locale.ENGLISH);
strDateTimeFormatted = ldt.format(formatter);
System.out.println(strDateTimeFormatted);
}
}
Output:
2014-04-08T12:30
2014-04-08 12:30:00
2014-04-08 12:30
ONLINE DEMO
Note: Here, you can use y instead of u but I prefer u to y.
Learn more about the modern Date-Time API from Trail: Date Time.
Related
I am trying to use the simpledateformat function but i am continuously getting errors as "Unparseable date:"
Currently the time which is stored in a string testtime=2021-09-14T21:15:09.863Z;//UTC time
I would want to convert this into PST time in the same format using T and Z notation;
Date date1=new SimpleDateFormat("yyyy-MM-dd'T'HH:mm.sss'Z'").parse(testtime);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm.sss'Z", Locale.US);
dateFormat.setTimeZone(TimeZone.getTimeZone("PST"));
System.out.println("PRINTING the TIME in PST"+dateFormat.format(date1.getTime()));
What is missing here,please advise?
Use java.time and you need no formatter
I recommend that you use java.time, the modern Java date and time API, for your date and time work. I am assuming that by PST you mean North American Pacific Standard Time (America/Vancouver or America/Los_Angeles). Other interpretations exist that are just as valid.
String testtime = "2021-09-14T21:15:09.863Z";
Instant instant1 = Instant.parse(testtime);
ZoneId desiredZone = ZoneId.of("America/Los_Angeles");
ZonedDateTime pstDateTime = instant1.atZone(desiredZone);
System.out.println("PRINTING the TIME in PST: " + pstDateTime);
Output:
PRINTING the TIME in PST:
2021-09-14T14:15:09.863-07:00[America/Los_Angeles]
Oops, we didn’t get PST. We got Pacific Daylight Time or PDT. Wanting PST in September, I doubt that it makes any sense. Unless, of course, you meant Philippines Standard Time or Pitcairn Standard Time.
ISO 8601: Your string is in ISO 8601 format. Instant and the other classes of java.time parse and print ISO 8601 format as their default, that is, without us specifying any formatter. So I didn’t. The output from ZonedDateTime isn’t strictly ISO 8601 format. If you wanted that, you may format the date and time using the builtin DateTimeFormatter.ISO_OFFSET_DATE_TIME:
System.out.println("PRINTING the TIME in PST: "
+ pstDateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
PRINTING the TIME in PST: 2021-09-14T14:15:09.863-07:00
What were you missing?
Apart from using the long outdated and troublesome classes:
Never hardcode Z as a literal in your format pattern string. In means UTC so you need to parse and format it as an offset, or you will get incorrect result in most cases.
In particular printing Z after the PST time is wrong. Instead we want offset -08:00 for PST and -07:00 for PDT as in my output above.
As others have pointed out there is a typo in the time part of your format pattern string both times: HH:mm.sss. It should be HH:mm.ss.SSS for two-digit seconds and three-digit milliseconds. This typo caused the exception that you probably got.
Don’t rely on PST or other three letter abbreviations for time zones. As I said, they have multiple interpretation. Use a time zone ID like America/Vancouver, so in the region/city format.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Wikipedia article: ISO 8601
Your format does not match your input, specifically, HH:mm.sss does not match 21:15:09.863.
Change the format to HH:mm:ss.SSS
And then start making use of the newer Date/Time APIs (ie java.time)
Java 8+
String dateInString = "2021-09-14T21:15:09.863Z";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX");
OffsetDateTime odt = OffsetDateTime.parse(dateInString, formatter);
ZoneId utcTZId = ZoneId.of("Etc/UTC");
ZonedDateTime utcDT = odt.atZoneSameInstant(utcTZId);
System.out.println(utcDT.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
ZoneId laTZId = ZoneId.of( "America/Los_Angeles" );
ZonedDateTime laDT = utcDT.withZoneSameInstant(laTZId);
System.out.println(laDT.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
which prints...
2021-09-14T21:15:09.863Z[Etc/UTC]
2021-09-14T14:15:09.863-07:00[America/Los_Angeles]
Java Convert UTC to PDT/PST with Java 8 time library was an interesting read
Not long ago, I provided an answer to a question about how to extract the time zone from a ZonedDateTime parsed from a String.
It worked in general, but there were different outputs of the same code on OP's system an my one, which somehow doesn't let me go anymore.
The related question asked how to get a ZoneId from a ZonedDateTime and I provided a way. Admittedly, it is not the accepted answer, but still seemed worth an upvote from someone.
The special thing about my answer is concerning the 'z' in the pattern used to parse the time String. There is a time zone name in that String representing the zone "Australia/Adelaide" by "... ACST" (Australian Central Standard Time).
When I parse it on my system and print/format the ZonedDateTime using the DateTimeFormatter.ISO_ZONED_DATE_TIME, it prints the time in "America/Manaus" and having extracted the ZoneId, it is still that one from South America. OP stated in a comment below my answer, that on his system, at least one of the output lines shows the desired/correct ZoneId.
How is that possible? Does the system default locale have any influence on parsing the 'z' in datetime Strings?
This is the code from my answer in the question plus an output of my ZoneId.systemDefault():
public static void main(String args[]) throws Exception {
String time = "2 Jun 2019 03:51:17 PM ACST";
String pattern = "d MMM yyyy hh:mm:ss a z"; // z detects the time zone (ACST here)
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
// parse a time object using the formatter and the time String
ZonedDateTime zdt = ZonedDateTime.parse(time, formatter);
// print it using a standard formatter
System.out.println(zdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
// extract the zone id
ZoneId zoneId = zdt.getZone();
ZoneId sysDefault = ZoneId.systemDefault();
// print the zone id
System.out.println("Time zone of parsed String is " + zoneId);
System.out.println("System default time zone is " + sysDefault);
// retrieve the instant seconds
Instant instant = zdt.toInstant();
// print the epoch seconds of another time zone
System.out.println("Epoch seconds in Australia/Adelaide are "
+ instant.atZone(ZoneId.of("Australia/Adelaide")).toEpochSecond());
}
and its output is this:
2019-06-02T15:51:17-04:00[America/Manaus]
Time zone of parsed String is America/Manaus
System default time zone is Europe/Berlin
Epoch seconds in Australia/Adelaide are 1559505077
Can anyone point me to a mistake I have made or confirm and explain the influence of different system default ZoneIds on parsing a String to a ZonedDateTime?
I too have experienced ambiguous time zone abbreviations being parsed differently on different JVMs (also when providing a locale, so that is not the only issue). I don't think the exact behaviour is documented. In some cases the JVMs default time zone was chosen, I don't know if it will always be when it matches, though.
You can control the choice of time zone in ambiguous cases through the overloaded DateTimeFormatterBuilder.appendZoneText(TextStyle, Set<ZoneId>) method.
An example where locale makes a difference: Europe/Berlin and many other European time zones will be formatted into Central European Time or CET in many locales. In a German locale they instead become Mitteleuropäische Zeit or MET.
The Answer by Ole V.V. is correct.
ISO 8601
Furthermore, strings such as "2 Jun 2019 03:51:17 PM ACST" should not be parsed. Such formats should never be used to exchange date-time values. Such strings should be used only for presentation to the human user, not for data-exchange.
Would you try the exchange the monetary amount of USD 23.67 as twenty three dollars and sixty-seven cents in United States dollars or as vingt-trois dollars et soixante-sept cents en dollars des États-Unis?
To exchange date-time values via text, always use the standard ISO 8601 formats. The sensible formats are designed to be unambiguous, easy to parse by machine, and easy to read by humans across cultures.
If using a time zone, use the ZonedDateTime::toString method to generate text. This method wisely extends the ISO 8601 format to append the name of the time zone in square brackets.
ZoneId z = ZoneId.of( "Australia/Adelaide" ) ;
String output = ZonedDateTime.now( z ).toString() ;
2020-02-10T06:30:57.756491+10:30[Australia/Adelaide]
Run that code live at IdeOne.com.
Parsing.
ZonedDateTime zdt = ZonedDateTime.parse( output ) ;
Even better, use UTC values when the time zone is not directly relevant. To capture and communicate the current moment in UTC, use Instant.
Again, call toString & parse to generate and parse text in standard ISO 8601 format. The Z on the end means UTC, an offset of zero hours-minutes-seconds, and is pronounced “Zulu”.
String output = Instant.now().toString() ;
2020-02-09T20:07:42.718473Z
i wrote an util function to convert a string time value of format 2018-11-26T15:12:03.000-0800 to localdatetime of format "M/dd/yy HH:mm:ss a z"
string of format 2018-11-26T15:12:03.000-0800 to java.time.localdatetime of format "M/dd/yy HH:mm:ss a z" conversion throwing exception.
public static LocalDateTime convertStringToTime(String time){
String pattern = "M/dd/yy HH:mm z";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
ZonedDateTime zonedDateTime = ZonedDateTime.parse(time,formatter);
return zonedDateTime.toLocalDateTime();
}
which gives me the below exception
java.time.format.DateTimeParseException: Text '2018-11-26T12:45:23.000-0800' could not be parsed at index 4
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1947)
You say that you want: a LocalDateTime of format M/dd/yy HH:mm:ss a z. This is impossible for three reasons:
A LocalDateTime cannot have a format. Its toString method always returns a string like 2018-11-26T15:12:03 (ISO 8601 format), there is no way we can change that. You also shouldn’t want a LocalDateTime with a specific format; I include a link at the bottom explaining why not.
I assume that by z in your format you mean time zone abbreviation like PDT for Pacific Daylight Time. A LocalDateTime neither has UTC offset not time zone, so this doesn’t make sense.
Your input time string doesn’t hold any time zone either, only an offset from UTC. So to print a time zone abbreviation, you will first need to choose a time zone.
Instead I suggest:
ZoneId zone = ZoneId.of("America/Whitehorse");
DateTimeFormatter inputFormatter
= DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSXX");
DateTimeFormatter desiredFormatter
= DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.LONG)
.withLocale(Locale.US);
String time = "2018-11-26T15:12:03.000-0800";
OffsetDateTime dateTime = OffsetDateTime.parse(time, inputFormatter);
String formattedDateTime = dateTime.atZoneSameInstant(zone)
.format(desiredFormatter);
System.out.println("Converted format: " + formattedDateTime);
Output is:
Converted format: 11/26/18, 3:12:03 PM PST
To convert date and time from a string in one format to a string in another format you generally need two DateTimeFormatters: one specifying the format of the string you’ve got and one specifying the format that you want.
Rather than building your own formatter from a format pattern string, rely on built-in formats when you can. In our case I specify FormatStyle.SHORT for the date (giving two-digit-year) and FormatStyle.LONG for the time, giving us the time zone abbreviation.
The idea of relying on built-in formats can be taken one step further. The string you’ve got is in ISO 8601 format, so we just need to piece two pieces together:
DateTimeFormatter inputFormatter = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
.appendOffset("+HHmm", "Z")
.toFormatter();
It’s longer, but it’s less error-prone.
Links
Wikipedia article: ISO 8601.
My answer to want current date and time in “dd/MM/yyyy HH:mm:ss.SS” format explaining why you don’t want a date-time object with a format.
Code sample:
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
System.out.println(dateFormat.getTimeZone());
System.out.println(dateFormat.parse(time));
// dateFormat.setTimeZone(TimeZone.getTimeZone("Asia/Kolkata"));
I don't want to use the commented section.
Zone should be set based on IST that I am giving in the input string:
String time ="2018-04-06 16:13:00 IST";
Current machine zone is: America/New_York. How should I get zone changes to IST based on z?
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
String time ="2018-04-06 18:40:00 IST";
dateFormat.setTimeZone(TimeZone.getTimeZone("Asia/Kolkata"));
Above is running correctly but I don't want to set zone explicitly. It should be choosen based on IST I am giving in input time string.
"I don't want to set zone explicitly"
Sorry to disappoint you, but that's not possible with SimpleDateFormat. Timezone abbreviations like IST are ambiguous - as already said in the comments, IST is used in many places (AFAIK, in India, Ireland and Israel).
Some of those abbreviations might work sometimes, in specific cases, but usually in arbitrary and undocumented ways, and you can't really rely on that. Quoting the javadoc:
For compatibility with JDK 1.1.x, some other three-letter time zone IDs (such as "PST", "CTT", "AST") are also supported. However, their use is deprecated 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.
Due to the ambiguous and non-standard characteristics of timezones abbreviations, the only way to solve it with SimpleDateFormat is to set a specific timezone on it.
"It should be set to based on Z"
I'm not really sure what this means, but anyway...
Z is the UTC designator. But if the input contains a timezone short-name such as IST, well, it means that it's not in UTC, so you can't parse it as if it was in UTC.
If you want to output the date with Z, then you need another formatter set to UTC:
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
String time = "2018-04-06 18:40:00 IST";
dateFormat.setTimeZone(TimeZone.getTimeZone("Asia/Kolkata"));
// parse the input
Date date = dateFormat.parse(time);
// output format, use UTC
SimpleDateFormat outputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssX");
outputFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(outputFormat.format(date)); // 2018-04-06 13:10:00Z
Perhaps if you specify exactly the output you're getting (with actual values, some examples of outputs) and what's the expected output, we can help you more.
DateTimeFormatter formatter
= DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss z", Locale.ENGLISH);
String time ="2018-04-06 16:13:00 IST";
ZonedDateTime dateTime = ZonedDateTime.parse(time, formatter);
System.out.println(dateTime.getZone());
On my Java 8 this printed
Asia/Jerusalem
So apparently IST was interpreted as Israel Standard Time. On other computers with other settings you will instead get for instance Europe/Dublin for Irish Summer Time or Asia/Kolkata for India Standard Time. In any case the time zone comes from the abbreviation matching the pattern letter (lowercase) z in the format pattern string, which I suppose was what you meant(?)
If you want to control the choice of time zone in the all too frequent case of ambiguity, you may build your formatter in this way (idea stolen from this answer):
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern("uuuu-MM-dd HH:mm:ss ")
.appendZoneText(TextStyle.SHORT,
Collections.singleton(ZoneId.of("Asia/Kolkata")))
.toFormatter(Locale.ENGLISH);
Now the output is
Asia/Kolkata
I am using and recommending java.time over the long outdated and notoriously troublesome SimpleDateFormat class.
Link: Oracle tutorial: Date Time explaining how to use java.time.
I am trying to get output for a date in the format (String):
20170801 123030 America/Los_Angeles
But using this code:
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd hhmmss Z", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
System.out.println(sdf.format(new java.util.Date()));
I am getting the output as (notice the zone part):
20174904 024908 -0700
Any idea how to fix it? I need to print "America/Los_Angeles" instead of "-0700".
Looks like the SimpleDateFormat doesn't support what you want.
Here are possible formats:
z Time zone General time zone Pacific Standard Time; PST; GMT-08:00
Z Time zone RFC 822 time zone -0800
X Time zone ISO 8601 time zone -08; -0800; -08:00
You can just add "America/Los_Angeles" after a formatted date:
TimeZone timeZone = TimeZone.getTimeZone("America/Los_Angeles");
DateFormat sdf = new SimpleDateFormat("yyyyMMdd hhmmss", Locale.US);
sdf.setTimeZone(timeZone);
System.out.println(sdf.format(new Date()) + " " + timeZone.getID());
DateFormat sdfWithTimeZone = new SimpleDateFormat("yyyyMMdd hhmmss zzzz", Locale.US);
sdfWithTimeZone.setTimeZone(timeZone);
System.out.println(sdfWithTimeZone.format(new Date()));
Output:
20171004 031611 America/Los_Angeles
20171004 031611 Pacific Daylight Time
If you can use Java 8, you can use DateTimeFormatter and its symbol V to display the Zone ID:
Instant now = Instant.now();
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMdd hhmmss VV")
.withLocale(Locale.US)
.withZone(ZoneId.of("America/Los_Angeles"));
System.out.println(fmt.format(now));
Prints:
20171004 032552 America/Los_Angeles
Note that SimpleDateFormat doesn't support this flag as mentioned in Alexandr's answer.
If you have to start from java.util.Date but can use Java 8 still, you can convert it to an Instant first:
Instant now = new java.util.Date().toInstant();
...
As pointed by #Alexandr's answer, there's no built-in pattern in SimpleDateFormat to print the timezone ID. But there's a way to overwrite this.
First you create a formatter with the z pattern, that corresponds to the timezone short name. I'm not sure if the locale matters for this (I know it affects the long names, not sure about the short names, but anyway I'm keeping it).
Then I get the java.text.DateFormatSymbols from the formatter and overwrite the strings that correspond to the short names:
// use "z" (short timezone name)
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd hhmmss z", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
// get the java.text.DateFormatSymbols
DateFormatSymbols symbols = sdf.getDateFormatSymbols();
// get the zones names
String[][] zones = symbols.getZoneStrings();
// overwrite zone short names
for (int i = 0; i < zones.length; i++) {
String zoneId = zones[i][0];
if ("America/Los_Angeles".equals(zoneId)) {
zones[i][2] = zoneId; // short name for standard time
zones[i][4] = zoneId; // short name for Daylight Saving Time
}
}
// update the symbols in the formatter
symbols.setZoneStrings(zones);
sdf.setDateFormatSymbols(symbols);
System.out.println(sdf.format(new java.util.Date()));
This will print:
20171005 045317 America/Los_Angeles
Note that I changed the zone name only for America/Los_Angeles timezone. You can modify the if to change whatever zones you want - or just remove the if to change it for all zones.
Another detail is that you're using hh for the hours. According to javadoc, this is the Hour in am/pm field (values from 1 to 12). Without the AM/PM designator (pattern a), the output might be ambiguous. You can change this to HH (Hour in day field, values from 0 to 23) or to kk (values from 1 to 24) if you want.
Java new Date/Time API
The old classes (Date, Calendar and SimpleDateFormat) have lots of problems and design issues, and they're being replaced by the new APIs.
If you're using Java 8, consider using the new java.time API. It's easier, less bugged and less error-prone than the old APIs.
If you're using Java 6 or 7, you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, you'll also need the ThreeTenABP (more on how to use it here).
The code below works for both.
The only difference is the package names (in Java 8 is java.time and in ThreeTen Backport (or Android's ThreeTenABP) is org.threeten.bp), but the classes and methods names are the same.
Actually, this new API is so straighforward that the code will be exactly the same as the other answers posted by #Juan and #Jens. But there's a subtle difference between those.
Let's suppose I have 2 different ZonedDateTime objects: one represents the current date/time in America/Los_Angeles timezone, and another one represents the same current date/time in Asia/Tokyo timezone:
// current date/time
Instant now = Instant.now();
// get the same instant in different timezones
ZonedDateTime nowLA = now.atZone(ZoneId.of("America/Los_Angeles"));
ZonedDateTime nowTokyo = now.atZone(ZoneId.of("Asia/Tokyo"));
System.out.println(nowLA); // 2017-10-05T05:04:31.253-07:00[America/Los_Angeles]
System.out.println(nowTokyo); // 2017-10-05T21:04:31.253+09:00[Asia/Tokyo]
These dates are:
2017-10-05T05:04:31.253-07:00[America/Los_Angeles]
2017-10-05T21:04:31.253+09:00[Asia/Tokyo]
Now let's see the difference. #Jens's answer sets the timezone in the formatter. This means that all dates will be converted to that specific timezone when formatting (I've just modified the code a little bit, to set the locale directly - instead of using withLocale - but the resulting formatter is equivalent):
// set the timezone in the formatter
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMdd hhmmss VV", Locale.US)
// use Los Angeles timezone
.withZone(ZoneId.of("America/Los_Angeles"));
// it converts all dates to Los Angeles timezone
System.out.println(nowLA.format(fmt)); // 20171005 050431 America/Los_Angeles
System.out.println(nowTokyo.format(fmt)); // 20171005 050431 America/Los_Angeles
As the timezone is set in the formatter, both dates are converted to this timezone (including the date and time values):
20171005 050431 America/Los_Angeles
20171005 050431 America/Los_Angeles
While in #Juan's answer, the formatter doesn't have a timezone set, which means it'll preserve the timezone used in the ZonedDateTime objects:
// formatter without a timezone set
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMdd hhmmss VV", Locale.US);
// it keeps the timezone set in the date
System.out.println(nowLA.format(fmt)); // 20171005 050431 America/Los_Angeles
System.out.println(nowTokyo.format(fmt)); // 20171005 090431 Asia/Tokyo
Now the timezone is preserved (and also the date and time values):
20171005 050431 America/Los_Angeles
20171005 090431 Asia/Tokyo
It's a subtle difference, and you must choose the approach that works best for your case. Note that the same issue about hh versus HH also applies here: the variable nowTokyo holds the value equivalent to 21:04:31 (9:04:31 PM in Tokyo), but it's formatted as 090431 - without the AM/PM designator, this time is ambiguous, so IMO the pattern should use HH (so the output would be 210431). But it's up to you to decide.
In the javadoc you can see details about all available patterns.