10
\$\begingroup\$

My requirement is to convert a time (long) to a String with this date format:

yyyy-MM-ddThh:mm:ss.SSS

For instance: 2017-03-07T03:52:31.298

My implementation:

private final static String DATE_FORMAT = "yyyy-MM-dd_hh:mm:ss.SSS";

private final static DateFormat DF =
        new SimpleDateFormat(DATE_FORMAT, Locale.ENGLISH);

public static String formatTime(long time) {
    return DF.format(time).replace('_', 'T');
}

The underscore trick is to avoid the Illegal pattern character T error I was getting if I had a T character in DATE_FORMAT. I am sure there is a better way to do this?
Also, I am sure more explicit names can be found for DATE_FORMAT/DF?

\$\endgroup\$
  • \$\begingroup\$ for others: the format is the iso8601 one, that is HIGHLY recommended to use everywhere. Actually, it just lacks the timezone part. en.wikipedia.org/wiki/ISO_8601 . Java now includes good-efficiency builtins for that one. \$\endgroup\$ – Olivier Dulac Mar 7 '17 at 16:44
  • \$\begingroup\$ Please tell me you're not doing this for use in building SQL. Because that would be bad. Really bad. "I just found out my app was hacked six months ago" bad. \$\endgroup\$ – Joel Coehoorn Mar 8 '17 at 4:03
  • \$\begingroup\$ @JoelCoehoorn: Not SQL. I develop a web service that returns timestamped audit records. \$\endgroup\$ – Nicolas Raoul Mar 8 '17 at 4:42
16
\$\begingroup\$

Don't reinvent the wheel, this is java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME.

long time = System.currentTimeMillis();
ISO_LOCAL_DATE_TIME.format(Instant.ofEpochMilli(time).atZone(ZoneOffset.UTC))
// "2017-03-07T12:09:04.374"
\$\endgroup\$
  • \$\begingroup\$ the added bonus, iirc, is that "ISO_LOCAL_DATE_TIME" is quite more efficient than using a custom one. You may want to use instead ISO_OFFSET_DATE_TIME to also have the offset included ! \$\endgroup\$ – Olivier Dulac Mar 7 '17 at 16:46
  • \$\begingroup\$ In that case you're probably best off with Instant.ofEpochMilli(time).toString(). \$\endgroup\$ – OrangeDog Mar 7 '17 at 16:49
  • 1
    \$\begingroup\$ Somehow I get Cannot be resolved if I don't write DateTimeFormatter.ISO_LOCAL_DATE_TIME on that line. \$\endgroup\$ – Nicolas Raoul Mar 8 '17 at 2:01
  • 1
    \$\begingroup\$ @NicolasRaoul well it depends on your imports \$\endgroup\$ – OrangeDog Mar 8 '17 at 7:53
15
\$\begingroup\$

You can single-quote the 'T' to have it accepted as a String literal

private final static String DATE_FORMAT = "yyyy-MM-dd'T'hh:mm:ss.SSS";
\$\endgroup\$
  • \$\begingroup\$ Wonderful! As for the variable names, any advice maybe? \$\endgroup\$ – Nicolas Raoul Mar 7 '17 at 7:57
11
\$\begingroup\$

Possible bug

The formatter is using an incorrect identifier for the hours. hh means " Hour in am/pm (1-12)", so it works with 12h format and a AM/PM marker. Since the formatter does not print the AM/PM marker, this is most likely a bug as it will not differentiate morning from afternoon, and return the same formatted String for two different dates.

You either want to use HH instead, which is "Hour in day (0-23)", or print the AM/PM marker with a.

Thread safety

private final static DateFormat DF =
         new SimpleDateFormat(DATE_FORMAT, Locale.ENGLISH);

This is a source of problems, because SimpleDateFormat is not a thread-safe class:

It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.

With using a SimpleDateFormat as a constant, the same instance will be shared. If you happen to call the method formatTime from multiple threads at the same time, anything could happen; from an exception thrown, a wrong result, to, even worse, a right result sometimes. This code looks like it lives in a utility class, so all the more reasons it could be used concurrently.

The simple solution here is to let the method formatTime create the instance of SimpleDateFormat when it is called, with

public static String formatTime(long time) {
    return new SimpleDateFormat(DATE_FORMAT, Locale.ENGLISH).format(time);
}

If creating an instance of SimpleDateFormat each time the method is called turns out to be slow for your purposes, you could use a ThreadLocal, like shown on Stack Overflow.

A better solution if you're under Java 8 is to use DateTimeFormatter, which is a thread-safe class.

private final static DateTimeFormatter DF =
    DateTimeFormatter.ofPattern(DATE_FORMAT, Locale.ENGLISH);

public static String formatTime(long time) {
    Instant instant = Instant.ofEpochMilli(time);
    return DF.format(LocalDateTime.ofInstant(instant, ZoneId.systemDefault()));
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.