I have time stamp in format 2017-18-08 11:45:30.345
.
I want to convert it to epoch time, so I am doing below:
String timeDateStr = "2017-18-08 11:45:30.345";
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-dd-MM HH:mm:ss.SSS");
ZonedDateTime zdt = ZonedDateTime.parse(timeDateStr, dtf);
System.out.println(zdt.toInstant().toEpochMilli());
I am getting below error:
java.time.format.DateTimeParseException: Text '2017-18-08 11:45:30.345' could not be parsed: Unable to obtain ZonedDateTime from TemporalAccessor
I also tried different formats but still getting errors.
Note: originally the question had the input 2017-18-08 12:60:30.345
(with 60
in the minutes field), then it was edited (the time changed from 12:60
to 11:45
), but I decided to keep this answer discussing about the original input (12:60
), as it also works for the edited version (11:45
).
ZonedDateTime
needs a timezone or offset, but the input String
doesn't have it (it has only date and time).
There are also another details in the input:
60
, which is not accepted: the valid values are from 0 to 59 (actually there's a way to accept this, see "Lenient parsing" below)hh
is the clock-hour-of-am-pm field, so it also needs the AM/PM designator to be fully resolved. As you don't have it, you should use the HH
pattern insteadSo the pattern must be yyyy-dd-MM HH:mm:ss.SSS
, the input can't have 60
as the minutes value (unless you use lenient parsing, which I'll explain below) and you can't direclty parse it to a ZonedDateTime
because it doesn't have a timezone/offset designator.
One alternative is to parse it to a LocalDateTime
and then define in which timezone/offset this date is. In the example below, I'm assuming it's in UTC:
// change 60 minutes to 59 (otherwise it doesn't work)
String timeDateStr = "2017-18-08 12:59:30.345";
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-dd-MM HH:mm:ss.SSS");
// parse to LocalDateTime
LocalDateTime dt = LocalDateTime.parse(timeDateStr, dtf);
// assume the LocalDateTime is in UTC
Instant instant = dt.toInstant(ZoneOffset.UTC);
System.out.println(instant.toEpochMilli());
This will output:
1503061170345
Which is the equivalent of 2017-18-08 12:59:30.345
in UTC.
If you want the date in another timezone, you can use the ZoneId
class:
// get the LocalDateTime in some timezone
ZonedDateTime z = dt.atZone(ZoneId.of("Europe/London"));
System.out.println(z.toInstant().toEpochMilli());
The output is:
1503057570345
Note that the result is different, because the same local date/time represents a different Instant
in each timezone (in each part of the world, the local date/time 2017-18-08 12:59:30.345
happened in a different instant).
Also note that API uses IANA timezones names (always in the format Region/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 available timezones (and choose the one that fits best your system) by calling ZoneId.getAvailableZoneIds()
.
You can also use the system's default timezone with ZoneId.systemDefault()
, but this can be changed without notice, even at runtime, so it's better to explicity use a specific one.
There's also the option to convert the LocalDateTime
to an offset (like -05:00
or +03:00
):
// get the LocalDateTime in +03:00 offset
System.out.println(dt.toInstant(ZoneOffset.ofHours(3)).toEpochMilli());
The output will be equivalent to the local date/time in the offset +03:00
(3 hours ahead of UTC):
1503050370345
As @MenoHochschild reminded me in the comments, you can use lenient parsing to accept 60
in the minutes field (using the java.time.format.ResolverStyle
class):
String timeDateStr = "2017-18-08 12:60:30.345";
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-dd-MM HH:mm:ss.SSS")
// use lenient parsing
.withResolverStyle(ResolverStyle.LENIENT);
// parse to LocalDateTime
LocalDateTime dt = LocalDateTime.parse(timeDateStr, dtf);
In this case, 60 minutes are adjusted to the next hour, and the LocalDateTime
will be:
2017-08-18T13:00:30.345
If you decide to use UTC or a fixed offset (using ZoneOffset
class), you can ignore this section.
But if you decide to use a timezone (with ZoneId
class), you must also take care of DST (Daylight Saving Time) issues. I'm gonna use the timezone I live in as example (America/Sao_Paulo
).
In São Paulo, DST starts at October 15th 2017: at midnight, clocks shift 1 hour forward from midnight to 1 AM. So all local times between 00:00 and 00:59 don't exist in this timezone. If I create a local date in this interval, it's adjusted to the next valid moment:
ZoneId zone = ZoneId.of("America/Sao_Paulo");
// October 15th 2017 at midnight, DST starts in Sao Paulo
LocalDateTime d = LocalDateTime.of(2017, 10, 15, 0, 0, 0, 0);
ZonedDateTime z = d.atZone(zone);
System.out.println(z);// adjusted to 2017-10-15T01:00-02:00[America/Sao_Paulo]
When DST ends: in February 18th 2018 at midnight, clocks shift back 1 hour, from midnight to 23 PM of 17th. So all local times from 23:00 to 23:59 exist twice (in DST and in non-DST), and you must decide which one you want:
// February 18th 2018 at midnight, DST ends in Sao Paulo
// local times from 23:00 to 23:59 at 17th exist twice
LocalDateTime d = LocalDateTime.of(2018, 2, 17, 23, 0, 0, 0);
// by default, it gets the offset before DST ends
ZonedDateTime beforeDST = d.atZone(zone);
System.out.println(beforeDST); // before DST end: 2018-02-17T23:00-02:00[America/Sao_Paulo]
// get the offset after DST ends
ZonedDateTime afterDST = beforeDST.withLaterOffsetAtOverlap();
System.out.println(afterDST); // after DST end: 2018-02-17T23:00-03:00[America/Sao_Paulo]
Note that the dates before and after DST ends have different offsets (-02:00
and -03:00
). This affects the value of epochMilli.
You must check when DST starts and ends in the timezone you choose and check the adjustments accordingly.