[英]Date change when converting from XMLGregorianCalendar to Calendar
When testing out a web service that maps datetime types between systems, I noticed that sending any date before the Gregorian calendar start time resulted in a loss of accuracy when casting to the final type, with the end result always slightly ahead in time in the range of a few days. 当测试在系统之间映射日期时间类型的Web服务时,我注意到在公历开始时间之前发送任何日期会导致在转换为最终类型时失去准确性,最终结果总是在范围内略微超前几天。
I narrowed down the problem to the exact line, but I still can't figure out why it's being cast like so, from the documentation it states that the Julian calendar is used for datetimes before the Gregorian calendar start: October 15, 1582. 我把问题缩小到了确切的界限,但是我仍然无法弄清楚为什么它会像这样投射,从文件中说明朱利安日历用于格里高利历开始之前的日期时间:1582年10月15日。
The problem line is at the cast from XMLGregorianCalendar
to GregorianCalendar
, line 78: calendarDate = argCal.toGregorianCalendar();
问题行是从XMLGregorianCalendar
到GregorianCalendar
,第78行: calendarDate = argCal.toGregorianCalendar();
When the time is taken from calendarDate
on line 86: cal.setTime(calendarDate.getTime());
当从第86行的calendarDate
获取时间时: cal.setTime(calendarDate.getTime());
The time comes back 2 days ahead of what it should be, Jan. 03 instead of Jan. 01, as you'll see from the output in the program below. 时间比1月3日而不是1月1日提前2天回来,正如您将从下面的程序输出中看到的那样。
Here's a sample program I made to show the casting process end to end: 这是我制作的示例程序,用于显示端到端的铸造过程:
import java.sql.Date;
import java.util.Calendar;
import java.util.GregorianCalendar;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
public class TestDateConversions {
public static void main(String[] args)
{
TestDateConversions testDates = new TestDateConversions();
try
{
XMLGregorianCalendar testDate1 = DatatypeFactory.newInstance().newXMLGregorianCalendar();
testDate1.setYear(0001);
testDate1.setMonth(01);
testDate1.setDay(01);
System.out.println("Start date: "+testDate1.toString() +"\n**********************");
testDates.setXMLGregorianCalendar(testDate1);
System.out.println("\nNull given \n"+ "**********");
testDates.setXMLGregorianCalendar(null);
}
catch(Exception e)
{
System.out.println(e);
}
}
public void setXMLGregorianCalendar(XMLGregorianCalendar argCal)
{
GregorianCalendar calendarDate;
if (argCal != null)
{
calendarDate = argCal.toGregorianCalendar();
System.out.println("XMLGregorianCalendar time: " + argCal.getHour() + ":"+argCal.getMinute()+":"+argCal.getSecond());
System.out.println("XMLGregorianCalendar time(ms): "+argCal.getMillisecond());
System.out.println("XMLGregorianCalendar -> GregorianCalendar: "+calendarDate.get(GregorianCalendar.YEAR) + "-"+(calendarDate.get(GregorianCalendar.MONTH)+1) + "-"+calendarDate.get(GregorianCalendar.DAY_OF_MONTH));
System.out.println("!!!!PROBLEM AREA!!!!");
Calendar cal = Calendar.getInstance();
System.out.println("-- New Calendar instance: "+cal.get(Calendar.YEAR) + "-"+(cal.get(Calendar.MONTH)+1)+"-"+cal.get(Calendar.DAY_OF_MONTH));
System.out.println("-- Calling Calendar.setTime(GregorianCalendar.getTime())");
cal.setTime(calendarDate.getTime());
System.out.println("-- calendarDate.getTime() = " + calendarDate.getTime() + " <-- time is incorrect");
System.out.println("-- Calendar with time set from GregorianCalendar: "+cal.get(Calendar.YEAR) + "-"+(cal.get(Calendar.MONTH)+1)+"-"+cal.get(Calendar.DAY_OF_MONTH) + " <-- day is increased here");
setCalendar(cal);
}
else
{
setCalendar(null);
}
}
public void setCalendar(Calendar argCal)
{
if (argCal != null)
{
Date date = new Date(argCal.getTimeInMillis());
System.out.println("Calendar to Date: "+date);
setDate(date);
}
else
{
setDate(null);
}
}
public void setDate(Date argDate)
{
try
{
if (argDate == null)
{
Calendar cal = new GregorianCalendar(1,0,1);
Date nullDate = new Date(cal.getTimeInMillis());
System.out.println("Null Calendar created: "+cal.get(Calendar.YEAR) + "-"+(cal.get(Calendar.MONTH)+1)+"-"+cal.get(Calendar.DAY_OF_MONTH));
System.out.println("Null Date created: "+nullDate);
}
else
{
System.out.println("Final date type: "+argDate);
}
}
catch (Exception ex)
{
System.out.println(ex);
}
}
}
Excerpt from XMLGregorianCalendar.toGregorianCalendar()
JavaDoc on how they create the GregorianCalendar
instance: 摘自XMLGregorianCalendar.toGregorianCalendar()
JavaDoc ,了解它们如何创建GregorianCalendar
实例:
Obtain a pure Gregorian Calendar by invoking GregorianCalendar.setGregorianChange( new Date(Long.MIN_VALUE)). 通过调用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. 这意味着,创建的日历将是无效的,并且不会像旧日期那样切换到Julian日历。 Then the problem is here: 那问题就在这里:
argCal.toGregorianCalendar()
- converting from XMLGregorianCalendar to GregorianCalendar using field representation (Julian system is not used - see above) argCal.toGregorianCalendar()
- 使用字段表示从XMLGregorianCalendar转换为GregorianCalendar (不使用Julian系统 - 见上文) cal.setTime(calendarDate.getTime());
There are few ways how to solve this: 解决这个问题的方法很简单:
LocalDate#fromCalendarFiels
if you are interested only in the date 如果您只对日期感兴趣,请使用JodaTime和LocalDate#fromCalendarFiels
#getTime
method 使用字段访问而不是#getTime
方法转换日历 UPDATE Please note that Java Date and Calendar APIs are not so well designed and can be (and are) sometimes pretty confusing. 更新请注意,Java日期和日历API的设计并不是很好,有时可能会(并且有时)令人困惑。 This is also why Java 8 contains completely reworked date-time library JSR-310 (based on JodaTime by the way). 这也是为什么Java 8包含完全重写的日期时间库JSR-310 (顺便说一下基于JodaTime)的原因。
Now, you have to realize, that you can store and work with a specific instant (calendar independent keyword) via two very different approaches: 现在,您必须意识到,您可以通过两种截然不同的方法存储和使用特定的即时 (日历独立关键字):
The first approach is what is being used under the hood in java.util.Date
. 第一种方法是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. 有些国家可以决定使用阴历月,其他人可能会说0年仅仅是10年前。 And gregorian calendar is just one way of converting actual instant to actual date fields. 格里高利历只是将实际即时字段转换为实际日期字段的一种方式。
A bit on XMLGregorianCalendar vs GregorianCalendar : 关于XMLGregorianCalendar与GregorianCalendar的一点点:
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. 如果Julian开关不会被禁用,那么GregorianCalendar会假设日历字段来自Julian系统,它会将它们移动3天。 You thought that the date has been shifted by 3 days and something must've went wrong, right? 你认为这个日期已经改变了3天而且肯定会出现问题,对吧? 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]. 我觉得这很令人困惑:) [JSR-310在后台笑]。
So if you want to work with pure gregorian calendar (ie to use so called proleptic gregorian for old dates), you need to initialize calendar like this: 因此,如果你想使用纯格里高利历(即在旧日期使用所谓的proleptic gregorian ),你需要像这样初始化日历:
Calendar calendar = Calendar.getInstance();
((GregorianCalendar) calendar).setGregorianChange(new Date(Long.MIN_VALUE));
You might say: calendar.getTime()
is still giving me incorrect date. 你可能会说: calendar.getTime()
仍然给我错误的日期。 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. 好吧,那是因为java.util.Date.toString()
(由System.out.println
调用)正在使用默认Calendar
,它将切换到旧日期的Julian系统。 Confused? 困惑? Maybe angry (I know I am :))? 也许生气(我知道我是:))?
UPDATE 2 更新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). 免责声明: 此代码错误 (结果瞬间与XML文件中的结果不同),但OP了解问题及其后果(请参阅本答案下的讨论)。
It looks as though you are accidentally converting from the Gregorian Calendar to the Julian Calendar. 看起来好像你不小心从格里高利历转换到朱利安日历。 The day that the (extrapolated or proleptic ) Gregorian Calendar refers to as 0001-01-01 is, indeed, 0001-01-03 in the Julian Calendar. (外推或延长的 )阳历称为0001-01-01的那一天确实是朱利安日历中的0001-01-03。 If you change your date to 1001-01-01, you will find that your end result is 1000-12-26. 如果您将日期更改为1001-01-01,您会发现最终结果是1000-12-26。 This is consistent with date conversions from Gregorian to Julian, as the offset changes over time. 这与从格里高利到朱利安的日期转换一致,因为偏移随时间变化。 It was actually positive before the 4th century, but has been negative (and getting more negative over time) since then. 它在4世纪之前实际上是积极的,但从那以后一直是消极的(随着时间的推移变得越来越消极)。
The instance of XMLGregorianCalendar
I get is actually a com.sun.org.apache.xerces.internal.jaxp.datatype.XMLGregorianCalendarImpl
and the toGregorianCalendar()
method includes these lines: 我得到的XMLGregorianCalendar
的实例实际上是com.sun.org.apache.xerces.internal.jaxp.datatype.XMLGregorianCalendarImpl
,而toGregorianCalendar()
方法包括以下这些行:
result = new GregorianCalendar(tz, locale);
result.clear();
result.setGregorianChange(PURE_GREGORIAN_CHANGE);
This makes the calendar an purely proleptic GregorianCalendar, so that dates before 1583 are still converted from the epoch time (milliseconds since 1970) by the Gregorian algorithm. 这使得日历成为纯粹的历史GregorianCalendar,因此1583年之前的日期仍然通过格里高利算法从纪元时间(自1970年以来的毫秒)转换。 Which means the problem is probably in GregorianCalendar
... 这意味着问题可能出在GregorianCalendar
......
Which is indeed the case. 确实如此。 Try this sample program and you'll see: 试试这个示例程序,你会看到:
public static void main(String...args) {
GregorianCalendar gcal = new GregorianCalendar();
gcal.clear();
gcal.setGregorianChange(new Date(Long.MIN_VALUE));
gcal.set(Calendar.YEAR, 1); // or any other year before 1582
gcal.set(Calendar.MONTH, Calendar.JANUARY);
gcal.set(Calendar.DATE, 1);
Date d = gcal.getTime();
System.out.println(d);
}
The field values going in are interpreted as Gregorian due to the value of getGregorianChange()
... but coming out (converting from epoch milliseconds) that field seems to be disobeyed. 由于getGregorianChange()
的值, getGregorianChange()
的字段值被解释为Gregorian,但是出现(从epoch毫秒转换)该字段似乎是不服从的。 The Date
that comes out of getTime()
has no concept of what the Gregorian changeover value was. getTime()
出来的Date
没有Gregorian转换值的概念。 This may or may not be a bug, but it's certainly unexpected behaviour. 这可能是也可能不是错误,但它肯定是意外行为。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.