Converting a separate date, hours, minutes, seconds, and AM/PM to a java.time.Instant

732 Views Asked by At

From the front end I am receiving a separate LocalDate (variable name is date), along with separate Integers for hours, minutes, seconds, and an "AM" or "PM" String, and I need to combine these into a java.time.Instant object to store in the database. I tried to construct a LocalTime as follows, adding 12 hours if this is a PM time and then constructing an Instant:

LocalTime time = LocalTime.of("pm".equals(amPm) ? hours + 12: hours, minutes, seconds);

Instant instant = date.atTime(time).toInstant(ZoneOffset.UTC);

But when I store and reload the page, though the date is always intact, the time is always being changed. If I set the date to 1/29/1900 and the time to 07:01:01 AM, the Instant I am creating and storing has the value: 1900-01-29T07:01:01Z when I debug, which appears correct, but when the page reloads, the time says 02:01:01 AM, and that is the time that is stored in the database.

Am I constructing the time or the instant incorrectly?

1

There are 1 best solutions below

0
On

There’s hardly any doubt that your unexpected observations are due to one or more time zone issues.

So the first thing you need to do is make sure you know which time zones are involved.

  1. Which time zone is your front end using for sending date and time to you?
  2. Which time zone is your database using for storing the date and time and displaying them to you when you check them? (UTC would be recommended for storing the times.)

Once you know this you can check:

  • Is the conversion from 1/29/1900 07:01:01 AM from the front end in some time zone to an Instant of 1900-01-29T07:01:01Z correct? The Instant displays its time in UTC (denoted by the trailing Z).
  • Is the conversion from the Instant to 02:01:01 AM in the database time zone correct?
  • Is the time being fetched correctly from the database? I am assuming you are fetching it back into Java?
  • Is the time you’ve got in Java being converted correctly to 02:01:01 AM on the front end? Again I am assuming that on page reload your are displaying the time fetched from the database, but I don’t think you have told us, so I could be wrong.

To answer your question:

Am I constructing the time or the instant incorrectly?

It depends; it’s certainly possible.

  • Your construction of the time is assuming that pm is always in lower case and that 12 o’clock (midnight or noon) is given as 0. On one hand I find both assumptions more or less unlikely, on the other hand they cannot account for the discrepancy of 5 hours that you observed. 12 would conventionally be given as 12 (not 0) on a 12 hour clock. And your question gives PM in upper case.
  • Your construction of the Instant assumes that the front end sent the time in UTC. To me this sounds unlikely too, and it may be the reason or one of the reasons why you observed an incorrect time being displayed back after page reoload.

Code example

In the following snippet I am making the opposite assumptions: 12 is given as 12, AM/PM may be in any case, and the front end time zone is America/New_York. It’s probably way off, but there may be a detail that you can pick and use for your purpose.

    DateTimeFormatter timeFormatter = new DateTimeFormatterBuilder()
            .parseCaseInsensitive() // Accept all of am, AM, aM and Am
            .appendPattern("h:m:sa")
            .toFormatter(Locale.US);
    ZoneId zone = ZoneId.of("America/New_York");
    
    LocalDate date = LocalDate.of(1900, Month.JANUARY, 29);
    int hours = 7;
    int minutes = 1;
    int seconds = 1;
    String amPm = "AM";
    
    String constructedTimeString
            = "" + hours + ':' + minutes + ':' + seconds + amPm;
    LocalTime time = LocalTime.parse(constructedTimeString, timeFormatter);

    Instant instant = date.atTime(time).atZone(zone).toInstant();
    
    System.out.println(instant);

Output is:

1900-01-29T12:01:01Z

Geeky section: avoiding formatting time into a string and parsing it back

I couldn’t help thinking about whether it would be possible to have java.time parse the AM/PM string without having to construct a string for the time of day and parse it. It is possible, but we need to use the low-level TemporalAccessor interface, which is otherwise usually unnecessary.

    DateTimeFormatter amPmFormatter = new DateTimeFormatterBuilder()
            .parseCaseInsensitive() // Accept all of am, AM, aM and Am
            .appendPattern("a")
            .toFormatter(Locale.US);

    int hours = 7;
    int minutes = 1;
    int seconds = 1;
    String amPm = "AM";
    
    TemporalAccessor parsedAmPm = amPmFormatter.parse(amPm);
    LocalTime time = LocalTime.of(0, minutes, seconds)
            .with(ChronoField.AMPM_OF_DAY, parsedAmPm.get(ChronoField.AMPM_OF_DAY))
            .with(ChronoField.CLOCK_HOUR_OF_AMPM, hours);

    System.out.println(time);

07:01:01

Construction of the Instant proceeds as before.