Converting string to 'Instant'
Asked Answered
H

5

61

I am trying to convert datetime in a string to an Instant instance using Java 8 or a utilities package.

For example,

String requestTime = "04:30 PM, Sat 5/12/2018";

to

Instant reqInstant should result in 2018-05-12T20:30:00.000Z

reqString is in the America/Toronto time zone.

This is what I tried

 String strReqDelTime = "04:30 PM, Sat 5/12/2018";
 Date date = new SimpleDateFormat("hh:mm a, EEE MM/dd/yyyy").parse(requestTime);
 Instant reqInstant = date.toInstant();

The above code results in "2018-05-12T23:30:00Z".

How can I do it?

Holy answered 11/5, 2018 at 20:15 Comment(8)
you can try to call sdf.setTimeZone("your TZ")Manta
@DanilaZharenkov I tried that but it gives "2018-05-12T16:30:00Z"Holy
Instant Is not a String and not have a format, can you describe what you want exactly?Wakefield
sorry I edited my question. I am expecting Instant to result in 2018-05-12T20:30:00.000Z for "04:30 PM, Sat 5/12/2018" which in America/Toronto timezoneHoly
@Venky can you try this Instant instance = LocalDateTime.parse( requestTime, DateTimeFormatter.ofPattern("hh:mm a, EEE MM/dd/yyyy") ).toInstant(ZoneOffset.of("America/Toronto"));Wakefield
@YCF_L That results in exception java.time.format.DateTimeParseException: Text '04:30 PM, Sat 5/12/2018' could not be parsed at index 14Holy
No need to mix the modern classes (Instant) with the troubled legacy classes (Date & SimpleDateFormat). Use the java.time classes only; they entirely supplant the old ones.Strasbourg
Your Question is not clear. Edit out your particular variable names, make them input, result and such to ease our reading. "reqString"??? And clarify exactly what are the inputs, outputs, and unmet expectations.Strasbourg
S
121

tl;dr

  • Fix your formatting pattern for unpadded month and day.
  • Use only java.time classes, never the legacy classes.

Contrived example:

LocalDateTime.parse(                   // Parse as an indeterminate `LocalDate`, devoid of time zone or offset-from-UTC. NOT a moment, NOT a point on the timeline.
    "04:30 PM, Sat 5/12/2018" ,        // This input uses a poor choice of format. Whenever possible, use standard ISO 8601 formats when exchanging date-time values as text. Conveniently, the java.time classes use the standard formats by default when parsing/generating strings.
    DateTimeFormatter.ofPattern("hh:mm a, EEE M/d/uuuu", Locale.US)  // Use single-character `M` & `d` when the number lacks a leading padded zero for single-digit values.
)                                      // Returns a `LocalDateTime` object.
.atZone(                               // Apply a zone to that unzoned `LocalDateTime`, giving it meaning, determining a point on the timeline.
    ZoneId.of("America/Toronto")       // Always specify a proper time zone with `Contintent/Region` format, never a 3-4 letter pseudo-zone such as `PST`, `CST`, or `IST`.
)                                      // Returns a `ZonedDateTime`. `toString` → 2018-05-12T16:30-04:00[America/Toronto].
.toInstant()                           // Extract a `Instant` object, always in UTC by definition.
.toString()                            // Generate a String in standard ISO 8601 format representing the value within this `Instant` object. Note that this string is *generated*, not *contained*.

2018-05-12T20:30:00Z

Use single-digit formatting pattern

You used MM in your formatting pattern, to mean any single-digit value (months January-September) will appear with a padded leading zero.

But your input lacks that padded leading zero. So use a single M.

Ditto for day-of-month I expect: d rather than dd.

Use only java.time

You are using troublesome flawed old date-time classes (Date & SimpleDateFormat) that were supplanted years ago by the java.time classes. The new classes entirely supplant the old. There isn't any need to mix the legacy and modern.

LocalDateTime

Parse as a LocalDateTime because your input string lacks any indicator of time zone or offset-from-UTC. Such a value is not a moment, and it is not a point on the timeline. It is only a set of potential moments along a range of about 26-27 hours.

String input = "04:30 PM, Sat 5/12/2018";
DateTimeFormatter f = DateTimeFormatter.ofPattern("hh:mm a, EEE M/d/uuuu", Locale.US);  // Specify locale to determine human language and cultural norms used in translating that input string.
LocalDateTime ldt = LocalDateTime.parse(input, f);

ldt.toString(): 2018-05-12T16:30

ZonedDateTime

If you know for certain that input was intended to represent a moment using the wall-clock time used by the people of the Toronto Canada region, apply a ZoneId to get a ZonedDateTime object.

Assigning a time zone gives meaning to your unzoned LocalDateTime. Now we have a moment, a point on the timeline.

ZoneId z = ZoneId.of("America/Toronto");
ZonedDateTime zdt = ldt.atZone(z);  // Give meaning to that `LocalDateTime` by assigning the context of a particular time zone. Now we have a moment, a point on the timeline.

zdt.toString(): 2018-05-12T16:30-04:00[America/Toronto]

Instant

To see that same moment as UTC, extract an Instant. Same moment, different wall-clock time.

Instant instant = zdt.toInstant();

instant.toString(): 2018-05-12T20:30:00Z


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. There isn't any need for strings or for java.sql.* classes.

Where can we obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

