Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calendar.get(Calendar.DAY_OF_MONTH) returning wrong day

When I run the following code:

int year = 2017;
int month = 7;
int dayOfMonth = 10;

Calendar dateOfBirth = new GregorianCalendar(year, month, dayOfMonth);
dateOfBirth.getTime(); // See http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4827490
dateOfBirth.setTimeZone(TimeZone.getTimeZone("UTC"));

System.out.println("Original Day Of Month:\t\t" + dayOfMonth);
System.out.println("Calendar:\t\t\t" + dateOfBirth);
System.out.println("Calendar substring:\t\t" + dateOfBirth.toString().substring(dateOfBirth.toString().indexOf("DAY_OF_MONTH"), dateOfBirth.toString().indexOf("DAY_OF_MONTH") + 15));
System.out.println("Formatted date:\t\t\t" + new SimpleDateFormat("dd").format(dateOfBirth.getTime()));
System.out.println("get(Calendar.DAY_OF_MONTH):\t" + dateOfBirth.get(Calendar.DAY_OF_MONTH));

I get this output:

Original Day Of Month:      10
Calendar:                   java.util.GregorianCalendar[time=1502312400000,areFieldsSet=false,areAllFieldsSet=false,lenient=true,zone=sun.util.calendar.ZoneInfo[id="UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=?,YEAR=2017,MONTH=7,WEEK_OF_YEAR=?,WEEK_OF_MONTH=?,DAY_OF_MONTH=10,DAY_OF_YEAR=?,DAY_OF_WEEK=?,DAY_OF_WEEK_IN_MONTH=?,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=?,ZONE_OFFSET=?,DST_OFFSET=?]
Calendar substring:         DAY_OF_MONTH=10
Formatted date:             10
get(Calendar.DAY_OF_MONTH): 9

As you can see, the value returned by dateOfBirth.get(Calendar.DAY_OF_MONTH) is incorrect.

I am using UTC because it's a date of birth, and I don't want it to be dependent on a particular time zone. And in order to do so correctly, I need to make the call to dateOfBirth.getTime() - see http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4827490. If I take out that call, this works correctly, but then I encounter other bugs.

Any idea what I can do to accurately get the correct day of the month?

I'm using Java v1.7.0_79, and, in case it matters, I'm currently in GMT+3.

like image 749
rweiser Avatar asked Jan 03 '23 20:01

rweiser


2 Answers

Just set the date values after setting the timezone:

int year = 2017;
int month = Calendar.JULY;
int dayOfMonth = 10;

Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
calendar.set(year, month, dayOfMonth);

System.out.println("DAY_OF_MONTH: " + calendar.get(Calendar.DAY_OF_MONTH));

This outputs the day of the month correctly:

DAY_OF_MONTH: 10
like image 164
Robby Cornelissen Avatar answered Jan 06 '23 08:01

Robby Cornelissen


Glad that the answer by Robby Cornelissen solved your problem. I should like to explain what happened and why that answer helped.

When you are using the three-arg GregorianCalendar constructor, it “Constructs a GregorianCalendar with the given date set in the default time zone with the default locale.” Quoting the docs; italics are mine. In your case this is GMT+3 (now; the important thing is that in August it will still be GMT plus something).

What the docs are not so clear about is that hour of day is set to 0, so you’ve really got a point in time of 10 August 2017 0:00 at zone offset +3:00. You would really have wanted the GregorianCalendar not to worry about time of day, but there’s no such thing. This is one of the reasons why I recommend LocalDate instead as I mentioned in a comment.

When you change the time zone, the point in time stays the same, but since you were ahead of GMT in your own time zone, you now have 9 August 2017 21:00 UTC. So 9 is the correct day-of-month value now.

I think the greatest surprise is that the toString method still produces DAY_OF_MONTH=10. When GregorianCalendar computes which fields is obscure to me, but obviously is still remembers the old value at this point.

For formatting you use new SimpleDateFormat("dd"). This formatter uses your default time zone of GMT+3, and the point in time from the calendar object, correctly producing 10. If you had wanted it to produce the day-of-month from the calendar, you might have set its time zone to UTC too.

dateOfBirth.get(Calendar.DAY_OF_MONTH) produces the correct value of 9.

The funny thing is that if after this call I repeat your line:

    System.out.println("Calendar substring:\t\t" 
            + dateOfBirth.toString().substring(dateOfBirth.toString().indexOf("DAY_OF_MONTH"), 
                    dateOfBirth.toString().indexOf("DAY_OF_MONTH") + 15));

This time it produces:

Calendar substring:     DAY_OF_MONTH=9,

So that field has now been computed.

In Robby Cornelissen’s code the calendar object is born with UTC time zone, so when you set the date, it is really set to — well, 10 July 2017 this time, but 0:00 UTC. This corresponds to 3:00 in your time zone, but the date is also 10 July there, so the rest of your code will produce the 10 you expected. However, users in time zones west of Greenwich will have problems now because their SimpleDateFormat will use their time zone, which is behind GMT, so they will see a formatted date of 09.

For the sake of completeness here’s the ThreeTen (Backport) version:

    LocalDate date = LocalDate.of(2017, Month.AUGUST, 10);
    System.out.println("Day of month:\t" + date.getDayOfMonth());

This prints

Day of month:   10

A LocalDate has got neither any time of day nor any time zone, leaving a lot less room for confusion. Contrary to Calendar it also numbers the months the way humans do, so 7 is July and 8 is August.

like image 20
Ole V.V. Avatar answered Jan 06 '23 09:01

Ole V.V.