java.time
Instant convertInstant = Instant.parse(date);
An Instant
(just like a Date
) represents a point in time independently of time zone. So you’re fine. As an added bonus your String of 2018-09-20T17:00:00Z
is in the ISO 8601 format for an instant, so the Instant
class parses it without the need for specifying the format.
EDIT: To format it into a human readable string in British Summer Time with unambiguous UTC offset use for example:
DateTimeFormatter formatter
= DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss Z yyyy", Locale.UK);
ZonedDateTime dateTime = convertInstant.atZone(ZoneId.of("Europe/London"));
String formatted = dateTime.format(formatter);
System.out.println(formatted);
This code snippet printed:
Thu Sep 20 18:00:00 +0100 2018
18:00 is the correct time at offset +01:00. The Z
at the end of the original string means offset zero, AKA “Zulu time zone”, and 17 at offset zero is the same point in time as 18:00 at offset +01:00. I took over the format pattern string from your own answer.
EDIT 2
I wanted to present to you my suggestion for rewriting the Fixture
class from your own answer:
public class Fixture implements Comparable<Fixture> {
private static DateTimeFormatter formatter
= DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss Z yyyy", Locale.UK);
public Instant date;
/** @param date Date string from either web service or persistence */
public Fixture(String date) {
this.date = Instant.parse(date);
}
/** @return a string for persistence, e.g., Firebase */
public String getDateForPersistence() {
return date.toString();
}
/** @return a string for the user in the default time zone of the device */
public String getFormattedDate() {
return date.atZone(ZoneId.systemDefault()).format(formatter);
}
@Override
public int compareTo(Fixture other) {
return date.compareTo(other.date);
}
@Override
public String toString() {
return "Fixture [date=" + date + "]";
}
}
This class has a natural ordering (namely by date and time) in that it implements Comparable
, meaning you no longer need your DateSorter
class. A few lines of code to demonstrate the use of the new getXx
methods:
String date = "2018-09-24T11:30:00Z";
Fixture fixture = new Fixture(date);
System.out.println("Date for user: " + fixture.getFormattedDate());
System.out.println("Date for Firebase: " + fixture.getDateForPersistence());
When I ran this snippet in Europe/London time zone I got:
Date for user: Mon Sep 24 12:30:00 +0100 2018
Date for Firebase: 2018-09-24T11:30:00Z
So the user gets the date and time with his or her own offset from UTC as I think you asked for. Trying the same snippet in Europe/Berlin time zone:
Date for user: Mon Sep 24 13:30:00 +0200 2018
Date for Firebase: 2018-09-24T11:30:00Z
We see that the user in Germany is told that the match is at 13:30 rather than 12:30, which agrees with his or her clock. The date to be persisted in Firebase is unchanged, which is also what you want.
What went wrong in your code
There are two bugs in your format pattern string, yyyy-MM-dd'T'hh:mm:ss'Z'
:
- Lowercase
hh
is for hour within AM or PM from 01 through 12 and only meaningful with an AM/PM marker. In practice you will get the correct result except when parsing an hour of 12, which will be understood as 00.
- By parsing
Z
as a literal you are not getting the UTC offset information from the string. Instead SimpleDateFormat
will use the time zone setting of the JVM. This obviously differs from one device to the other and explains why you got different and conflicting results in different devices.
The other thing going on in your code is the peculiar behaviour of Date.toString
: this method grabs the JVM’s time zone setting and uses it for generating the string. So when one device is set to Europe/London and another to GMT+01:00, then equal Date
objects will be rendered differently on those devices. This behaviour has confused many.
Question: Can I use java.time on Android?
Yes, java.time
works nicely on older and newer Android devices. It just requires at least Java 6.
- In Java 8 and later and on newer Android devices (from API level 26, I’m told) the modern API comes built-in.
- In Java 6 and 7 get the ThreeTen Backport, the backport of the new classes (ThreeTen for JSR 310; see the links at the bottom). The code above was developed and run with
org.threeten.bp.Duration
from the backport.
- On (older) Android use the Android edition of ThreeTen Backport. It’s called ThreeTenABP. And make sure you import the date and time classes from
org.threeten.bp
with subpackages.
Links