0

I have the following date

2017-08-23-11.19.02.234850

it has the following date format

yyyy-MM-dd-HH.mm.ss.SSSSSS

What I want to do is to convert the date to format yyyy-MM-dd'T'HH:mm:ss.SSSSSS

I have the following code

    public static void main(String[] args) {
        String strDate = "2017-08-23-11.19.02.234850";
        String dateFmt = "yyyy-MM-dd-HH.mm.ss.SSSSSS";

        System.out.println("converted Date: " + convertDate(strDate, dateFmt));
    }

    public static String convertDate(String strDate, String format) {
        SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.US);
        sdf.setLenient(true);

        try {
            Date dateIn = sdf.parse(strDate);
            return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS").format(dateIn);

        }catch(ParseException e) {
            e.printStackTrace();
        }

        return "";
    }

the result is

converted Date: 2017-08-23T11:22:56.000850

input date 2017-08-23-11.19.02.234850 converted date 2017-08-23T11:22:56.000850 doesn't look the same, it seems java is rounding the milliseconds besides if I turn lenient off for date validation

sdf.setLenient(false);

I get the following

java.text.ParseException: Unparseable date: "2017-08-23-11.19.02.234850"
    at java.text.DateFormat.parse(Unknown Source)
    at mx.santander.canonical.datamodel.enums.Main.convertDate(Main.java:74)
    at mx.santander.canonical.datamodel.enums.Main.main(Main.java:66)
converted Date:

How to build a function which validates and converts date strings like this in a proper way?


EDIT:

I added a new function to obtain results

/**
      * Gets the ISO 8601 date str from string.
      *
      * @param strDate the str date
      * @return the ISO 8601 date str from string
      */
     private String getISO8601DateStrFromString (String strDate)  {
        String responseISO8601Date = "";
        if(strDate == null || "".equals(strDate.trim())) {
            return responseISO8601Date;
        }
         try {
             String strDtWithoutNanoSec = strDate.substring(0, strDate.lastIndexOf("."));
             String strDtNanoSec        = strDate.substring(strDate.lastIndexOf(".") + 1, strDate.length());

             SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH.mm.ss");
             formatter.setLenient(false);

             Date   date = formatter.parse(strDtWithoutNanoSec);
             Timestamp t = new Timestamp(date.getTime());
             t.setNanos(Integer.parseInt(strDtNanoSec));

             DateFormat   df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'.'");
             NumberFormat nf = new DecimalFormat("000000");

             responseISO8601Date = df.format(t.getTime()) + nf.format(t.getNanos());

         } catch (ParseException | StringIndexOutOfBoundsException | NumberFormatException e) {
             String errorMsg = String.format("The date provided for conversion to ISO 8601 format [%s] is not correct", strDate);
             System.out.println(errorMsg);
         } 
         return responseISO8601Date;
     }

What I get:

Uptadet date 2017-12-20T11:19:02.234850

Jesús Ayala
  • 2,743
  • 3
  • 32
  • 48
  • 2
    `.SSS` is the maximum for milliseconds, the range is `0` to `999` – Elliott Frisch Aug 30 '17 at 03:37
  • Good read https://stackoverflow.com/questions/1712205/current-time-in-microseconds-in-java.. Do checkout the answer by @Basil Bourque on how Java 9 has fixed its implementation. – Naman Aug 30 '17 at 03:57
  • You are using troublesome old date-time classes that are now legacy, supplanted by the java.time classes. – Basil Bourque Aug 30 '17 at 05:32

4 Answers4

4

As others have already mentioned, your requirement does not fit the use of Date and SimpleDateFormat since these only support milliseconds, that is, three decimals on the seconds, where you have six decimals (microseconds). So we need to find some other way. This is basically a good idea anyway, since Date and SimpleDateFormat are long outdated, and today we have better tools for the job.

I have got two suggestions for you.

java.time

Even in Java 7 I think that it’s a good idea to use the modern Java date and time API that came out with Java 8, AKA JSR-310. Can you do that? Certainly; use the backport for Java 6 and 7, ThreeTen Backport. The modern API supports anything from 0 through 9 decimals on the seconds, and the code is straightforward when you know how:

private static DateTimeFormatter inputParser 
        = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH.mm.ss.SSSSSS");
private static DateTimeFormatter outputFormatter 
        = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS");

public static String convertDate(String strDate) {
    return LocalDateTime.parse(strDate, inputParser)
            .format(outputFormatter);
}

