13
\$\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\$
3
  • \$\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\$ Commented Mar 7, 2017 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\$ Commented Mar 8, 2017 at 4:03
  • \$\begingroup\$ @JoelCoehoorn: Not SQL. I develop a web service that returns timestamped audit records. \$\endgroup\$ Commented Mar 8, 2017 at 4:42

3 Answers 3

17
\$\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\$
4
  • \$\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\$ Commented Mar 7, 2017 at 16:46
  • \$\begingroup\$ In that case you're probably best off with Instant.ofEpochMilli(time).toString(). \$\endgroup\$
    – OrangeDog
    Commented Mar 7, 2017 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\$ Commented Mar 8, 2017 at 2:01
  • 1
    \$\begingroup\$ @NicolasRaoul well it depends on your imports \$\endgroup\$
    – OrangeDog
    Commented Mar 8, 2017 at 7:53
18
\$\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\$
1
  • \$\begingroup\$ Wonderful! As for the variable names, any advice maybe? \$\endgroup\$ Commented Mar 7, 2017 at 7:57
12
\$\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 and acknowledge you have read our privacy policy.

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