While using a SimpleDateFormat and a couple of GregorianCalendar objects in what I thought was a simple piece of code, I came across something that was quite unexpected. When I would call DateFormat.format, one of my Calendar objects would get updated to reflect the Date passed into the function.
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.
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.
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.
I too thought my problem was with clone being a shallow and not a deep copy when I first ran into this. My actual program used a loop similar to the Calendar clone problem example on Vivek's Tech Blog but as my test case shows instead of getting the Calendar from the DateFormat object, I was setting my "baseCalendar" (cal) into my DateFormat object. In the end the effect is the same, the DateFormat and my "baseCalendar" are a reference to the same object being modified with each call to DateFormat.format.
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.
I searched through the bug list and found a few that show similar mistakes or comments on how to use DateFormat related to get/setCalendar and parse/format. Here are a couple of notable ones with their problems, workarounds and the bug triage evaluation comments paraphrased. See the actual bug reports for exact details.
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).