1

This question solves the case for seconds: How to convert a date time string to long (UNIX Epoch Time) in Java 8 (Scala)

But if I want milliseconds it seems I have to use

def dateTimeStringToEpoch(s: String, pattern: String): Long = 
    LocalDateTime.parse(s, DateTimeFormatter.ofPattern(pattern))
      .atZone(ZoneId.ofOffset("UTC", ZoneOffset.ofHours(0)))
      .toInstant().toEpochMilli

which is ugly for the 4 issues I detail in the other question (main things I don't like is the magic literal "UTC" and the magic number 0).

Unfortunately the following does not compile

def dateTimeStringToEpoch(s: String, pattern: String): Long = 
     LocalDateTime.parse(s, DateTimeFormatter.ofPattern(pattern))
                  .toEpochMilliSecond(ZoneOffset.UTC)

as toEpochMilliSecond does not exist

samthebest
  • 30,803
  • 25
  • 102
  • 142
  • 1
    Replace `ZoneId.ofOffset("UTC", ZoneOffset.ofHours(0))` with [`ZoneOffset.UTC`](https://docs.oracle.com/javase/8/docs/api/java/time/ZoneOffset.html#UTC) – Andreas Mar 12 '19 at 15:28
  • @Andreas almost. ZoneOffset.UTC is a `ZoneOffset`, so need to change the call to `atZone` to be `atOffset`. – ptomli Mar 12 '19 at 15:33
  • 1
    @ptomli Not strictly needed since a `ZoneOffset` is a `ZoneId`. The difference is whether you get a `ZonedDateTime` or `OffsetDateTime` back, but since both have the `toInstant()` method, and the object is not otherwise used, it makes no difference. – Andreas Mar 12 '19 at 15:34
  • @Andreas And StackOverflow teaches me something new, yet again... Thanks – ptomli Mar 12 '19 at 15:35

3 Answers3

5

Could you not use LocalDateTime#atOffset and ZoneOffset#UTC?

LocalDateTime.parse(s, dateTimeFormatter).atOffset(ZoneOffset.UTC).toInstant().toEpochMilli()

As @Andreas points out in the comments, ZoneOffset is-a ZoneId, so you could use

def dateTimeStringToEpoch(s: String, pattern: String): Long = 
    LocalDateTime.parse(s, DateTimeFormatter.ofPattern(pattern))
      .atZone(ZoneOffset.UTC)
      .toInstant()
      .toEpochMilli()
samthebest
  • 30,803
  • 25
  • 102
  • 142
ptomli
  • 11,730
  • 4
  • 40
  • 68
2

When working with UNIX Epoch, I suggest using java.time.Instant as it designed for epoch representation and takes UTC into consideration by default, as it is part of the standard.

import java.time.Instant

object InstantFormat extends App {

  //Instant.parse uses DateTimeFormatter.ISO_INSTANT
  println(Instant.parse("2019-03-12T15:15:13.147Z"))
  println(Instant.parse("2019-03-12T15:15:13Z"))
  println(Instant.parse("2019-03-12T15:15:13Z").toEpochMilli)
  println(Instant.parse("2019-03-12T15:15:13Z").getEpochSecond)
  println(Instant.ofEpochMilli(1552403713147L))
  println(Instant.ofEpochSecond(1552403713L))
}

outputs

2019-03-12T15:15:13.147Z
2019-03-12T15:15:13Z
1552403713000
1552403713
2019-03-12T15:15:13.147Z
2019-03-12T15:15:13Z
Ivan Stanislavciuc
  • 7,140
  • 15
  • 18
2

You can change this answer to return epoch millis like

static long dateTimeStringToEpoch(String s, String pattern) {
    return DateTimeFormatter.ofPattern(pattern).withZone(ZoneOffset.UTC)
        .parse(s, Instant::from).toEpochMilli();
}

Or, if you even want to avoid the construction of a temporary Instant:

static long dateTimeStringToEpoch(String s, String pattern) {
    return DateTimeFormatter.ofPattern(pattern).withZone(ZoneOffset.UTC)
        .parse(s, ta -> ta.getLong(ChronoField.INSTANT_SECONDS)*1000
                       +ta.get(ChronoField.MILLI_OF_SECOND));
}

Note that both, DateTimeFormatter.ofPattern(pattern).withZone(ZoneOffset.UTC) and ta -> ta.getLong(ChronoField.INSTANT_SECONDS)*1000+ta.get(ChronoField.MILLI_OF_SECOND) are reusable components here, e.g. you could do

static final DateTimeFormatter MY_PATTERN
    = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").withZone(ZoneOffset.UTC);
static final TemporalQuery<Long> EPOCH_MILLIS
    = ta -> ta.getLong(ChronoField.INSTANT_SECONDS)*1000+ta.get(ChronoField.MILLI_OF_SECOND);

and

long millis = MY_PATTERN.parse("2018-07-21 18:30", EPOCH_MILLIS);

The question is, how many different format strings do you expect to occur within you application. Usually, it’s not something that changes as often as the formatted dates you have to parse. It might be beneficial to create a cache mapping from format string to prepared DateTimeFormatter. The lambda expression is a singleton anyway.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • I can't seem to get that to compile in Scala, not sure how to translate `Instant::from` – samthebest Mar 12 '19 at 17:24
  • 1
    `Instant::from` is equivalent to `ta -> Instant.from(ta)` or `(TemporalAccessor ta) -> Instant.from(ta)`. It’s a function invoking the `static` method [`Instant.from(TemporalAccessor)`](https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html#from-java.time.temporal.TemporalAccessor-). On the Java side, this function will implement `TemporalQuery` but that’s determined by type inference. – Holger Mar 12 '19 at 18:21
  • Because I'm using an old (2.11) version of Scala I think I'm having issues, so I have to do it the long way like this: `.parse(s, new TemporalQuery[Instant] {override def queryFrom(temporal: TemporalAccessor): Instant = Instant.from(temporal)})` – samthebest Mar 12 '19 at 18:26