java 从 XMLGregorianCalendar 转换为 Calendar 时的日期更改
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16321193/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Date change when converting from XMLGregorianCalendar to Calendar
提问by JWiley
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 whyit's being cast like so, from the documentationit 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();
When the time is taken from calendarDate
on line 86: 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.
问题行是从XMLGregorianCalendar
to的演员表GregorianCalendar
,第 78 行:calendarDate = argCal.toGregorianCalendar();
当时间取自calendarDate
第 86 行时:cal.setTime(calendarDate.getTime());
时间比应有的时间提前 2 天,即 1 月 3 日而不是 1 月 1 日,如您所见从下面程序的输出中。
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);
}
}
}
回答by Pavel Horal
Excerpt from XMLGregorianCalendar.toGregorianCalendar()
JavaDocon 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. Then the problem is here:
这意味着,创建的日历将是可预测的,并且不会像默认情况下旧日期那样切换到儒略历。那么问题就在这里:
argCal.toGregorianCalendar()
- converting from XMLGregorianCalendarto GregorianCalendarusing field representation(Julian system is not used - see above)cal.setTime(calendarDate.getTime());
- this is actually converting field representation to a timestamp representationand initializing a new calendar with this timestamp
- the new calendar is using Julian system to represent the date as it is older than 1582
argCal.toGregorianCalendar()
-使用字段表示从XMLGregorianCalendar转换为GregorianCalendar(不使用儒略系统 - 见上文)cal.setTime(calendarDate.getTime());
- 这实际上是将字段表示转换为时间戳表示并使用此时间戳初始化新日历
- 新日历使用儒略系统来表示日期,因为它早于 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 XMLGregorianCalendaris doing it)
- 使用 JodaTime,
LocalDate#fromCalendarFiels
如果您只对日期感兴趣 使用字段访问而不是#getTime
方法转换日历- 强制公历使用预测系统(与XMLGregorianCalendar使用的方式相同)
UPDATEPlease 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).
更新请注意,Java 日期和日历 API 的设计不是很好,有时会(并且)有时会令人困惑。这也是 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:
现在,您必须意识到,您可以通过两种截然不同的方法存储和使用特定的时刻(与日历无关的关键字):
- storing an offset (e.g. in milliseconds) from a well defined instantcalled epoch (e.g. unix epoch 1970-01-01)
- storing date by its calendar fields (e.g. 1st of January 1970)
- 从称为 epoch的明确定义的时刻(例如unix epoch 1970-01-01)存储偏移量(例如,以毫秒为单位)
- 按日历字段存储日期(例如 1970 年 1 月 1 日)
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.
第一种方法是在java.util.Date
. 然而,这种表示通常是非人类友好的。人类使用日历日期,而不是时间戳。将时间戳转换为日期字段是 Calendar 介入的地方。这也是有趣的部分开始的地方......如果你想用字段来表示日期,你需要意识到总是有多种方法可以做到这一点。有些国家可以决定使用农历月份,其他国家可能会说 0 年只是 10 年前。而公历只是将实际即时转换为实际日期字段的一种方式。
A bit on XMLGregorianCalendarvs GregorianCalendar:
关于XMLGregorianCalendar与GregorianCalendar 的一些内容:
- XML specification explicitly says that the human-readable date is a gregorian calendardate
- Java's GregorianCalendarcontains this "magic", which switches to Julian system under the hood, if the instant is older than a defined switch-over date
- that is why XMLGregorianCalendarmodifies GregorianCalendarduring its initialization to disable this magic switch (see the excerpt from JavaDoc above)
- XML 规范明确指出人类可读的日期是公历日期
- Java 的GregorianCalendar包含这个“魔法”,如果瞬间早于定义的切换日期,它会在引擎盖下切换到儒略系统
- 这就是为什么XMLGregorianCalendar在初始化期间修改GregorianCalendar以禁用这个魔术开关(参见上面 JavaDoc 的摘录)
Now the interesting part:
现在有趣的部分:
If the Julian switch won't be disabled, GregorianCalendarwould 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 correctand 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].
如果 Julian 开关不会被禁用,GregorianCalendar会假设日历字段来自 Julian 系统,并将它们移动 3 天。你认为日期已经改变了 3 天,一定是出了什么问题,对吧?不,日期实际上一直都是正确的,并且它包含正确的时间戳!只有日历向您展示了儒略场而不是格里高利场。我会说这很令人困惑:) [JSR-310 在后台大笑]。
So if you want to work with pure gregorian calendar (i.e. to use so called proleptic gregorianfor 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 :))?
您可能会说:calendar.getTime()
仍然给我错误的日期。嗯,那是因为java.util.Date.toString()
(由 调用System.out.println
)正在使用默认值Calendar
,对于较旧的日期,它将切换到儒略系统。使困惑?也许生气(我知道我是:))?
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 了解问题及其后果(请参阅此答案下的讨论)。
回答by dcsohl
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. If you change your date to 1001-01-01, you will find that your end result is 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.
看起来好像您不小心从公历转换为儒略历。(外推的或预测的)格里高利历指的是 0001-01-01 的那一天实际上是儒略历中的 0001-01-03。如果将日期更改为 1001-01-01,您会发现最终结果是 1000-12-26。这与从公历到儒略的日期转换一致,因为偏移量随时间变化。在 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
I get的实例实际上是 acom.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. Which means the problem is probably in GregorianCalendar
...
这使得日历成为纯粹的预测公历,因此 1583 年之前的日期仍然通过公历算法从纪元时间(自 1970 年以来的毫秒数)转换而来。这意味着问题可能出在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. The Date
that comes out of getTime()
has no concept of what the Gregorian changeover value was. This may or may not be a bug, but it's certainly unexpected behaviour.
由于 ... 的值,进入的字段值被解释为公历,getGregorianChange()
但出来(从纪元毫秒转换)该字段似乎不服从。在Date
自带的出来getTime()
没有的阳历转换值是什么概念。这可能是也可能不是错误,但这肯定是意料之外的行为。