Daniel Widdis Daniel Widdis - 2 years ago 280
Java Question

Parse CIM_DateTime with milliseconds to Java Date

I am trying to convert a DateTime value retrieved from Windows Management Interface into a Java (1.7) Date. The format is specified here.

An example that I am trying to parse is

20160513072950.782000-420
which is 2016-05-13 at 07:29:50 plus 782 milliseconds, in my local timezone (-420 minutes = UTC-7 hours). The digits after the decimal are fractional seconds; in theory up to 6 digits of microseconds, but in practice only the first 4 digits are nonzero.

I initially attempted to parse using a SimpleDateFormat specifying the three digits of milliseconds that I wanted to parse:

SimpleDateFormat cimDateFormat = new SimpleDateFormat("yyyyMMddHHmmss.SSS");
Date date = cimDateFormat.parse(s, new ParsePosition(0));


My reasoning was that specifying the three digits of milliseconds with
SSS
would stop the parsing. Unfortunately, this didn't work; many more than 782 milliseconds in the above example were added.

I eventually got it to work as desired by trimming the string to the required characters:

SimpleDateFormat cimDateFormat = new SimpleDateFormat("yyyyMMddHHmmss.S");
Date date = cimDateFormat.parse(s.substring(0, 18), new ParsePosition(0));


In this case, I only included one
S
for milliseconds but it parsed all three.

I can't find anything in the
SimpleDateFormat
javadoc that clearly explains what's going on at the end of this parsing. Specific questions:


  1. Why does it keep parsing past the specified number of digits in the
    SSS
    case?

  2. Why does a single
    S
    parse all 3 millisecond digits?

  3. Other than truncating the string like I did, is there any other way of telling the
    SimpleDateFormat
    to stop parsing the string at the indicated position?


Answer Source

Modify the input

As far as I know, none of the three common date-time frameworks for Java (the old bundled java.util.Date/.Calendar/java.text.SimpleDateFormat classes, the Joda-Time framework, or the java.time framework built into Java 8 and later) allow for an offset-from-UTC as a total number of minutes.

As suggested by Sotirios Delimanolis, you must modify the offset-from-UTC to convert from a number of total minutes to the standard number of hours and minutes (and seconds – a possibility ignored by that odd Microsoft format). So -420 should become -07:00 or -07:00:00.

java.time

You are using the troublesome old date-time classes bundled with the earliest versions of Java. The old classes are now legacy, and have been supplanted by the java.time framework built into Java 8 and later, and largely back-ported to Java 6 & 7 and further adapted to Android.

The java.time classes have a resolution of nanoseconds, for up to nine digits of a decimal fraction of second. So no problem handling your inputs 4-6 digits of fractional second.

Our strategy has two parts: (a) Modify the input to convert that offset-from-UTC, and (b) Parse the modified input string as a date-time object.

Modify input

First we change the input from 20160513072950.782000-420 to 20160513072950.782000-07:00:00. We do this by extracting the characters trailing after the + or -, the 420 in this case.

// Modify the input to replace offset as a number of minutes to the standard format, a number of hours, minutes, and seconds.
String input = "20160513072950.782000-420";
String offsetInMinutesAsString = input.substring ( 22 );

Convert that to a long, and create a LocalTime object so that we can generate a string in the format of HH:mm:ss.

long offsetInMinutes = Long.parseLong ( offsetInMinutesAsString );
LocalTime offsetAsLocalTime = LocalTime.MIN.plusMinutes ( offsetInMinutes );
String offsetAsString = offsetAsLocalTime.format ( DateTimeFormatter.ISO_LOCAL_TIME );

Replace those trailing characters with our generated string.

String inputModified = ( input.substring ( 0 , 22 ) + offsetAsString );

Parse string to date-time object

Define a custom formatting pattern by which to parse that string into a OffsetDateTime object.

// Parse the modified input as an OffsetDateTime.
DateTimeFormatter formatter = DateTimeFormatter.ofPattern ( "yyyyMMddHHmmss.SSSSSSZZZZZ" , Locale.US );
OffsetDateTime odt = OffsetDateTime.parse ( inputModified , formatter );

Dump to console.

System.out.println ( "input: " + input + " | inputModified: " + inputModified + " | odt: " + odt );

input: 20160513072950.782000-420 | inputModified: 20160513072950.782000-07:00:00 | odt: 2016-05-13T07:29:50.782-07:00

Convert

I strongly suggest avoiding the old date-time classes. But if you must use a java.util.Date object to interoperate with old date-time code, you can convert.

Look for new methods added to the old classes for conversion. For this conversion we use java.util.Date.from. We need to feed that conversion method a Instant object, a moment on the timeline in UTC with a resolution of nanoseconds. We can extract one from our OffsetDateTime.

Instant instant = odt.toInstant();
java.util.Date utilDate = java.util.Date.from( instant );

For more info about converting, including a nifty diagram, see my Answer to another Question. Keep in mind that we are working with only a mere offset-from-UTC in our input strings and our OffsetDateTime, not a full time zone. A time zone is an offset plus rules for handling anomalies such as Daylight Saving Time (DST). Both the Instant and the java.util.Date are in UTC (an offset of zero).

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download