Search code examples
javasimpledateformatjava-6java.util.date

Sometimes java.util.Date before fails


I'm supporting Java 1.6 code where a date comparison is done.

static final SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy");

static void process(Message message) {
 String now =formatter.format(new Date());
 String end = formatter.format("01-12-2017");

 Date endDay = formatter.parse(end);
 Date currentDay = formatter.parse(now);

 if(endDay.before(currentDay)){
    System.out.println("Send a notification indicating the end day has been reached, so the message is not processed.");
 }else {
    System.out.println("Process the message and applies some business rules");
 }
}

I know the code is not the best, but of 160,000 transactions 3 have failed and the code in the if block is executed. I'm planning use Calendar, but What could have happened here?


Solution

  • tl;dr

    Use the thread-safe java.time classes instead of the thread-unsafe legacy SimpleDateFormat class.

    DateTimeFormatter f = DateTimeFormatter.ofPattern( "MM-dd-uuuu" ) ;
    ZoneId z = ZoneId.of( "America/Montreal" ) ;
    …
    LocalDate.parse( "01-12-2017" , f )
             .isBefore( LocalDate.now( z ) )
    

    Legacy date-time classes not thread-safe

    I noticed you are using a singleton for the formatter, a single static instance of SimpleDateFormat. That legacy class is not thread-safe, as documented on the class JavaDoc:

    Synchronization

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

    Your report of occasional error for seemingly no good reason suggests to me that you are using that object from more that one thread at a time. The static method named process, and your mention in comments of a server, makes me even more suspicious. Once in a while during runtime you experience a collision between threads.

    Avoid legacy date-time classes.

    You are using troublesome old date-time classes that are now legacy, supplanted by the java.time classes. Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.

    The java.time classes use immutable objects, and are designed to be thread-safe. So says the package documentation.

    LocalDate

    You are using date-time objects for a date-only value. The java.time classes include a way to represent a date-only. The LocalDate class represents a date-only value without time-of-day and without time zone.

    A time zone is crucial in determining a date. For any given moment, the date varies around the globe by zone. For example, a few minutes after midnight in Paris France is a new day while still “yesterday” in Montréal Québec.

    Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).

    The ZoneId class is thread-safe. So you may retain an instance for repeated use across threads.

    ZoneId z = ZoneId.of( "America/Montreal" );
    LocalDate today = LocalDate.now( z );
    

    When representing dates as strings, I suggest using standard ISO 8601 formats. For date-only that would be YYYY-MM-DD such as 2017-01-23. The java.time classes use these standard formats by default when parsing/generating strings.

    LocalDate target = LocalDate.parse( "2017-01-23" );
    

    If the format is out of your control, use DateTimeFormatter as shown in many other Questions & Answers on Stack Overflow. This class is thread-safe, so you can retain an instance for repeated use across threads.

    DateTimeFormatter f = DateTimeFormatter.ofPattern( "MM-dd-uuuu" );
    LocalDate target = LocalDate.parse( "01-12-2017" , f );
    

    Compare with methods such as isEqual, isBefore, and isAfter.

    if( target.isBefore( today ) ){
        System.out.println( "Send a notification indicating the end day has been reached, so the message is not processed." );
    } else {
        System.out.println( "Process the message and applies some business rules" );
    }
    

    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.

    Where to obtain the java.time classes?