As explained in @Matt's answer, it's not a good thing to just ignore the offset (GMT-8), because SimpleDateFormat
will use the system's default timezone - and this timezone can be different in each device/environment, and you have no control over it. Even if the default timezone is correct, it can be changed, even at runtime, so you can't assume that it'll always be what you need.
Example: my default timezone is America/Sao_Paulo
, and the current offset is -03:00
(or GMT-3
, or 3 hours behind UTC). If I use your code, it will assume it is 13:34 in São Paulo (which is 16:34 in UTC - the millis value is 4876130061000
, which is wrong because it's not equivalent to the original input (13:34 in GMT-8)).
The code only gives the correct values if I change my default timezone to be GMT-8
. To not depend on that, you can set the timezone of the formatter.
SimpleDateFormat
has some patterns to parse timezone/offset, but none of them worked with GMT-8
(it seems that it accepts only GMT-08:00
), so one solution is to remove this from the input and use it to set the correct timezone to the formatter:
String input = "Tue, 08 Jul 2124 13:34:21 GMT-8";
String[] v = input.split(" GMT"); // split the string, before and after "GMT"
SimpleDateFormat serverDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss", Locale.ENGLISH);
// set the formatter timezone to the offset (in this case, to "GMT-8")
serverDateFormat.setTimeZone(TimeZone.getTimeZone("GMT" + v[1]));
// parse the date/time part
Date date = serverDateFormat.parse(v[0]);
The date millis value will be 4876148061000
, which is equivalent to 2124-07-08T21:34:21Z
(21:34 UTC = 13:34 in GMT-8).
Another detail is that, when you do:
System.out.println(date);
It calls Date::toString
method, which gets the millis value and convert to system's default timezone, giving the false impression that the date object has a timezone - but that's wrong: the date contains only the number of milliseconds since 1970-01-01T00:00Z
.
toString
is usually called also when you see the date in a debugger
If you want to print the equivalent date and time values for some specific timezone, you can use a SimpleDateFormat
and set the timezone you want in this formatter. Using Date::toString
always misleads you about the values.
To print the date/time using the same offset used by the server, you can create a SimpleDateFormat
and set the GMT-8
to it, as above:
// display the date in a specific format
SimpleDateFormat outputFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
// use the same offset used by the server (GMT-8)
outputFormat.setTimeZone(TimeZone.getTimeZone("GMT-8"));
System.out.println(outputFormat.format(date)); // 08/07/2124 13:34:21
The output will be:
08/07/2124 13:34:21
If you don't specify a timezone, it'll use the device/system's default, giving different results depending on the device's configuration.
Note that I also used Locale.ENGLISH
to parse the input. That's needed because the month and day of week are in English. If I don't specify a java.util.Locale
, the formatter will use the system's default and it's not guaranteed to always be English. And this also can be changed at runtime, so it's better to use an explicit one instead of relying on the default.
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.
In Android you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. To make it work, you'll also need ThreeTenABP (more on how to use it here).
As the input has a date, time and offset, you can parse it to a org.threeten.bp.OffsetDateTime
, using a org.threeten.bp.format.DateTimeFormatter
.
One detail is that July 8th 2124 is a Saturday, so I had to change the input string, otherwise I get an error. SimpleDateFormat
doesn't give this error because it's known to be too lenient and ignore lots of errors and try to "fix" in not-so-smart ways (that might be a little opinion-based, but many consider this a bad thing, and that's why the new API is more strict about this).
String input = "Sat, 08 Jul 2124 13:34:21 GMT-8";
DateTimeFormatter parser = DateTimeFormatter.ofPattern("EE, dd MMM yyyy HH:mm:ss O", Locale.ENGLISH);
OffsetDateTime odt = OffsetDateTime.parse(input, parser);
odt
will be equivalent to 2124-07-08T13:34:21-08:00
.
There's no need to set a timezone in the formatter, because it parses the offset from the input correctly. To get the millis value, just do:
// the same value as date.getTime()
long millis = odt.toInstant().toEpochMilli();
To convert to a java.util.Date
, you can use the org.threeten.bp.DateTimeUtils
class:
// convert to java.util.Date
Date date = DateTimeUtils.toDate(odt.toInstant());
To display the to the users, you can use a different DateTimeFormatter
. As the OffsetDateTime
keeps the correct values, there's no need to set a timezone in the formatter:
// display date in a specific format
DateTimeFormatter outputFormat = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss");
System.out.println(odt.format(outputFormat)); // 08/07/2124 13:34:21
The output will be:
08/07/2124 13:34:21
To convert to another timezone or offset, you can use the classes org.threeten.bp.ZoneId
and org.threeten.bp.ZoneOffset
:
// convert to UTC
System.out.println(outputFormat.format(odt.withOffsetSameInstant(ZoneOffset.UTC)));
// convert to offset +05:00
System.out.println(outputFormat.format(odt.withOffsetSameInstant(ZoneOffset.ofHours(5))));
// convert to Europe/Berlin timezone
System.out.println(outputFormat.format(odt.atZoneSameInstant(ZoneId.of("Europe/Berlin"))));
The output is:
08/07/2124 21:34:21
09/07/2124 02:34:21
08/07/2124 23:34:21
Note that I used the timezone Europe/Berlin
.
The API uses IANA timezones names (always in the format Continent/City
, like America/Sao_Paulo
or Europe/Berlin
).
Avoid using the 3-letter abbreviations (like CST
or PST
) because they are ambiguous and not standard.
You can get a list of all available timezones (and choose accordingly) with ZoneId.getAvailableZoneIds()
.