从XMLGregorianCalendar转换为日历时的日期更改 [英] Date change when converting from XMLGregorianCalendar to Calendar

查看:1728
本文介绍了从XMLGregorianCalendar转换为日历时的日期更改的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

测试在系统之间映射datetime类型的web服务时,我注意到在公历日历开始时间之前发送任何日期导致投射到最终类型时精度损失,最终结果总是稍微提前在几天的范围内。



我将问题范围缩小到了正确的行,但我仍然无法找出为什么 a href =http://docs.oracle.com/javase/1.5.0/docs/api/java/util/GregorianCalendar.html =nofollow>文档,它指出儒略历是



问题行是从 XMLGregorianCalendar的转换 GregorianCalendar ,第78行: calendarDate = argCal.toGregorianCalendar();
calendarDate 在第86行: cal.setTime(calendarDate.getTime());



这是我做的一个示例程序显示投放流程从头到尾:

  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(开始日期:+ testDate1.toString()+\\\
**********************) ;

testDates.setXMLGregorianCalendar(testDate1);
System.out.println(\\\
Null given \\\
+**********);
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( - 日历时间设置从GregorianCalendar:+ cal.get(Calendar.YEAR)+ - +(cal.get(Calendar.MONTH)+1)+ + cal.get(Calendar.DAY_OF_MONTH)+< - day in this);
setCalendar(cal);
}
else
{
setCalendar(null);
}
}

public void setCalendar(Calendar argCal)
{
if(argCal!= null)
{
Date date = new Date(argCal.getTimeInMillis());
System.out.println(日历到日期:+日期);
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);
}
}
}


解决方案>

摘录自 XMLGregorianCalendar.toGregorianCalendar() JavaDoc 了解如何创建 GregorianCalendar 实例:


通过调用GregorianCalendar.setGregorianChange(new Date(Long.MIN_VALUE))获取纯Gregorian日历。


这意味着,创建的日历将是proleptic,不会切换到Julian日历,因为它是默认的旧日期。然后问题出在这里:




  • argCal.toGregorianCalendar() 使用字段表示(不使用Julian系统 - 见上文)
  • $ g $ {$ g $ c> cal.setTime(calendarDate.getTime());

    • 这实际上是将字段表示转换为 strong>并使用此时间戳记初始化新日历

    • 新日历使用Julian系统表示日期,因为它早于1582年




有几种方法可以解决这个问题:




  • 使用JodaTime和 LocalDate#fromCalendarFiels 如果您只对日期感兴趣

  • 使用字段访问转换日历,而不是 #getTime 方法

  • 强制gregorian日历使用proleptic系统(与 XMLGregorianCalendar 正在这样做)






UPDATE 日期和日历API不是那么好设计,可以(有时)很混乱。这也是为什么Java 8包含完全重做的日期时间库 JSR-310 (基于JodaTime的方式)。 / p>

现在,您必须意识到,您可以通过两种不同的方法存储和处理特定的即时(日历独立关键字):




  • 存储来自被定义的称为epoch的定义的偏移(例如,以毫秒为单位)(例如unix epoch 1970-01-01

  • 按日期字段存储日期(例如1970年1月1日)



第一种方法是在 java.util.Date 。然而,这种表示通常是非人类友好的。人类使用日历日期,而不是时间戳。将时间戳转换为日期字段是日历所在的位置。这也是有趣的部分开始的地方...如果你想通过它的字段来表示日期,你需要意识到总是有多种方式如何做。有些国家可以决定使用农历月,有些人可能会说,0年只是10年前。



XMLGregorianCalendar vs GregorianCalendar >:




  • XML规范明确说明,人类可读的日期是一个 gregorian日历日期

  • Java的 GregorianCalendar 包含这个魔法,如果时间早于定义的切换日期

  • 这是为什么 XMLGregorianCalendar 在初始化期间修改 GregorianCalendar 以禁用此魔术开关
  • >


现在有趣的部分:



如果Julian开关不会被禁用, GregorianCalendar会假设日历字段来自Julian系统,它会将其移动3天。你认为日期已经改变了3天,有些事情必须出错了,对吧? 不,日期实际上是正确的,它包含正确的时间戳!只有日历给你朱利安字段,而不是格雷戈里领域。这是很混乱我会说:) [JSR-310在后台笑]。



所以如果你想使用纯gregorian日历(即使用so对于旧日期调用 proleptic gregorian ),您需要按如下方式初始化日历:

  Calendar.getInstance(); 
((GregorianCalendar)calendar).setGregorianChange(new Date(Long.MIN_VALUE));

您可能会说: calendar.getTime()仍然给我不正确的日期。嗯,这是因为 java.util.Date.toString()(由 System.out.println 调用)是使用默认的日历,对于较旧的日期将切换到Julian系统。困惑?可能生气了(我知道我是))?






UPDATE 2

  //获取XML gregorian日历
XMLGregorianCalendar xmlCalendar = DatatypeFactory.newInstance()。newXMLGregorianCalendar
xmlCalendar.setYear(1); //观察八进制数表示(你有0001)
xmlCalendar.setMonth(1);
xmlCalendar.setDay(1);

//转换为日历,因为它更容易使用
Calendar calendar = xmlCalendar.toGregorianCalendar(); //对于旧日期Proleptic

//转换为默认日历(会错误解释为Julian,但它是一种解决方法)
日历结果= 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());

错误:此代码错误(结果即时不一样作为XML文件中的一个),但OP了解问题及其后果(参见本答案下的讨论)。


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.

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.

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.

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:

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).

这篇关于从XMLGregorianCalendar转换为日历时的日期更改的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