Reputation: 2430
I have a function that parses an Unix epoch time into the format yyyy-MM-dd'T'HH:mm:ss.SSSXXX
like this in order to export it to a file:
public static final SimpleDateFormat REQIF_DATE_FORMAT_WITH_MILLIS
= new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
public static String convertEpochStringToReqifDateString(String epochString) {
Date timestamp = new Date(Long.parseLong(epochString));
return REQIF_DATE_FORMAT_WITH_MILLIS.format(timestamp);
}
Now, I have tests for this export, but while they pass locally, they fail on the server because it's apparently in a different time zone. Specifically, the differences look like this:
LAST-CHANGE="2017-03-13T21:36:44.261+01:00"
LAST-CHANGE="2017-03-13T20:36:44.261Z"
I have already tried running a number of things before the test in order to make sure that the test is always run in the same time zone, such as:
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
System.setProperty("user.timezone", "UTC");
As well as the JUnitPioneer annotation:
@DefaultTimeZone("UTC")
...however, none of them seemed to affect the parsing output at all.
What can I do about this? All I want is some way to ensure that my tests are run in the same time zone regardless of where the machine they are run is standing so I can test the export correctly.
Upvotes: 2
Views: 2805
Reputation: 79105
The java.util
Date-Time API and their formatting API, SimpleDateFormat
are outdated and error-prone. It is recommended to stop using them completely and switch to the modern Date-Time API*.
You can use Instant.ofEpochMilli
to convert the epoch milliseconds into Instant
and then use the Instant#toString
. However, Instant#toString
omits the fraction-of-second part if they are zero. Therefore, if you need the value strictly in the pattern, yyyy-MM-dd'T'HH:mm:ss.SSSXXX
, you can convert the Instant
to OffsetDateTime
and format it using a DateTimeFormatter
.
Solution using java.time
, the modern Date-Time API:
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
// An example epoch milliseconds
long millis = 1631113620000L;
Instant instant = Instant.ofEpochMilli(millis);
String strDateTime = instant.toString();
System.out.println(strDateTime);
// If you need the value strictly in the pattern, yyyy-MM-dd'T'HH:mm:ss.SSSXXX
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSXXX", Locale.ENGLISH);
OffsetDateTime odt = instant.atOffset(ZoneOffset.UTC);
strDateTime = odt.format(dtf);
System.out.println(strDateTime);
}
}
Output:
2021-09-08T15:07:00Z
2021-09-08T15:07:00.000Z
Learn more about the modern Date-Time API from Trail: Date Time.
* For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.
Upvotes: 6
Reputation: 140319
A better way to do this would be to use java.time.Instant
:
Instant instant = Instant.ofEpochMilli(Long.parseLong(epochString));
return instant.toString();
This will always print in UTC, whatever the JVM's default time zone.
You can also do this by setting the time zone on the SimpleDateFormatter
:
REQIF_DATE_FORMAT_WITH_MILLIS.setTimeZone(TimeZone.getTimeZone("UTC"));
Note that you need to take care with a shared SimpleDateFormat
because it has mutable state, which is corrupted by access from multiple threads. You can have a separate instance per thread like so:
static final ThreadLocal<SimpleDateFormat> REQIF_DATE_FORMAT_WITH_MILLIS =
ThreadLocal.withInitial(() -> {
SimpleDateFormat sdf = new SimpleDateFormat();
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
return sdf;
});
and then access it with:
return REQIF_DATE_FORMAT_WITH_MILLIS.get().format(timestamp);
But this is getting quite messy, isn't it? Much easier just to use Instant
.
Upvotes: 2