I am using your own two format pattern strings. convertDate("2017-08-23-11.19.02.234850") returns 2017-08-23T11:19:02.234850.

There is a simplification possible: Since the format you want to obtain, conforms with ISO 8601, you don’t need an explicit formatter for it. The modern classes understand and produce ISO 8601 formats natively, so you may use:

    return LocalDateTime.parse(strDate, inputParser).toString();

However, if the decimals on the seconds happened to end in 000, this will not print the last three zeroes. So if six decimals are required even in this case, use the formatter.

Regular expression

If you don’t want to rely on an external library, even temporarily until once you upgrade to Java 8 (or 9), your job can be done with a regular expression:

    return strDate
            .replaceFirst("^(\\d{4}-\\d{2}-\\d{2})-(\\d{2})\\.(\\d{2})\\.(\\d{2}\\.\\d{6})$",
                    "$1T$2:$3:$4");

It’s less elegant and harder to read, and it doesn’t offer the level of input validation you get from using a proper date and time API. Other than that, it’s a way through.

java.sql.Timestamp?

As others have said, java.sql.Timestamp offers nanosecond precision and thus will hold your date-time. Parsing your string into a Timestamp isn’t straightforward, though, so I don’t think it’s worth the trouble. Usagi Miyanmoto correctly identifies Timestamp.valueOf() as the method to use, but before you could do that, you would have change the format, so you would end up changing the format twice instead of just once. Or maybe three times since Timestamp also doesn’t produce your desired ISO 8601 format readily. Additionally you would need to decide a time zone for the timestamp, but I assume you could do that without any trouble.

If you needed to keep the the date-time around, a Timestamp object might be worth considering, but again, it’s a long outdated class. In any case, for reformatting alone, I certainly would not use it.

What happened in your code?

SimpleDateFormat understood 234850 as milliseconds, that is, 234 seconds 850 milliseconds. So it added 234 seconds to your time, 11:19:02. And then printed the remaining 850 milliseconds in 6 decimal places as you had requested.

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
2

Date has precision only till milli seconds. Please use timestamp instead - it has precision till nano seconds, which is expected in your case.

Please refer this answer - precision till nano seconds

TimeStamp API

A thin wrapper around java.util.Date that allows the JDBC API to identify this as an SQL TIMESTAMP value. It adds the ability to hold the SQL TIMESTAMP fractional seconds value, by allowing the specification of fractional seconds to a precision of nanoseconds. A Timestamp also provides formatting and parsing operations to support the JDBC escape syntax for timestamp values.

Srikanth Balaji
  • 2,638
  • 4
  • 18
  • 31
1

SimpleDateFormat of Java does not support microsecond in pattern.

java.util.Date format SSSSSS: if not microseconds what are the last 3 digits?

You have several choices:

  1. Manually handle the parsing and formatting of the microseconds
  2. Switch to use Java 8 as Time API supports fraction of second in pattern (https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html)
  3. If you need to use Java 7, consider using JODA Time for your date-time logics. JODA support fraction of second in its DateTimeFormat (http://joda-time.sourceforge.net/apidocs/org/joda/time/format/DateTimeFormat.html)
Adrian Shum
  • 38,812
  • 10
  • 83
  • 131
0

That result you got is expected. In your format string S were used. S is for milliseconds, hat is thousandths of seconds, and in this case the number of S's does not matter for parsing.
Your input string ends with 11.19.02.234850, the last part is interpreted as an integer value, and added to the date and time as milliseconds. That is as 234.850 seconds. Now, if you add 234 secs to 11:19:02, it becomes 11:22:56, just as you got in the result...

You cannot make a SimpleDateFormat mask that can parse microseconds into a Date, and Date cannot hold microseconds value either.

You have to choose, whether you want to use Date, or really need the finer then milliseconds resolution?

If you stick with Date, you should truncate the string of the last 3 characters.

Or you could use java.sql.Timestamp, which has a valueOf() method, hat uses SQL timestamp format.
Unfortunately it is not exactly he same as yours (being yyyy-[m]m-[d]d hh:mm:ss[.f...])...
Another way could be to split the string by separators (like [-.]), parse them to integers, and use hese integers with the Timestamp() constructor...

Usagi Miyamoto
  • 6,196
  • 1
  • 19
  • 33
  • That `Timestamp` constructor is deprecated. And deprecated for a reason, it is unreliable across time zones. So I wouldn’t want to go that way. – Ole V.V. Aug 30 '17 at 10:56