Java Date year calculation is off by year for two days

This caused a Y2K-style bug in my software if you can imagine. Strange thing is the off-by-one year calculation only occurs for two days in the year, which I'm less sure how to troubleshoot.

The output:

03-Jan-2013
02-Jan-2013
01-Jan-2013
31-Dec-2013 ** strange
30-Dec-2013 ** strange
29-Dec-2012
28-Dec-2012
27-Dec-2012
26-Dec-2012
25-Dec-2012

I am not sure which part of the Java date utilities could cause such an error.

The code (since the test is so small I included a complete working program):

import java.util.Calendar;
import java.util.Date;
import java.text.SimpleDateFormat;

public class DateT {

        private static String getFormattedBackscanStartTime(int days) {

                SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MMM-YYYY");
                Calendar workingDate = Calendar.getInstance();
                workingDate.add(Calendar.DATE, -1 * days);

                String formattedStartTime = dateFormat.format(workingDate.getTime());
                return formattedStartTime;
        }

        public static void main(String args[]) {

                for(int i = 35; i < 45; i++) {
                        System.out.println(getFormattedBackscanStartTime(i));
                }
        }
}

Solution 1:

This is the problem:

"dd-MMM-YYYY"

YYYY is the week-year, not the calendar year. You want yyyy instead.

The last two days of calendar year 2012 were in the first week of week-year 2013. You should normally only use the week year in conjunction with the "week of year" specifier (w).

Solution 2:

I am assuming you are using java 1.7.

The code snippet above will not work with java 1.6 as SimpleDateFormat("dd-MMM-YYYY") will raise an java.lang.IllegalArgumentException (YYYY is not available in java 1.6)

You need to use yyyy instead of YYYY.

Y -> week-year
y -> year

here

EDIT

Works great with yyyy:

$ java DateT
03-Jan-2013
02-Jan-2013
01-Jan-2013
31-Dec-2012
30-Dec-2012
29-Dec-2012
28-Dec-2012
27-Dec-2012
26-Dec-2012
25-Dec-2012

Solution 3:

The problem lies in your date format string - year should be yyyy not YYYY.

If you print the value of workingDate.getTime() in each iteration of the loop, you'll see it has the expected values:

Thu Jan 03 11:19:33 EST 2013
Wed Jan 02 11:19:33 EST 2013
Tue Jan 01 11:19:33 EST 2013
Mon Dec 31 11:19:33 EST 2012
Sun Dec 30 11:19:33 EST 2012
Sat Dec 29 11:19:33 EST 2012
Fri Dec 28 11:19:33 EST 2012
Thu Dec 27 11:19:33 EST 2012
Wed Dec 26 11:19:33 EST 2012
Tue Dec 25 11:19:33 EST 2012

Therefore the problem lies in the SimpleDateFormat usage.

Solution 4:

For the sake of completeness, here’s the modern answer using LocalDate (as recommended by Basil Bourque in a comment).

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class DateT {

    private static DateTimeFormatter dateFormatter 
            = DateTimeFormatter.ofPattern("dd-MMM-uuuu", Locale.US);

    private static String getFormattedBackscanStartTime(int days) {
        return LocalDate.now(ZoneId.systemDefault()).minusDays(days).format(dateFormatter);
    }

    public static void main(String args[]) {
        for(int i = 155; i < 165; i++) {
            System.out.println(getFormattedBackscanStartTime(i));
        }
    }
}

Running this today I got

04-Jan-2017
03-Jan-2017
02-Jan-2017
01-Jan-2017
31-Dec-2016
30-Dec-2016
29-Dec-2016
28-Dec-2016
27-Dec-2016
26-Dec-2016

A few things to note:

  • Give an explicit locale to your formatter to control the langauge of your output. Even if you just pass Locale.getDefault() you are telling the reader that you have thought about locale and made a decision.
  • Similarly give an explicit time zone to LocalDate.now() to tell the reader you’ve made a decision (for example ZoneId.of("America/New_York") for a specific time zone; ZoneId.systemDefault() for the JVM’s current time zone setting).
  • I find the code simpler and more straightforward than the code using the oldfashioned Calendar class. This is typical for the newer classes.
  • I have used uuuu for year. yyyy (lowercase) works too, there will only be a difference for years before the common era (AKA BC).

Solution 5:

You need to use lower case y for the year. Try this:

   SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MMM-yyyy");