0

I need to format the Java Date object into a String like yyyyMMdd (round to day). For e.g, 20180129. I have the following implementation:

SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
sdf.format(roundedDate);

The code works most the time, but sometimes it'll generate results like 2018129, which is not what I want. So I'll have both 20180129 and 2018129 in my database.

The app consumes messages from a MQ and ummarshalls the timestamp in the message into a Java Date object. And it formats the date into a the above String.

The issue is that I cannot reproduce the issue in debug mode. It always produces the expected results in the debugger. But after I ran it on a server (in Docker) for some time, I see such corrupted data.

I wonder why the SimpleDateFormat could have such undetermined behavior given a valid Date object? Any idea will be appreciated.

J Freebird
  • 3,664
  • 7
  • 46
  • 81
  • You cannot possibly get BOTH `20180129` _and_ `2018129` out of formats with patterns identical to what you've shown. Either you have threading issues or in one place you have a typo and the format is `yyyyMdd`. – Jim Garrison Feb 01 '18 at 05:51
  • 1
    @JimGarrison Thanks. Let me look through the code once again. But I'm sure I don't have threading issues because I create a new instance of `SimpleDateFormat` to do the job every time the method is invoked. – J Freebird Feb 01 '18 at 06:08
  • 1
    Any reason why you are using the long outdated and notoriously troublesome `SimpleDateFormat` class? [`java.time`, the modern Java date and time API,](https://docs.oracle.com/javase/tutorial/datetime/) and its `DateTimeFormatter` are so much nicer to work with. – Ole V.V. Feb 01 '18 at 10:56
  • I’ve been looking a bit into the source code of `SimpleDateFormat` out of curiosity. No guarantees that I got it right, but it seems that with a non-negative month value and `MM` in the format pattern string, we end up in [a method called `zeroPaddingNumber`](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/text/SimpleDateFormat.java#SimpleDateFormat.zeroPaddingNumber%28int%2Cint%2Cint%2Cjava.lang.StringBuffer%29) with `minDigits` equal to 2, and then I see no way you could get `1` rather then `01` in the output. – Ole V.V. Feb 01 '18 at 12:37
  • @OleV.V. Thank you for your info. I'm using the old one because the code base is java 1.7. I guess for new apps I'll use the new API instead. – J Freebird Feb 01 '18 at 19:16

2 Answers2

4

SimpleDateFormat is not thread-safe, see this excellent article.

java.time.format.DateTimeFormatter is the modern thread-safe implementation of this functionality in the core Java.

jihor
  • 2,478
  • 14
  • 28
  • The code above is in a bean which is used by many Java threads concurrently. But in each method invocation, I'm creating a new instance of `SimpleDateFormat ` to do the job, as you can see. So I think it's not a thread safety issue. – J Freebird Feb 01 '18 at 05:41
  • @JFreebird are you getting only "yyyyMdd" pattern from time to time, or do you get complete gibberish of numbers sometimes? – jihor Feb 01 '18 at 06:08
  • I'm only getting the `yyyyMdd ` pattern from time to time. It's rather random and that's the only other format. – J Freebird Feb 01 '18 at 06:09
  • 2
    @JFreebird Hmm, that may have something to do with Locale and TimeZone which are often incorrect in Docker, or leniency. You can try to set Locale explicitly in constructor, but honestly, with Java 1.8+ I would switch to using `DateTimeFormatter` right away instead of wasting time dealing with the much hated old API. – jihor Feb 01 '18 at 07:11
  • 1
    @JFreebird Since you have accepted this answer, could you please elaborate on what caused the problem in the end? – jihor Feb 01 '18 at 19:23
  • Actually I'm not sure, but after changing the library, I don't see such issue. – J Freebird Feb 02 '18 at 21:22
  • Date “rounding”?? – Basil Bourque Jun 07 '18 at 02:46
1

tl;dr

Use the thread-safe java.time classes instead.

Specifically, use LocalDate and DateTimeFormatter.BASIC_ISO_DATE.

LocalDate.parse(
    "2018129" ,
    DateTimeFormatter.BASIC_ISO_DATE
)

2018-01-29

LocalDate.now()
    .format( DateTimeFormatter.BASIC_ISO_DATE )

20180129

Thread-safety

You do not provide enough information to diagnose your problem. I would guess either:

  • You are using those legacy date-time objects across threads, and they were not designed to be thread-safe. Instead use the java.time classes which are designed to be thread-safe by design via immutable objects pattern.
  • Something is going wrong during whatever you are doing in this mysterious “date rounding” which you mention but neglect to explain.

Wrong data type

timestamp in the message into a Java Date object.

You are putting a date-only value into a date-with-time-of-day type. Square peg, round hole.

Instead, use a date-only type for a date-only value: LocalDate.

ISO 8601

Your desired format YYYYMMDD happens to be defined in the ISO 8601 standard, as the “basic” variant where the use of delimiters is minimized.

Java provides a DateTimeFormatter object for this purpose: DateTimeFormatter.BASIC_ISO_DATE. So no need to define a formatting pattern.

String input = "2018129" ;
LocalDate ld = LocalDate.parse( input , DateTimeFormatter.BASIC_ISO_DATE ) ;

To generate such a string, use the same formatter.

LocalDate today = LocalDate.now( ZoneId.of( "Africa/Tunis" ) ) ;
String output = today.format( DateTimeFormatter.BASIC_ISO_DATE ) ;

By the way, I recommend using the full-length versions of ISO 8601 formats rather than the compact “basic” variants. The few bytes saved are not worth giving up the readability and reduced ambiguity, in my experience. Plus, the java.time classes use the full-length ISO 8601 formats by default when parsing/generating String objects, so you can dispense with DateTimeFormatter objects entirely.


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