For a REST web service, I need to return dates (no time) with a time zone.
Apparently there is no such thing as a ZonedDate in Java (only LocalDate and ZonedDateTime), so I'm using ZonedDateTime as a fallback.
When converting those dates to JSON, I use DateTimeFormatter.ISO_OFFSET_DATE to format the date, which works really well:
DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE;
ZonedDateTime dateTime = ZonedDateTime.now();
String formatted = dateTime.format(formatter);
2018-04-19+02:00
However, attempting to parse back such a date with...
ZonedDateTime parsed = ZonedDateTime.parse(formatted, formatter);
... results in an Exception:
java.time.format.DateTimeParseException: Text '2018-04-19+02:00' could not be parsed: Unable to obtain ZonedDateTime from TemporalAccessor: {OffsetSeconds=7200},ISO resolved to 2018-04-19 of type java.time.format.Parsed
I also tried ISO_DATE and ran into the same problem.
How can I parse such a zoned date back?
Or is there any other type (within the Java Time API) I'm supposed to use for zoned dates?
The problem is that ZonedDateTime needs all the date and time fields to be built (year, month, day, hour, minute, second, nanosecond), but the formatter ISO_OFFSET_DATE produces a string without the time part.
When parsing it back, there are no time-related fields (hours, minutes, seconds) and you get a DateTimeParseException.
One alternative to parse it is to use a DateTimeFormatterBuilder and define default values for the time fields. As you used atStartOfDay in your answer, I'm assuming you want midnight, so you can do the following:
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
// date and offset
.append(DateTimeFormatter.ISO_OFFSET_DATE)
// default values for hour and minute
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
.toFormatter();
ZonedDateTime parsed = ZonedDateTime.parse("2018-04-19+02:00", fmt); // 2018-04-19T00:00+02:00
Your solution also works fine, but the only problem is that you're parsing the input twice (each call to formatter.parse will parse the input again). A better alternative is to use the parse method without a temporal query (parse only once), and then use the parsed object to get the information you need.
DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE;
// parse input
TemporalAccessor parsed = formatter.parse("2018-04-19+02:00");
// get data from the parsed object
LocalDate date = LocalDate.from(parsed);
ZoneId zone = ZoneId.from(parsed);
ZonedDateTime restored = date.atStartOfDay(zone); // 2018-04-19T00:00+02:00
With this solution, the input is parsed only once.
tl;dr
Use a time zone (continent/region) rather than a mere offset-from-UTC (hours-minutes-seconds). For any particular zone, the offset is likely to change over time.
Combine the two to determine a moment.
LocalDate.parse(
"2018-04-19"
)
.atStartOfDay(
ZoneId.of( "Europe/Zurich" )
) // Returns a `ZonedDateTime` object.
2018-04-19T00:00+02:00[Europe/Zurich]
From your REST service, either:
Return the date and zone separately (either with a delimiter or as XML/JSON), or,
Return the start of day as that is likely the intended outcome of a date with a time zone.
Separate your text inputs
The solution in the Answer by Walser is effectively treating the string input as a pair of string inputs. First the date-only part is extracted and parsed. Second, the offset-from-UTC part is extracted and parsed. So, the input is parsed twice, each time ignoring the opposite half of the string.
I suggest you make this practice explicit. Track the date as one piece of text, track the offset (or, better, a time zone) as another piece of text. As the code in that other Answer demonstrates, there is no real meaning to a date with zone until you take the next step of determining an actual moment such as the start of day.
String inputDate = "2018-04-19" ;
LocalDate ld = LocalDate.parse( inputDate ) ;
String inputOffset = "+02:00" ;
ZoneOffset offset = ZoneOffset.of( inputOffset) ;
OffsetTime ot = OffsetTime.of( LocalTime.MIN , offset ) ;
OffsetDateTime odt = ld.atTime( ot ) ; // Use `OffsetDateTime` & `ZoneOffset` when given a offset-from-UTC. Use `ZonedDateTime` and `ZoneId` when given a time zone rather than a mere offset.
odt.toString(): 2018-04-19T00:00+02:00
As you can see, the code is simple, and your intent is obvious.
And no need to bother with any DateTimeFormatter object nor formatting patterns. Those inputs conform with ISO 8601 standard formats. The java.time classes use those standard formats by default when parsing/generating strings.
Offset versus Zone
As for applying the date and offset to get a moment, you are conflating a offset-from-UTC with a time zone. An offset is simply a number of hours, minutes, and seconds. No more, no less. In contrast, a time zone is a history of the past, present, and future changes in offset used by the people of a particular region.
In other words, the +02:00 happens to be used by many time zones on many dates. But in a particular zone, such as Europe/Zurich, other offsets may be used on other dates. For example, adopting the silliness of Daylight Saving Time (DST) means a zone will be spending half the year with one offset and the other half with a different offset.
Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).
ZoneId z = ZoneId.of( "Europe/Zurich" ) ;
ZonedDateTime zdt = ld.atStartOfDay( z ) ;
zdt.toString(): 2018-04-19T00:00+02:00[Europe/Zurich]
So I suggest you track two strings of input:
Date-only (LocalDate): YYYY-MM-DD such as 2018-04-19
Proper time zone name (ZoneId): continent/region such as Europe/Zurich
Combine.
ZonedDateTime zdt =
LocalDate.parse( inputDate )
.atStartOfDay( ZoneId.of( inputZone ) )
;
Note: The ZonedDateTime::toString method generates a String in a format that wisely extends the standard ISO 8601 format by appending the name of the time zone in square brackets. This rectifies a huge oversight made by the otherwise well-designed standard. But you can only return such a string by your REST service if you know your clients can consume it.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, Java SE 10, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
I found the solution (using TemporalQueries):
parse the date and zone separately, and restore the zoned date using that information:
LocalDate date = formatter.parse(formatted, TemporalQueries.localDate());
ZoneId zone = formatter.parse(formatted, TemporalQueries.zone());
ZonedDateTime restored = date.atStartOfDay(zone);
Related
I have received a string in format "yyyy-MM-dd'T'HH:mm:ssXXX"
e.g. "2020-06-01T11:04:02+02:00"
I want to convert it into "yyyy-MM-dd'T'HH:mm:ss:SSSZ"
e.g "2020-06-01T11:04:02.000+0200"
I don't know the time zone actually. It should take from that last part of string as it is.
I have tried but it is taking my local time and time zone when I convert string to date(i.e IST).
SimpleDateFormat sd1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
//sd1.setTimeZone(TimeZone.getTimeZone("PST"));
Date dt = sd1.parse("2020-06-01T11:04:02+02:00");
SimpleDateFormat sd2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss:SSSZ");
System.out.println(sd2.format(dt));
Output:
2020-06-01T14:34:02:000+0530
Only date is right, time and timezone has changed.
I know I am doing it wrong, it will be really helpful if someone can tell me how can I do this.
Thanks for the help.
OffsetDateTime
You said:
I have received a string in format "yyyy-MM-dd'T'HH:mm:ssXXX"
e.g. "2020-06-01T11:04:02+02:00"
No need to define a formatting pattern. Your input complies with the ISO 8601 standard.
These standard formats are used by default in the java.time classes when parsing/generating strings.
Your input should be parsed as a OffsetDateTime.
String input = "2020-06-01T11:04:02+02:00" ;
OffsetDateTime odt = OffsetDateTime.parse( input ) ;
odt.toString(): 2020-06-01T11:04:02+02:00
Offset-from-UTC versus time zone
You said:
I don't know the time zone actually.
That +02:00 on the end is not a time zone. That text represents a mere offset-from-UTC. An offset is just a number of hours-minutes-seconds, positive or negative. A time zone is much more. A time zone is a history of the past, present, and future changes to the offset used by the people of a particular region. A time zone has a name in the format of Continent/Region, such as Europe/Brussels or Africa/Cairo.
You can adjust from a mere offset to a specific time zone. Apply a ZoneId to get a ZonedDateTime.
ZoneId z = ZoneId.of( "Asia/Kolkata" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant( z ) ;
zdt.toString(): 2020-06-01T14:34:02+05:30[Asia/Kolkata]
You said:
It should take from that last part of string as it is.
I am not sure what you meant by that. If you parse your input as an OffsetDateTime, that object knows its offset, accessible as a ZoneOffset.
ZoneOffset offset = odt.getOffset() ;
See the code shown in this Answer run live at IdeOne.com.
offset.toString(): +02:00
Formatting strings
You said:
I want to convert it into "yyyy-MM-dd'T'HH:mm:ss:SSSZ"
e.g "2020-06-01T11:04:02.000+0200"
Not sure what you mean here. Do you mean to force the display of milliseconds even if the value is zero? Firstly, you should know that java.time objects have a resolution of nanoseconds for up to nine decimal digits, much finer that the milliseconds shown in 3 digits of a decimal fraction. Secondly, forcing display of fractional second has been covered on Stack Overflow, such as here. Always search Stack Overflow before posting.
Or do you mean displaying the offset without a COLON character as a delimiter between minutes and seconds?
I advise against this. While dropping the COLON is technically allowed by the ISO 8601 standard, I have seen more than one software library or system fail to handle an offset without that delimiter. Ditto for using an offset of hours without the minutes. I advise always using the hours, the minutes, and the delimiter.
If you insist, use DateTimeFormatter with a formatting pattern. Study the Javadoc, keeping mind that the formatting codes are (a) case-sensitive, and (b) sensitive to repeating the character 0, 1, or more times. Here we use xx to get the hours and minutes of an offset without the COLON character delimiting. (Again, I do not recommend that format.)
Code shown in that same IdeOne.com page.
DateTimeFormatter f = DateTimeFormatter.ofPattern( "uuuu-MM-dd'T'HH:mm:ss.SSSxx" ) ;
String output = odt.format( f ) ;
output: 2020-06-01T11:04:02.000+0200
Date::toString injects time zone
You said:
I have tried but it is taking my local time and time zone when I convert string to date(i.e IST).
The java.util.Date::toString method tells a lie. While well-intentioned, that method unfortunately applies the JVM’s current default time zone to the Date value as it generates the text. The Date class actually represents a moment in UTC. This is one of many reasons to never use Date. That class nowadays is replaced by java.time.Instant.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes. Hibernate 5 & JPA 2.2 support java.time.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, Java SE 10, Java SE 11, and later - Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Most of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
The first suggestion I would make to you is to switch from using the Date object to LocalDateTime (java 8+)
Using the new API would work in this way
String YOUR_DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ssXXX";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(YOUR_DATE_TIME_PATTERN);
LocalDateTime dateTime = LocalDateTime.parse(input_date, formatter);
//Then you can set your timezone in this way - remember to replace the values with the proper timezone you want.
ZonedDateTime zonedUTC = dateTime.atZone(ZoneId.of("UTC"));
ZonedDateTime zonedIST = zonedUTC.withZoneSameInstant(ZoneId.of("Asia/Kolkata"));
let me know if that works for you
To elaborate the comment and picking up on #Daniel Vilas-Boas answer, you should go for Java8 and I think what you want is something like:
public static void main(String[] args) {
String YOUR_DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ssXXX";
String YOUR_NEW_DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss:SSSZ";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(YOUR_DATE_TIME_PATTERN);
DateTimeFormatter newFormatter = DateTimeFormatter.ofPattern(YOUR_NEW_DATE_TIME_PATTERN);
ZonedDateTime zonedDateTime = ZonedDateTime.parse("2020-06-01T11:04:02+02:00", formatter);
ZoneId from = ZoneId.from(zonedDateTime);
System.out.println(from);
ZonedDateTime zonedIST = zonedDateTime.withZoneSameInstant(ZoneId.of("Asia/Kolkata"));
System.out.println(zonedDateTime.format(newFormatter));
System.out.println(zonedIST.format(newFormatter));
}
The prints should print:
2020-06-01T11:04:02:000+0000
2020-06-01T16:34:02:000+0530
EDIT
Included ZoneId to allow handling different timezones.
I am very new to OffsetDateTime usage and I am trying to compare OffsetDateTime strings with OffsetDateTime.now() in java this way,
import java.time.OffsetDateTime;
public class OffsetDateTimeDemo {
public static void main(String[] args) {
OffsetDateTime one = OffsetDateTime.parse("2017-02-03T12:30:30+01:00");
System.out.println("First ::" + OffsetDateTime.now().compareTo(one));
OffsetDateTime date1 = OffsetDateTime.parse("2019-02-14T00:00:00");
System.out.println("Second ::" + OffsetDateTime.now().compareTo(date1));
OffsetDateTime date3 = OffsetDateTime.parse("Mon Jun 18 00:00:00 IST 2012");
System.out.println(" Third :: " +OffsetDateTime.now().compareTo(date3));
}
}
But I am getting java.time.format.DateTimeParseException in all the 3 cases.
However if i compare 2 OffsetDateTime Strings with CompareTo method its working fine.
Can someone shed some light to me in this regard and kindly guide me through my mistake.
Thanks in Advance.
Your compareTo coding is a distraction. Your exception is about parsing the string inputs into objects.
Another problem: You are using wrong classes on the 2nd and 3rd inputs.
Another problem: You are relying implicitly on your JVM’s current default time zone when calling now(). Poor practice as any programmer reading will not know if you intended the default or if you were unaware of the issue as are so many programmers. Furthermore, the current default can be changed at any moment during runtime by any code in any thread of any app within the JVM. So better to always specify explicitly your desired/expected zone or offset.
OffsetDateTime.now(
ZoneOffset.UTC
)
Or better yet, use a ZonedDateTime to capture more information than a OffsetDateTime.
ZonedDateTime.now(
ZoneId.of( "Pacific/Auckland" )
)
First: OffsetDateTime works
Your first string input is proper, and parses successfully.
OffsetDateTime.parse( "2017-02-03T12:30:30+01:00" )
Full line of code:
OffsetDateTime odt = OffsetDateTime.parse( "2017-02-03T12:30:30+01:00" ) ;
See this code run live at IdeOne.com.
odt.toString(): 2017-02-03T12:30:30+01:00
To compare, extract an Instant. Doing so effectively adjusts your moment from some offset to an offset of zero, or UTC itself. An Instant is always in UTC, by definition.
Instant instant = Instant.now() ; // Capture the current moment as seen in UTC.
boolean odtIsPast = odt.toInstant().isBefore( instant ) ;
Second: LocalDateTime
Your second string input lacks any indicator of offset-from-UTC or time zone. So an OffsetDateTime is the wrong class to use. Instead use LocalDateTime which lacks any concept of offset or zone.
This means a LocalDateTime cannot represent a moment. For example, noon on the 23rd of January this year could mean noon on Asia/Tokyo which would be hours earlier than noon in Europe/Paris, or it could mean noon in America/Montreal which would be a moment even more hours later. Without the context of a zone or offset, a LocalDateTime has no real meaning. So comparing a LocalDateTime to the current moment is senseless.
LocalDateTime.parse( "2019-02-14T00:00:00" )
See this code run live at IdeOne.com.
ldt.toString(): 2019-02-14T00:00
To compare, you can’t — illogical as discussed above. You must assign a time zone (or offset) to determine a moment on the timeline. If you know for certain this date and time were meant for a specific time zone, assign ZoneId to get a ZonedDateTime. Then extract a Instant to compare.
ZoneId z = ZoneId.of( "Asia/Kolkata" ) ; // India time.
ZonedDateTime zdt = ldt.atZone( z ) ;
Instant instant = Instant.now() ; // Capture the current moment as seen in UTC.
boolean zdtIsPast = zdt.toInstant().isBefore( instant ) ; // Compare.
By the way, I noticed the time-of-day is zero. If your goal was to represent the date only, without any time-of-day and without any zone, use LocalDate class.
Third: Don’t bother, ambiguous input
Your third string input carries a time zone indicator. So it should be parsed as a ZonedDateTime.
Unfortunately, you’ve chosen a terrible string format to parse. Never use the 2-4 character pseudo-zones like IST. They are not standardized. And they are not unique! Your IST could mean Ireland Standard Time or India Standard Time or others.
Specify a proper time zone name in the format of Continent/Region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland.
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = ZonedDateTime.now( z ) ;
See this code run live at IdeOne.com.
zdt.toString(): 2019-02-20T22:34:26.833+01:00[Africa/Tunis]
You could try to parse this. ZonedDateTime will make a guess as to which zone was meant by IST. But it would be just a guess, and so is unreliable given the inherently ambiguous input. Personally, I would refuse to code that, rejecting this input data back to its source.
If you insist on making this unreliable parse attempt, see the correct Answer to a similar Question you asked recently.
Educate your source about always using standard ISO 8601 formats to exchange date-time values as human-readable text.
The java.time classes use these ISO 8601 formats by default when parsing/generating strings. The ZonedDateTime class wisely extends the standard to append the standard name of the time zone in square brackets.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, Java SE 10, Java SE 11, and later - Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Most of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
I'm writing automated bdds for a rest API. And the API returns a date. I want to get the difference between the returned date from the API and the current date today.
So for example, the API returns "March 13, 2018 12:00 pm"
And today's date is "March 11, 2018 12:00pm"
The times are always the same, it's only the days that change. And the API will also return a date that's in the future.
I have this piece of code:
Date currentDate = Date.from(Instant.now());
// endDate comes from the API
long diff = endDate.getTime() - currentDate.getTime();
long differenceInDays = TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS);
This returns 1, but I want it to include the last day. I can just add +1 at the end of endDate.getTime() - currentDate.getTime(); but I'm not sure if that's the right approach.
I also read that this is not a good solution in general, because it doesn't account for daylight savings time. I'm not sure how or if it would affect my automated bdds when daylight savings comes. What's the best way to capture the difference in days?
Think of it as how many days do I have left until expiration
Your real problem is that your backend REST service is poorly designed.
ISO 8601
First of all, date-time values exchanged should be in standard ISO 8601 format, not some localized presentation string.
The standard formats are used by default in the java.time classes when parsing/generating text.
java.time
Never use the terrible Date class. That class, along with Calendar, SimpleDateFormat, and such, was supplanted years ago by the java.time classes defined in JSR 310.
Date.from(Instant.now())
Never mix the terrible legacy date-time classes (Date) with their replacements (Instant), the modern java.time classes. Mixing these is unnecessary and confusing.
The java.time classes entirely replace their predecessors.
The times are always the same, it's only the days that change. And the API will also return a date that's in the future.
If you only want to exchange date values, without a time-of-day and without a time zone or offset, use LocalDate class, and exchange the ISO 8601 format YYYY-MM-DD such as 2018-03-11. Call LocalDate.parse and LocalDate::toString.
long differenceInDays = TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS);
Representing a count of days as a count of milliseconds without the context of a time zone or offset-from-UTC is reckless. Days are not always 24 hours long. They can be 23, 23.5, 25, or some other number of hours.
If you mean to use UTC so as to always have 24-hour days, say so. Represent your date-time with an indication of time zone or offset. For example, the standard format: 2018-03-11T00:00Z where the Z on the end means UTC and is pronounced “Zulu”.
So your entire problem could be reduced to this one-liner.
ChronoUnit.DAYS.between(
LocalDate.now( ZoneId.of( "America/Montreal" ) ) , // Get the current date as seen in the wall-clock time used by the people of a particular region (a time zone).
LocalDate.parse( "2019-01-23" ) // Parse a string in standard ISO 8601 format for a date-only value.
) // Returns a `long` integer number of days elapsed.
Unzoned
If you are not in a position to clean up all those messy design problems, then let's forge ahead, trying to use this messy data.
First fix the am/pm which should be in uppercase.
String input = "March 13, 2018 12:00 pm".replace( " am" , " AM" ).replace( " pm" , " PM" );
Define a formatting pattern to match your input string.
Specify a Locale to determine the human language and cultural norms to use in translating the text.
Locale locale = Locale.US;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "MMMM d, uuuu HH:mm a" );
Parse as a LocalDateTime because your input lacks an indicator of time zone or offset-from-UTC.
LocalDateTime ldt = LocalDateTime.parse( input , f );
ldt.toString(): 2018-03-13T12:00
A LocalDateTime purposely has no concept of time zone or offset-from-UTC. So this class cannot represent a moment, is not a point on the timeline.
If you want generic 24-hour days without regard to the reality of anomalies in wall-clock time used by various people in various places, such as Daylight Saving Time (DST), we can continue to use this class.
Get the current date as seen in the wall-clock time used the people to whom your app is aimed (a time zone).
A time zone is crucial in determining a date. For any given moment, the date varies around the globe by zone. For example, a few minutes after midnight in Paris France is a new day while still “yesterday” in Montréal Québec.
If no time zone is specified, the JVM implicitly applies its current default time zone. That default may change at any moment during runtime(!), so your results may vary. Better to specify your desired/expected time zone explicitly as an argument.
Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 2-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).
ZoneId z = ZoneId.of( "America/Montreal" ) ;
LocalDate today = LocalDate.now( z ) ;
If you want to use the JVM’s current default time zone, ask for it and pass as an argument. If omitted, the JVM’s current default is applied implicitly. Better to be explicit, as the default may be changed at any moment during runtime by any code in any thread of any app within the JVM.
ZoneId z = ZoneId.systemDefault() ; // Get JVM’s current default time zone.
Get noon on that date, in no particular time zone.
LocalDateTime ldtTodayNoon = LocalDateTime.of( today , LocalTime.NOON ) ;
Count days elapsed.
long daysElapsed =
ChronoUnit.DAYS.between(
ldtTodayNoon ,
ldt
)
;
Of course we could just as well have done this using only LocalDate rather than LocalDateTime, but I followed your problem statement as written.
Notice that in your given example, the string represents a date in the past. So our number of days will be negative.
Zoned
If you did want to account for anomalies seen on some dates in some zones, then you should have represented a moment properly, as discussed above, with an indicator of time zone or offset-from-UTC.
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = ldt.atZone( z ) ;
ZonedDateTime zdtNow = ZonedDateTime.now( z ) ;
Or perhaps you want noon today in the desired time zone. If noon is not a valid time-of-day on this date in this zone, the ZonedDateTime class will adjust. Be sure to read the ZonedDateTime.of JavaDoc to understand the algorithm of that adjustment.
LocalDate today = LocalDate.now( z ) ;
ZonedDateTime zdtTodayNoon = ZonedDateTime.of( today , LocalTime.NOON , z ) ;
Calculate elapsed time either based in fractional seconds, or in whole calendar days.
Duration d = Duration.between( zdtTodayNoon , zdt ) ; // For a calculation based in whole seconds plus a fractional second in nanoseconds without regard for a calendar, just using generic 24-hour days.
Period p = Period.between( zdtTodayNoon , zdt ) ; // For a calculation based in whole days, for a number of years-months-days based on calendar dates.
If you insist on tracking by a count of milliseconds, call Duration::toMillis.
long millisecondsElapsed = d.toMillis() ; // Entire duration as a total number of milliseconds, ignoring any microseconds or nanos.
All of this has been covered many times already on Stack Overflow. You can learn more and see more examples by searching for these java.time class names.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, Java SE 10, Java SE 11, and later - Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Most of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
I am dealing with Time and Date in Java.
I am having date as : 2018-08-22T22:00:00-0500
My Time Zone offset here is -0500
How can I get the list of available Time Zone IDs?
My main objective here is to set a date to a particular Time Zone. However I do not know the time zone as it is embedded in this date format.
Update :
My question is different from Java TimeZone offset
as according to the accepted answer to that question, I need to have time zone info : "Europe/Oslo" . However I only have offset. See the accepted answer below which solves my problem.
tl;dr
eachZoneId.getRules().getOffset(
OffsetDateTime.parse(
"2018-08-22T22:00:00-0500" ,
DateTimeFormatter.ofPattern( "uuuu-MM-dd'T'HH:mm:ssX" )
).toInstant()
).equals( myTargetZoneOffset )
Offset versus Zone
I do not know the time zone as it is embedded in this date format.
No, your input string of 2018-08-22T22:00:00-0500 has no time zone. It has only a mere offset-from-UTC.
An offset is simply a number of hours, minutes, and seconds of displacement ahead of, or behind, UTC. Yours shows an offset five hours behind UTC. In contrast, a time zone is a history of past, present, and future changes to the offset used by the people of a certain region.
OffsetDateTime
In java.time, we represent a moment with an offset as a OffsetDateTime class.
Your input string is in standard ISO 8601 format. So we should be able to parse directly without specifying a formatting pattern, as the java.time classes use ISO 8601 formats by default when parsing/generating strings. However your input lacks a colon in the offset between hours and minutes. While allowed by the standard, the OffsetDateTime class a small bug in Java 8 & 9 that fails by default to parse such values. As a workaround, specify a DateTimeFormatter.
String input = "2018-08-22T22:00:00-0000";
DateTimeFormatter f = DateTimeFormatter.ofPattern( "uuuu-MM-dd'T'HH:mm:ssX" ); // Specify formatting pattern to match input string.
OffsetDateTime odt = OffsetDateTime.parse( input , f ); // Parse from dumb string to smart `OffsetDateTime` object.
odt.toString(): 2018-08-22T22:00-05:00
Time zone names
How can I get the list of available Time Zone IDs?
Not sure what you mean by “Time Zone IDs”. I am guessing that you are asking for a list of all the time zones using that particular offset-from-UTC at that particular moment.
A proper time zone name has the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).
We represent the time zone using the ZoneId. The TimeZone class is now legacy, and should be avoided.
To get our list of ZoneId objects with that offset in use on that date, we need to first extract the offset (ZoneOffset) from our OffsetDateTime.
ZoneOffset offset = odt.getOffset() ;
offset.toString(): -05:00
Next phase is to interrogate all known time zones, asking each for the offset in effect at the moment of our OffsetDateTime. The argument for that moment must be in UTC, a Instant object. So we must extract an Instant from our OffsetDateTime. Still the same moment, the same point on the timeline, but seen through the lens of a different wall-clock time.
Instant instant = odt.toInstant() ; // Extract a UTC value (`Instant`) from our `OffsetDateTime` object.
instant.toString(): 2018-08-23T03:00:00Z
The Z on the end is short for Zulu and means UTC.
Make an empty list to collect the desired zones.
List< ZoneId > hits = new ArrayList<>() ; // Make an empty list of `ZoneId` objects found to have our desired offset-from-UTC.
Now get all known zones. A method exists giving a set of all zone names, but not the zone objects. So for each iteration we must instantiate the ZoneId. Then we ask the zone for its rules, the list of changes in effect over time for that region. To the rules we pass our moment (Instant), and get back the ZoneOffset in effect at that time. If this offset matches our target offset, we add the zone to our list.
Be aware that many of the zones may be essentially duplicates or deprecated. The list of zones has had a fractured history, with many changes, and some are mere aliases to others.
Set < String > names = ZoneId.getAvailableZoneIds(); // Get a collection of all known time zones’ names.
for ( String name : names ) // Loop each name.
{
ZoneId z = ZoneId.of( name ); // Instantiate a `ZoneId` for that zone name.
ZoneRules rules = z.getRules(); // Get the history of past, present, and future changes in offset used by the people of this particular region (time zone).
ZoneOffset o = rules.getOffset( instant ); // Get the offset-from-UTC in effect at this moment for the people of this region.
if( o.equals( offset )) { // Compare this particular offset to see if it is the same number of hours, minutes, and seconds as our target offset.
hits.add( z ); // If we have a hit, add to our collection of `ZoneId` objects.
}
}
Dump our hits collection to the console.
[America/Panama, America/Chicago, America/Eirunepe, Etc/GMT+5, Pacific/Easter, Mexico/General, America/Porto_Acre, America/Guayaquil, America/Rankin_Inlet, US/Central, America/Rainy_River, America/Indiana/Knox, America/North_Dakota/Beulah, America/Monterrey, America/Jamaica, America/Atikokan, America/Coral_Harbour, America/North_Dakota/Center, America/Cayman, America/Indiana/Tell_City, Chile/EasterIsland, America/Mexico_City, America/Matamoros, CST6CDT, America/Knox_IN, America/Bogota, America/Menominee, America/Resolute, SystemV/EST5, Canada/Central, Brazil/Acre, America/Cancun, America/Lima, America/Bahia_Banderas, US/Indiana-Starke, America/Rio_Branco, SystemV/CST6CDT, Jamaica, America/Merida, America/North_Dakota/New_Salem, America/Winnipeg]
Be aware that this list of zones is valid only for our chosen particular moment. In earlier times, or later times, some of these zones may be using some other offset-from-UTC. Conversely, at other moments some zones not on this list may be using our desired offset.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android, the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
You can convert your date to a Calendar. Calendar.getTimeZone() would return the TimeZone match the Calendar. And then TimeZone.getAvailableIDs() would give you the available IDs.
I have to convert UTC time into user local time zone. Currently, I have the two parameters one is time in long format and another is time zone name in string format like "(UTC-05:00) Eastern Time (US and Canada), (UTC-06:00) Central Time (US and Canada)" etc.
So now using these two parameters I have to get date time in string format. I am facing the issue while I am trying to convert the date into a string because the SimpleDateFormat.format(...) will convert the date using its default time zone.
Below are the code portion
public static void main(String[] args)
{
long time = 1490112300000L;
System.out.println("UTC Time "+ convertLongToStringUTC(time));
String EST = "(UTC-05:00) Eastern Time (US and Canada)";
TimeZone timeZone1 = TimeZone.getTimeZone(EST);
System.out.println("EST "+ convertTimeZone(time, timeZone1));
String CST = "(UTC-06:00) Central Time (US and Canada)";
TimeZone timeZone2 = TimeZone.getTimeZone(CST);
System.out.println("CST "+ convertTimeZone(time, timeZone2));
String IST = "IST";
TimeZone timeZone = TimeZone.getTimeZone(IST);
System.out.println("IST "+ convertTimeZone(time, timeZone));
}
public String convertTimeZone(long time, TimeZone timeZone)
{
Date date = new Date(time);
DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
format.setTimeZone(timeZone);
return format.format(date);
}
public String convertLongToStringUTC(long time)
{
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
String utcTime = sdf.format(new Date(time));
return utcTime;
}
Also let me know if we can achieve this using offset ?
Use this constructor
SimpleDateFormat(String pattern, Locale locale)
Constructs a SimpleDateFormat using the given pattern and the default
date format symbols for the given locale. Note: This constructor may
not support all locales. For full coverage, use the factory methods in
the DateFormat class.
Java Doc
tl;dr
Instant.ofEpochMilli( 1_490_112_300_000L )
.atOffset( ZoneOffset.of( "-05:00" ) )
Instant.ofEpochMilli( 1_490_112_300_000L )
.atZone( ZoneId.of( "America/New_York" ) )
Details
The Answer by Dennis is close. I will provide further information.
Your Question is not exactly clear about the inputs. I will assume your long integer number represents a moment in UTC.
An offset-from-UTC is an number of hours and minutes and seconds before or after UTC. In java.time, we represent that with a ZoneOffset.
While ZoneId technically works (as seen in code by Dennis), that is misleading as a zone is much more than an offset. A zone is a region’s history of various offsets that were in effect at different periods of history. A zone also includes any planned future changes such as DST cutovers coming in the next months.
ZoneOffset offset = ZoneOffset.of( 5 , 30 ); // Five-and-a-half hours ahead of UTC.
ZoneOffset offset = ZoneOffset.of( "+05:30" );
Tip: Always include the padding zero on the hours. While not always required in various protocols such as ISO 8601, I have seen software systems burp when encountering single-digit hours like +5:00.
If you know the intended time zone for certain, use it. A zone is always better than a mere offset as it brings all that historical information of other offsets for the past, present, and future.
Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).
ZoneId z = ZoneId.of( "Asia/Kolkata" );
I am guessing your number is a number of milliseconds since the epoch of 1970-01-01T00:00:00Z.
Instant instant = Instant.ofEpochMilli( 1_490_112_300_000L );
The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).
You can adjust into a time zone.
ZonedDateTime zdt = instant.atZone( z );
These issues have been covered many times in Stack Overflow. Hence the down-votes you are collecting (I am guessing). Please search Stack Overflow thoroughly before posting.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
Java SE 8 and SE 9 and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
The ThreeTenABP project adapts ThreeTen-Backport (mentioned above) for Android specifically.
See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
Using Java 8 you can do
OffsetDateTime dt = Instant.ofEpochMilli(System.currentTimeMillis())
.atOffset( ZoneOffset.of("-05:00"));
//In zone id you put the string of the offset you want