Patrick Schaefer
Patrick Schaefer

Reputation: 851

How to deal with timstamps with extra milliseconds in a Java Date

I have a javax.ws.rs REST API that accepts an object body that has a timestamp field mapped to a util Date with JPA, but something sending to it is sending a timestamp with extra milliseconds (maybe supposed to be nanoseconds?) and that causes the date to be in the future when I use the object within my request handler method.

for example this came in: "TimeStamp": "2020-04-24T16:26:11.9376071Z",

and it resolved to "2020-04-24T19:02:27" in the object.

If I use Postman and send the exact same message just with the TimeStamp reduced to 2 milliseconds it works as expected and the date is correct.

So, assuming I can't change what's being sent but want to be able to handle it, how can I shorten the milliseconds so the Date resolves correctly?

Upvotes: 1

Views: 403

Answers (1)

rzwitserloot
rzwitserloot

Reputation: 103502

Such strings are parsed according to a pattern. The numbers following the 'dot' are parsed as milliseconds, and parsing dates is evidently configured in lenient mode, which means overflow is adjusted into the higher fields. If you parse 9376071 as milliseconds, that's 9376.071 seconds; that's about 2 hours and change. Add that to '16:26:11' and you get 19:02. So, that's what's going wrong here.

I don't see enough detail in the mechanism you're using to transit this string into a value of type java.util.Date - in various frameworks you can explicitly specify the pattern. However, the 'old' API (the one java.util.Date belongs to cannot parse this input - it has no option to parse that dangling batch of digits properly. Yes, really. The old API (java.text.SimpleDateFormat) cannot actually read ISO8601 - a grievous ommision which strongly suggests you really, really need to stop using this incapable old deprecated stuff. (ISO8601 does indeed allow any number of digits on the fractional part, and it allows a fractional part on the 'lowest' entry in the input, therefore, the timestamp you get, while somewhat exotic, fits the ISO8601 spec).

But, good news!

The newer API does it just fine!

import java.time.Instant;
import java.time.format.DateTimeFormatter;

class Test { public static void main(String[] args) {
    Instant parsed =  
        DateTimeFormatter.ISO_INSTANT.parse("2020-04-24T16:26:11.9376071Z", Instant::from);
    System.out.println(parsed);
}}

I'm not entirely sure how you can tell your framework to stop using the bad API, but once you've managed to tell it to stop hitting itself in the face with the old one, all will be well again.

Sidenote: j.u.Date is a really bad type to use; it does not represent a date at all, but an instant in time, and badly at that. In general I wouldn't use API that is so epically badly named! May I suggest java.time.Instant instead? Its name matches what it represents, and should be drop-in ready. Another workable option is ZonedDateTime or LocalDateTime depending on what it represents).

Upvotes: 2

Related Questions