1

I need to parse a UTC date and time string, e.g. 20180531_132001Z into a Java 8 date and time object. How do I go about doing this using Java 8's new date and time libraries? Most examples I see is for LocalDateTime, like this:

    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss'Z'");
    LocalDateTime localDateTime = LocalDateTime.parse("20180531_132001Z", formatter);
    System.out.println(localDateTime);
    System.out.println(localDateTime.atOffset(ZoneOffset.UTC));

The code outputs:

2018-05-31T13:20:01
2018-05-31T13:20:01Z
  • Is this considered local time or UTC time? The string value I am parsing is based on UTC, so I am wondering if I need to do anything further before persisting to the database.
  • If the former, how do I convert that to UTC date and time?

I ultimately need to persist this to a SQL Server database table (column type is [datetime2](7), using [Spring] JDBC.

Update: Based on the comments and answers, I think my question is not well thought out. Putting it another way, if I get an input string and I parse it without factoring any zone or offset, I will get a LocalDateTime object. How do I take that object and convert the encapsulated value to UTC date and time?

Web User
  • 7,438
  • 14
  • 64
  • 92

5 Answers5

9

LocalDateTime can be misleading. It doesn't represent your local date/time, it represents a local date/time. It carries no time zone info at all. That is, it just says for example "it's 13:20". It doesn't say where it's 13:20. It's up to you to interpret the where part.

Due to this LocalDateTime is usually not very useful for carrying timestamps, it's only useful for situations when the timezone is dependent on some context.1

When working with timestamps it's better to use ZonedDateTime or OffsetDateTime instead. These carry the date, time and offset.

So localDateTime.atOffset(ZoneOffset.UTC) will actually return an instance of OffsetDateTime, by interpreting localDateTime as UTC time.

One could argue that you can avoid the interpreting part by parsing with the timezone info in the first place (even though it's always Z):

    String example = "20180531_132001Z";
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmssX");
    OffsetDateTime dateTime = OffsetDateTime.parse(example, formatter);
    System.out.println(dateTime); // look ma, no hardcoded UTC

Will print:

2018-05-31T13:20:01Z

The added value is that your code automatically supports timezones (e.g. "20180531_132001+05").

JDBC 4.2 compliant driver may be able to directly address java.time types by calling setObject.

For older JDBC drivers you can convert dateTime to a java.sql.Timestamp or java.util.Date:

java.sql.Timestamp.from(dateTime.toInstant());
java.util.Date.from(dateTime.toInstant());

1 There is almost always some context in which LocalDateTime operates. For example "Flight KL1302 arrives at airport X tomorrow at 13:20". Here the context of "tomorrow at 13:20" is the local time at airport X; it can be determined by looking up the time zone of X.

rustyx
  • 80,671
  • 25
  • 200
  • 267
  • 1
    Correct Answer, but the “more useful for printing the date/time to the user” part is misleading. If you want to generate a String representing a ZonedDateTime while omitting the offset/zone information, use a `DateTimeFormatter` for that. No need to create a `LocalDateTime` object for that purpose alone. Doing so makes your code harder to read, as that is not at all the intended purpose of `LocalDateTime`. – Basil Bourque Jun 02 '18 at 02:33
4

tl;dr

myPreparedStatement.setObject(   // Pass java.time objects directly to database, as of JDBC 4.2.
    … ,                          // Indicate which placeholder in your SQL statement text.
    OffsetDateTime.parse(        // Parse input string as a `OffsetDateTime` as it indicates an offset-from-UTC but not a time zone.
        "20180531_132001Z" ,     // Define a formatting pattern to match your particular input.
        DateTimeFormatter.ofPattern( "uuuuMMdd_HHmmssX" )  // TIP: When exchanging date-time values as text, use use standard ISO 8601 formats rather than inventing your own.
    )                            // Returns a `OffsetDateTime` object.
    .toInstant()                 // Returns a `Instant` object, always in UTC by definition.
)

Details

There is some helpful information in the other Answers, but all of them have some misinformation which I tried to correct by posting comments.

Most importantly, your code is using the wrong Java class and the wrong database data type for that given input.

Below is explanation along with a complete code example, using the modern java.time classes with JDBC 4.2 or later.

Z = UTC

DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss'Z'")

Never put single-quotes around vital parts of your input such as you did here with Z. That Z means UTC and is pronounced “Zulu”. It tells us the text of the date and time-of-day should be interpreted as using the wall-clock time of UTC rather than, say, America/Montreal or Pacific/Auckland time zones.

Do not use the LocalDateTime for such inputs. That class lacks any concept of time zone or offset-from-UTC. As such, this class does not represent a moment, and is not a point on the timeline. A LocalDateTime represents the set of potential moments along a range of about 26-27 hours (across all time zones). Use LocalDateTime when you mean any or all time zones rather than one particular zone/offset. In contrast, the Z tells us this input uses the wall-clock time of UTC specifically.

Parsing

Define a formatting pattern to match all important parts of your input string.

String input = "20180531_132001Z" ;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "uuuuMMdd_HHmmssX" ) ;

By the way, whenever possible, use standard ISO 8601 formats rather than a custom format as seen in your Question. Those formats are wisely designed to be easy to parse by machine and easy to read by humans across cultures while eliminating ambiguity.

Parse as a OffsetDateTime because your input indicates an offset-from-UTC (of zero hours). An offset-from-UTC is merely a number of hours and minutes, nothing more, nothing less.

Use the ZonedDateTime class only if the input string indicates a time zone. A time zone has a Contintent/Region name such as Africa/Tunis. A zone represents the history of past, present, and future changes in the offset used by the people of a particular region.

OffsetDateTime odt = OffsetDateTime.parse( input , f ) ;

odt.toString(): 2018-05-31T13:20:01Z

Database

To communicate this moment to a database using JDBC 4.2 and later, we can directly pass the java.time object.

myPreparedStatement.setObject( … , odt ) ;

If your JDBC driver does not accept the OffsetDateTime, extract the simpler class Instant. An Instant is in UTC always, by definition.

Instant instant = odt.toInstant() ;
myPreparedStatement.setObject( … , instant ) ;

And retrieval.

Instant instant = myResultSet.getObject( … , Instant.class ) ;

Beware - Wrong datatype in your database

I am not a MS SQL Server user, but according to this documentation, the column data type DATETIME2 is not appropriate to your input. That data type seems to be equivalent to the SQL-standard type DATETIME WITHOUT TIME ZONE. Such a type should never be used when recording a specific moment in history.

Lacking any concept of time zone or offset-from-UTC, that column type should only be used for three situations:

  • The zone or offset is unknown.
    This is bad. This is faulty data. Analogous to having a price/cost without knowing the currency. You should be rejecting such data, not storing it.
  • The intention is “everywhere”, as in, every time zone.
    Example, a corporate policy that states “All our factories will break for lunch at 12:30" means the factory in Delhi will break hours before the factory in Düsseldorf which breaks hours before the factory in Detroit.
  • A specific moment in the future is intended, but we are afraid of politicians redefining the time zone.
    Governments change the rules of their time zones with surprising frequency and with surprisingly little warning (even [no warning at all][10]). So if you want to book an appointment at 3 PM on a certain date, and you really mean 3 PM regardless of any crazy decision a government might make in the interim, then store a LocalDateTime. To print a report or display a calendar, dynamically apply a time zone (ZoneId) to generate a specific moment (ZonedDateTime or Instant). This must be done on-the-fly rather than storing the value.

Since your input is a specific moment, a certain point on the timeline, you should be storing it in the database using a column type akin to the SQL-standard type TIMESTAMP WITH TIME ZONE.


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. No need for strings, no need for java.sql.* classes.

Where to 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.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • Wow... I can save a couple days of searching online and in books just by going through the information packed in your answer. Totally worth the read, maybe tl;dr for others but not to me. Thanks!! – Web User Jun 02 '18 at 03:36
  • Hello, I am parsing with `OffsetDateTime` and using the same data type in my POJO. I am using `datetimeoffset(7)` as the database column type. I am still some troubles which are implementation specific and it made sense to ask a [separate question](https://stackoverflow.com/questions/50693048/unable-to-insert-datetimeoffset-value-using-spring-boot-on-sql-server-2016) on SO. – Web User Jun 05 '18 at 15:53
0

this should do the trick to parse string to LocalDateTime :

    String example = "20180531_132001Z";
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmssX");
    ZonedDateTime dateTime = ZonedDateTime.parse(example, formatter);

See that code run live in IdeOne.com.

dateTime.toString(): 2018-05-31T13:20:01Z

    Timestamp timestamp = Timestamp.from(dateTime.toInstant());

Timestamp then is saved into db

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Szychan
  • 169
  • 1
  • 8
  • This is a wrong answer. It is throwing `Text '20180531_132001Z' could not be parsed at index 15` – Gatusko Jun 01 '18 at 19:19
  • Correct date format string (based on example code in my question is `"yyyyMMdd_HHmmss'Z'"`. That works for me. You need to single-quote the "Z". – Web User Jun 01 '18 at 19:25
  • edited answer. Z in example string means zero so X pattern should do the trick ;) – Szychan Jun 01 '18 at 19:31
  • This is almost a correct solution, only you should use `ZonedDateTime` or `OffsetDateTime` instead of `LocalDateTime` when parsing a timezone. – rustyx Jun 01 '18 at 19:41
  • from what I understood Z in string means zero offset so no offset at all . so then when putting it in db sql timestamp. it would look like Timestamp timestamp = Timestamp.valueOf(dateTime); – Szychan Jun 01 '18 at 19:47
  • But if you parse `20180531_132001+01` the result will be wrong. – rustyx Jun 01 '18 at 19:49
  • agreed. didnt take into consideration other strings that are applied to my pattern – Szychan Jun 01 '18 at 19:54
  • 1
    @WebUser Wrong advice - Putting single-quote marks around the `Z` means to ignore that input entirely. That would be ignoring vital information, the fact that this date-time uses the wall-clock time of UTC. [The `Z` means UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time#Time_zones), and is pronounced “Zulu”, also known as “Zulu" time. – Basil Bourque Jun 02 '18 at 02:40
  • 1
    Good Answer, except no need to ever use `java.sql.Timestamp` again. As of JDBC 4.2, we can directly exchange *java.time* types with a database. `myPreparedStatement.setObject( … , dateTime.toInstant() ) ;` – Basil Bourque Jun 02 '18 at 02:44
  • @BasilBourque thanks for correcting me and providing the clarification on use of `Z` as well as using `java.time` with a database. I am using MS SQL JDBC drivers 6.x which should be JDBC 4.2 compliant. – Web User Jun 02 '18 at 03:27
0

Maybe this can help you.

public static void main(String... strings) {
   OffsetDateTime utc = OffsetDateTime.now(ZoneOffset.UTC);
   System.out.println(utc.toString());
   DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy mm dd  hh:mm a");
   System.out.println(utc.format(format));
}
RodolfoTVA
  • 77
  • 9
0

While you certainly can use LocalDateTime and format it to look like a zoned date time using offset, it would be easier to use an Object designed to store time zone.

ZonedDateTime zonedDateTime = ZonedDateTime.parse("20180531_132001Z", DateTimeFormatter.ofPattern("yyyMMdd_HHmmssX"));

This gives you the option to use Instant to convert to SQL timestamp or any other format without having to hard-code the time zone, especially if time zone is added in the future or changes.

java.sql.Timestamp timestamp = new java.sql.Timestamp(zonedDateTime.toInstant().toEpochMilli());

You can view the timestamp's instant and compare it to the toString, which should be pegged to your timezone, and instant.toString, which pegs to UTC.

System.out.print(timestamp + " " + timestamp.toInstant().toString());
Compass
  • 5,867
  • 4
  • 30
  • 42
  • The `java.sql.Timestamp` class is badly-designed, troublesome, and now “legacy”. Avoid it. No need to use that class ever again as of JDBC 4.2 and later. The generate a `String` representing a `ZonedDateTime`, use a [`DateTimeFormatter`](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html?is-external=true) – that is its very purpose. – Basil Bourque Jun 02 '18 at 02:36
  • `ZonedDateTime` is not the appropriate class here, as the input lacks a time zone. For an input indicating only an offset-from-UTC (or UTC itself) rather than a full time zone, use `OffsetDateTime` or `Instant` classes. – Basil Bourque Jun 02 '18 at 03:31