After a lot of unfruitful searching and testing I found that the affected Calendar object was the one I passed into DateFormat.setCalendar. If I didn't set the calendar or if I set it using a defensive copy then the program worked as I expected.
As far as I can tell this behavior is undocumented in the 1.6 Javadocs. DateFormat.setCalendar doesn't say if it makes a copy or not and DateFormat.format doesn't mention it will change the Calendar object it was given. I haven't tested but now I won't be surprised if DateFormat.parse also changes it's related Calendar object. I think DateFormat.setCalendar should have done the defensive copy.
Are there well-known patterns that use this side-effect on the Calendar object given to DateFormat.setCalendar? How about a list of gotchas that like Odi's Date and Time in Java that warns of this side-effect? I didn't find them and that makes me think I did something unusual.
Here's the test case:
And the output:
cal : 2010-Oct-27 16:56 EDT
cal2: 2010-Oct-27 16:56 EDT (clone)
cal : 2010-Oct-27 16:56 EDT
cal2: 2010-Nov-03 00:56 EDT (post-add/set)
cal : 2010-Nov-03 00:56 EDT (post-cal2-add/set)
cal2: 2010-Nov-03 00:56 EDT
Bobby Smallman wrote:To my knowledge there are a few different ways this, for lack of a better word "linked cloning" comes about. I had not encountered it in exactly the same way you have, so it sparked my interest. Check out this link for a similar yet different issues to potentially help shed some light on the situation, I look forward to hearing what others on here say.
In that link, they refer to the weird behavior as a cloning problem, but the code is doing exactly the same thing that Jacob's example does. You can verify that it's not the cloning part that causes the behavior by just instantiating a separate, second Calendar object instead of using the clone function--you will get similar behavior. Although the API documentation doesn't spell it out, it seems pretty clear that the DateFormat.format() method is applying the date/time parameter value to its calendar as part of the formatting process.
Since I just got bit it currently feels like a bug that DateFormat.setCalendar doesn't itself make the defensive copy and perhaps Vivek will feel it is a bug that DateFormat.getCalendar doesn't appear to return a defensive copy. I recognize that others may be taking advantage of this side effect as a feature so the most I hope for is better education/documentation.
I agree, assuming the worst is safest. Being new to Java I was wondering if this side-effect was commonly used as a feature or listed somewhere as a gotcha.
Bug 6609675: [Fmt-Da] DateFormat.parse() on a timezone changes its calendar's timezone
DateFormat.parse() has an undocumented side-effect: calling it with a string that contains a timezone, different from one that was explicitly set, will overwrite the DateFormat's internal timezone with that of the string.
Further attempts to use the DateFormat for formatting will use the new timezone.
Use separate DateFormats for formatting and parsing.
- The parse methods may overwrite the date-time fields and the TimeZone value of the embedded calendar (DateFormat.calendar).
Bug 6609452: [Fmt-Da] DateFormat.getCalendar().getTime() outputs wrong year
Inputting DateFormat.calendar.getTime() into DateFormat.format gives the wrong results.
Use a different source for the Calendar object or make a new Date.
The Calendar instance in a DateFormat is first used to calculate the base year for the 2-digit year handling. Please don't rely on the [ DateFormat ] Calendar fields.