Strasbourg answered 11/5, 2018 at 21:35 Comment(4)
Thank you for to the point explanation!! that solves my problem.Holy
Very extensive and good answer, thank you very much! Could you comment on the use of the Locale in DateTimeFormatter.ofPattern( "hh:mm a, EEE M/d/uuuu" , Locale.US ), please? The doc says that "The locale affects some aspects of formatting and parsing. For example, the ofLocalizedDate provides a formatter that uses the locale specific date format." I don't really get how it affects parsing as the parsing is determined by the provided pattern...? How does a change to the locale (implicitly) affect the prescribed parsing pattern?Hall
@JanusVarmarken The Locale determines the human language and cultural norms used in localizing, for translating the name of the day of the week, for example, and for deciding issues such as commas versus periods, capitalization, abbreviations, order of elements, and so on.Strasbourg
There is a problem with LocalDateTime.parse() - if your pattern doesn't contain time, parse() will throw an exception, so you have to add two try blocks like (pseudocode): fun parse(): Instant? = try { LocalDateTime.parse() } catch { try { LocalDate.parse() } catch { null } }Bavardage
K
2

It seems like the time zone on your computer (server) is US Pacific DST (PDT, GMT-7), but you expect to have the result for US Eastern DST (EDT, GMT-4).

Instant.toString() returns UTC (GMT+0) DateTime in ISO 8601 format. ('Z' at the end means UTC).

SimpleDateFormat treats DateTime String in the default time zone of the computer when it is not specified. And your input does not specify a time zone.

So, you need to do something about in what time zone your input is.

PS.: On my machine in Eastern DST, your code gives me the result exactly as you expected.

Karol answered 11/5, 2018 at 21:51 Comment(1)
Correct answer.Catania
R
1

What went wrong in your attempt?

The SimpleDateFormat uses the system's time-zone by default while you have mentioned that requestTime is in America/Toronto timezone. You should never rely on the default time-zone because when your code will be run on a machine in different time-zone your application may behave in an unexpected manner.

How should you have done it?

Set the time-zone to America/Toronto before parsing. Also, Never use Date-Time formatting/parsing API without a Locale.

Demo:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

public class Main {
    public static void main(String[] args) throws ParseException {
        String requestTime = "04:30 PM, Sat 5/12/2018";
        SimpleDateFormat sdf = new SimpleDateFormat("hh:mm a, EEE MM/dd/yyyy", Locale.ENGLISH);
        sdf.setTimeZone(TimeZone.getTimeZone("America/Toronto"));
        Date date = sdf.parse(requestTime);
        Instant reqInstant = date.toInstant();
        System.out.println(reqInstant);
    }
}

Output:

2018-05-12T20:30:00Z

Do not pollute the clean java.time API with the error-prone java.util API

The java.time API introduced with Java-8 (March 2014) supplants the error-prone and outdated java.util and their formatting API, SimpleDateFormat. It is recommended to stop using the legacy date-time API and switch to the modern date-time API. You tried parsing the date-time string using the legacy API and then switching to the modern API using Date#toInstant while you could have done everything using the modern API.

You should use Date#toInstant to switch to the modern API if you are using an old code/library that uses java.util.Date.

Solution using the modern date-time API

Parse the date-time string to LocalDateTime as it does not have time-zone → Convert the obtained LocalDateTime into ZonedDateTime of the given time-zone → Convert the obtained ZonedDateTime into Instant.

Demo:

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        String requestTime = "04:30 PM, Sat 5/12/2018";
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("h:m a, EEE M/d/u", Locale.ENGLISH);

        // Parse the date-time string to LocalDateTime as it does not have time-zone
        LocalDateTime ldt = LocalDateTime.parse(requestTime, dtf);

        // Convert the LocalDateTime into ZonedDateTime of the given time-zone
        ZonedDateTime zdt = ldt.atZone(ZoneId.of("America/Toronto"));

        // Convert the ZonedDateTime into Instant
        Instant instant = zdt.toInstant();
        System.out.println(instant);
    }
}

Output:

2018-05-12T20:30:00Z

Note that I prefer u to y with DateTimeFormatter.

An alternative solution using the modern date-time API

You can convert the obtained LocalDateTime directly into an Instant using LocalDateTime#toInstant by supplying it with the time-zone ID.

Instant instant = ldt.toInstant(ZoneId.of("America/Toronto").getRules().getOffset(ldt));

Learn more about the the modern date-time API from Trail: Date Time.

Ronna answered 14/10, 2022 at 19:0 Comment(0)
N
0

For the description, you can read Convert String to Date in Java.

String requestTime = "04:30 PM, Sat 5/12/2018 America/Toronto";

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("hh:mm a, EEE M/dd/yyyy z");
ZonedDateTime zonedDateTime = ZonedDateTime.parse(requestTime, formatter);
System.out.println(zonedDateTime.toInstant());
Nightstick answered 18/11, 2021 at 10:5 Comment(1)
It works correctly on my Java 17 when running in an English-speaking locale. For it to work in other locales, we need to specify locale in the formatter. In any case it’s much better than your previous (now deleted) answer.Catania
P
0

Instant.parse(String) appropriately formatted

Pinhead answered 14/1, 2022 at 10:6 Comment(3)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Endo
Please tell us what 'appropriately formatted' means, this answer is close to useless.Somber
The most useful answer among others. It could be used like that: Instant.parse("2023-12-15T14:16:43.12Z");. Standard ISO-8601 format, check .parse() method Javadoc for more detailsHobby

© 2022 - 2024 — McMap. All rights reserved.