Excerpt from XMLGregorianCalendar.toGregorianCalendar()
JavaDoc on how they create the GregorianCalendar
instance:
Obtain a pure Gregorian Calendar by invoking GregorianCalendar.setGregorianChange( new Date(Long.MIN_VALUE)).
This means, that the created calendar will be proleptic and won't switch to Julian calendar as it does by default for old dates. Then the problem is here:
argCal.toGregorianCalendar()
- converting from XMLGregorianCalendar to GregorianCalendar using field representation (Julian system is not used - see above)
cal.setTime(calendarDate.getTime());
- this is actually converting field representation to a timestamp representation and initializing a new calendar with this timestamp
- the new calendar is using Julian system to represent the date as it is older than 1582
There are few ways how to solve this:
- use JodaTime and
LocalDate#fromCalendarFiels
if you are interested only in the date
convert calendars using field access and not the #getTime
method
- force the gregorian calendar to use proleptic system (in the same way as XMLGregorianCalendar is doing it)
UPDATE Please note that Java Date and Calendar APIs are not so well designed and can be (and are) sometimes pretty confusing. This is also why Java 8 contains completely reworked date-time library JSR-310 (based on JodaTime by the way).
Now, you have to realize, that you can store and work with a specific instant (calendar independent keyword) via two very different approaches:
- storing an offset (e.g. in milliseconds) from a well defined instant called epoch (e.g. unix epoch 1970-01-01)
- storing date by its calendar fields (e.g. 1st of January 1970)
The first approach is what is being used under the hood in java.util.Date
. However this representation is usually non-human friendly. Humans work with calendar dates, not timestamps. Converting timestamps to date fields is where Calendar steps in. Also that is where the funny part starts... if you want to represent date by its fields, you need to realize that there are always multiple ways how to do that. Some nation can decide to use lunar months, others may say that the year 0 was just 10 years ago. And gregorian calendar is just one way of converting actual instant to actual date fields.
A bit on XMLGregorianCalendar vs GregorianCalendar:
- XML specification explicitly says that the human-readable date is a gregorian calendar date
- Java's GregorianCalendar contains this "magic", which switches to Julian system under the hood, if the instant is older than a defined switch-over date
- that is why XMLGregorianCalendar modifies GregorianCalendar during its initialization to disable this magic switch (see the excerpt from JavaDoc above)
Now the interesting part:
If the Julian switch won't be disabled, GregorianCalendar would assume that the calendar fields are from Julian system and it will shift them by 3 days. You thought that the date has been shifted by 3 days and something must've went wrong, right? No, the date was actually all the time correct and it contained correct timestamp under the hood! Only the calendar had presented you Julian fields instead of Gregorian fields. And this is pretty confusing I would say :) [JSR-310 laughing in the background].
So if you want to work with pure gregorian calendar (i.e. to use so called proleptic gregorian for old dates), you need to initialize calendar like this:
Calendar calendar = Calendar.getInstance();
((GregorianCalendar) calendar).setGregorianChange(new Date(Long.MIN_VALUE));
You might say: calendar.getTime()
is still giving me incorrect date. Well, that is because java.util.Date.toString()
(called by System.out.println
) is using the default Calendar
, which will switch to Julian system for older dates. Confused? Maybe angry (I know I am :))?
UPDATE 2
// Get XML gregorian calendar
XMLGregorianCalendar xmlCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar();
xmlCalendar.setYear(1); // Watch for octal number representations (you had there 0001)
xmlCalendar.setMonth(1);
xmlCalendar.setDay(1);
// Convert to Calendar as it is easier to work with it
Calendar calendar = xmlCalendar.toGregorianCalendar(); // Proleptic for old dates
// Convert to default calendar (will misinterpret proleptic for Julian, but it is a workaround)
Calendar result = Calendar.getInstance();
result.setTimeZone(calendar.getTimeZone());
result.set(Calendar.YEAR, calendar.get(Calendar.YEAR));
result.set(Calendar.MONTH, calendar.get(Calendar.MONTH));
result.set(Calendar.DAY_OF_MONTH, calendar.get(Calendar.DAY_OF_MONTH));
result.set(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY));
result.set(Calendar.MINUTE, calendar.get(Calendar.MINUTE));
result.set(Calendar.SECOND, calendar.get(Calendar.SECOND));
result.set(Calendar.MILLISECOND, calendar.get(Calendar.MILLISECOND));
System.out.println(result.getTime());
Disclamer: this code is wrong (the result instant is not the same as the one in the XML file), but OP understands the problem and its consequences (see the discussion under this answer).