1. Overview
In this tutorial, we’ll see how to create a DateTime object from epoch milliseconds, which is the standard in UNIX systems. For that, we’ll make use of the existing Java Date/Time APIs.
2. Getting Current Epoch Milliseconds
The first step is to get the current date and time in milliseconds. This can be achieved easily by using the standard method System.currentTimeMillis():
scala> val millis = System.currentTimeMillis()
millis: Long = 1697797337706
This value represents the total number of milliseconds as of when the call was made since the UNIX epoch (midnight January 1, 1970 UTC).
Another possibility is to use the Instant class from the Java Date/Time API:
scala> Instant.now().toEpochMilli
res0: Long = 1698401674961
This is the equivalent to the previous example.
3. Converting Milliseconds to DateTime Objects
In this next step, we want to create a DateTime object from the milliseconds we got in the previous step. Unfortunately, we can’t create a ZonedDateTime object directly from milliseconds, so we first need to create an Instant object:
scala> val instant = Instant.ofEpochMilli(millis)
instant: java.time.Instant = 2023-10-20T15:09:36.315Z
This Instant object that we created represents an instantaneous point on the timeline. This class doesn’t contain any time zone information, which may lead to wrong calendar math for many systems.
Let’s see how we can add time zone information to the Instant object:
scala> ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
res0: java.time.ZonedDateTime = 2023-10-20T15:09:36.315Z[UTC]
scala> ZonedDateTime.ofInstant(instant, ZoneId.of("UTC+3"))
res1: java.time.ZonedDateTime = 2023-10-20T18:09:36.315+03:00[UTC+03:00]
scala> ZonedDateTime.ofInstant(instant, ZoneId.of("Europe/Lisbon"))
res2: java.time.ZonedDateTime = 2023-10-20T16:09:36.315+01:00[Europe/Lisbon]
Above, we demonstrate how to add timezone information in three different ways.
All of them consist of creating a ZonedDateTime from an Instant. For that, we need to pass the ZoneId. The ZoneId can be retrieved in many ways. We can just pass a known zone, like UTC. Alternatively, we can provide an offset like UTC+3. And lastly, we can just pass the region ID, such as Europe/Lisbon.
4. Formatting DateTime Objects
Another very common concern is how to properly format the DateTime objects we just created.
For that, we’ll use the DateTimeFormatter class, which offers a wide range of default formats.
scala> val utcDateTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
utcDateTime: java.time.ZonedDateTime = 2023-10-20T15:09:36.315Z[UTC]
scala> val lisbonDateTime = ZonedDateTime.ofInstant(instant, ZoneId.of("Europe/Lisbon"))
lisbonDateTime: java.time.ZonedDateTime = 2023-10-20T16:09:36.315+01:00[Europe/Lisbon]
scala> val dateTimeFormatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME
dateTimeFormatter: java.time.format.DateTimeFormatter = ParseCaseSensitive(false)(ParseCaseSensitive(false)(Value(Year,4,10,EXCEEDS_PAD)'-'Value(MonthOfYear,2)'-'Value(DayOfMonth,2))'T'(Value(HourOfDay,2)':'Value(MinuteOfHour,2)[':'Value(SecondOfMinute,2)[Fraction(NanoOfSecond,0,9,DecimalPoint)]]))ParseStrict(false)Offset(+HH:MM:ss,'Z')ParseStrict(true)
We started by creating two objects using the previous approaches. We got a ZonedDateTime for UTC and another for Lisbon time zones from the initial Instant. Finally, we decided to use one of the default formats available in the DateTimeFormatter class. Let’s see the output:
scala> dateTimeFormatter.format(utcDateTime)
res0: String = 2023-10-20T15:09:36.315Z
scala> dateTimeFormatter.format(lisbonDateTime)
res1: String = 2023-10-20T16:09:36.315+01:00
There are a lot more formats, and we can even build our own.
5. Conclusion
In this article, we saw how to leverage Scala interoperability with Java to convert from milliseconds to DateTime.
As we saw, this can be done by passing the UNIX epoch millisecond and then converting it into an Instant object or a ZonedDateTime if we want to include time zone information as well.
Finally, we had a sneak peek into formatting DateTime objects properly.