Mark Barnard
Mark Barnard

Reputation: 21

Adding days with java.util.Calendar gives strange results

Using java.util.Calendar to add a single day to a Date, and SimpleDateFormat to display the result, sometimes seems to lose a day (generally in March) and sometimes skips a day (in November).

The program below, with output, illustrates the issue. Notice that I'm just adding one day at a time, then skipping a few months and adding a few more days. You'll see that 2008-03-09 gets printed twice, but 2008-11-02 is skipped. The same thing happens in other years, but on different days. I had to experiment to find the days that cause the problem.

If I don't set the timezone to UTC in the SimpleDateFormat then the problem does not occur. I ran this on a machine in the US Central Time Zone.

This certainly looks like a bug in Calendar or SimpleDateFormat, but I have not been able to find it documented anywhere. Anybody have an explanation of what is happening here?

The program:

package mab;

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

public class CalendarHiccup2 {

    public static void main(String[] args) {
        addDays("2008-03-08");
        addDays("2009-03-07");
        addDays("2010-03-13");
    }

    public static void addDays(String dateString) {
        System.out.println("Got dateString: " + dateString);

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));

        Calendar calendar = Calendar.getInstance();
        try {
            calendar.setTime(sdf.parse(dateString));
            Date day1 = calendar.getTime();
            System.out.println("  day1 = " + sdf.format(day1));

            calendar.add(java.util.Calendar.DAY_OF_MONTH, 1);
            Date day2 = calendar.getTime();
            System.out.println("  day2 = " + sdf.format(day2));

            calendar.add(java.util.Calendar.DAY_OF_MONTH, 1);
            Date day3 = calendar.getTime();
            System.out.println("  day3 = " + sdf.format(day3));

            calendar.add(java.util.Calendar.DAY_OF_MONTH, 1);
            Date day4 = calendar.getTime();
            System.out.println("  day4 = " + sdf.format(day4));

            // Skipping a few days ahead:
            calendar.add(java.util.Calendar.DAY_OF_MONTH, 235);
            Date day5 = calendar.getTime();
            System.out.println("  day5 = " + sdf.format(day5));

            calendar.add(java.util.Calendar.DAY_OF_MONTH, 1);
            Date day6 = calendar.getTime();
            System.out.println("  day6 = " + sdf.format(day6));

            calendar.add(java.util.Calendar.DAY_OF_MONTH, 1);
            Date day7 = calendar.getTime();
            System.out.println("  day7 = " + sdf.format(day7));

            calendar.add(java.util.Calendar.DAY_OF_MONTH, 1);
            Date day8 = calendar.getTime();
            System.out.println("  day8 = " + sdf.format(day8));

        } catch (Exception e) {
        }
    }

}

The output:

Got dateString: 2008-03-08
  day1 = 2008-03-08
  day2 = 2008-03-09
  day3 = 2008-03-09
  day4 = 2008-03-10
  day5 = 2008-10-31
  day6 = 2008-11-01
  day7 = 2008-11-03
  day8 = 2008-11-04
Got dateString: 2009-03-07
  day1 = 2009-03-07
  day2 = 2009-03-08
  day3 = 2009-03-08
  day4 = 2009-03-09
  day5 = 2009-10-30
  day6 = 2009-10-31
  day7 = 2009-11-02
  day8 = 2009-11-03
Got dateString: 2010-03-13
  day1 = 2010-03-13
  day2 = 2010-03-14
  day3 = 2010-03-14
  day4 = 2010-03-15
  day5 = 2010-11-05
  day6 = 2010-11-06
  day7 = 2010-11-08
  day8 = 2010-11-09

Upvotes: 2

Views: 7084

Answers (4)

Basil Bourque
Basil Bourque

Reputation: 338201

Daylight Saving Time

The answer by Tomasz Nurkiewicz is correct, the problem is Daylight Saving Time (DST).

the local time changes at 02:00 local standard time to 03:00 local daylight time on the second Sunday in March and returns at 02:00 local daylight time to 01:00 local standard time on the first Sunday in November.

See Wikipedia on Central Time Zone

Joda-Time

The Joda-Time library makes this kind of work much easier.

A DateTime in Joda-Time knows its own time zone. To use UTC/GMT (no time offset), pass the built-in constant DateTimeZone.UTC.

To print out only the date portion of a date-time, use a DateTimeFormatter.

// © 2013 Basil Bourque. This source code may be used freely forever by anyone taking full responsibility for doing so.
// import org.joda.time.*;
// import org.joda.time.format.*;

String dateString = "2008-03-08";

// withTimeAtStartOfDay() is probably superfluous in this example, but I like that it self-documents our focus on the day rather than a particular time of day.
DateTime dateTime = new DateTime( dateString, DateTimeZone.UTC ).withTimeAtStartOfDay();
DateTime dateTimePlus1 = dateTime.plusDays( 1 ).withTimeAtStartOfDay();
DateTime dateTimePlus2 = dateTime.plusDays( 2 ).withTimeAtStartOfDay();
DateTime dateTimePlus3 = dateTime.plusDays( 3 ).withTimeAtStartOfDay();

Dump to console…

System.out.println("dateTime: " + dateTime );
System.out.println("dateTime (date portion): " + ISODateTimeFormat.date().withZone( DateTimeZone.UTC ).print( dateTime ) );
System.out.println("dateTimePlus1: " + dateTimePlus1 );
System.out.println("dateTimePlus2: " + dateTimePlus2 );
System.out.println("dateTimePlus3: " + dateTimePlus3 );

When run…

dateTime: 2008-03-08T00:00:00.000Z
dateTime (date portion): 2008-03-08
dateTimePlus1: 2008-03-09T00:00:00.000Z
dateTimePlus2: 2008-03-10T00:00:00.000Z
dateTimePlus3: 2008-03-11T00:00:00.000Z

Upvotes: 0

Marsellus Wallace
Marsellus Wallace

Reputation: 18601

When you have these kind of issues you have to print the Calendar and Date instances to figure out what's going on.

The following:

sdf.setTimeZone(TimeZone.getTimeZone("UTC"));

changes the time of your Calendar depending on your JVM local time (and timezone). Calendar.getInstance(); creates a Calendar in your JVM local time, when you do calendar.setTime(sdf.parse(".....")) that sets the time in UTC (given how you created the sdf!). Depending on the DoY (and the Year as well!) that can make you pass the midnight and when you print your Date with the yyyy-MM-dd format you see the one day difference.

Print the full Calendar and full Date and you'll figure out what's going on!

Upvotes: 0

srkavin
srkavin

Reputation: 1180

It looks more like a day light saving time issue, which changes in Mar and Nov. Can you try setting the time element to 00:00:00? If you do,

addDays("2008-03-08 00:00:00");
addDays("2009-03-07 00:00:00");
addDays("2010-03-13 00:00:00");

and change the format to,

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

you'll see the difference it makes in the time elements.

Upvotes: 2

Tomasz Nurkiewicz
Tomasz Nurkiewicz

Reputation: 340693

This is caused by the daylight saving time and is completely correct.

The time (on north hemisphere) is advanced one hour typically in March and moved back in November.

Upvotes: 3

Related Questions