草庐IT

java - 从XMLGregorianCalendar转换为Calendar时的日期更改

coder 2024-03-27 原文

在测试可在系统之间映射日期时间类型的Web服务时,我注意到在公历开始时间之前发送任何日期会导致转换为最终类型时准确性下降,最终结果总是在该范围内稍早一些几天。

我将问题缩小到确切的范围,但是我仍然无法弄清为什么要这样进行转换,从documentation中可以看出,儒略历用于公历开始前的日期时间:1582年10月15日。

问题行在从XMLGregorianCalendarGregorianCalendar的强制转换,第78行:calendarDate = argCal.toGregorianCalendar();从第86行的calendarDate提取时间时:cal.setTime(calendarDate.getTime());时间比原定时间提前2天,即1月03日而不是1月1日,如您从以下程序的输出中看到的那样。

这是我制作的一个示例程序,用来展示类型转换过程的端到端:

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);
        }
    }
}

最佳答案

摘自 XMLGregorianCalendar.toGregorianCalendar() JavaDoc,介绍了它们如何创建GregorianCalendar实例:

Obtain a pure Gregorian Calendar by invoking GregorianCalendar.setGregorianChange( new Date(Long.MIN_VALUE)).



这意味着,所创建的日历将具有丰富的功能,并且不会像默认的旧日期那样切换到朱利安日历。然后问题就在这里:
  • argCal.toGregorianCalendar()-使用字段表示形式(未使用Julian系统-参见上文)从XMLGregorianCalendar转换为GregorianCalendar (未使用Julian系统-参见上文)
  • cal.setTime(calendarDate.getTime());
  • 这实际上是将字段表示形式转换为时间戳表示形式,并使用该时间戳记
  • 初始化新日历
  • 新日历使用儒略历系统表示日期,因为该日期早于1582

  • 有几种解决方法:
  • 如果只对日期
  • 感兴趣,请使用JodaTime和 LocalDate#fromCalendarFiels
  • 使用字段访问而不是#getTime方法
  • 转换日历
  • 强制公历使用多产系统(与XMLGregorianCalendar的使用方式相同)


  • 更新请注意,Java Date和Calendar API的设计不是很好,有时可能会(并且确实)令人困惑。这也是Java 8包含完全重做的日期时间库JSR-310(顺便基于JodaTime)的原因。

    现在,您必须意识到,可以通过两种截然不同的方法来存储和使用特定的即刻(日历独立关键字):
  • 存储与定义良好的瞬间(称为epoch(例如unix epoch 1970-01-01))的偏移量(例如毫秒)
  • 按其日历字段存储日期(例如1970年1月1日)

  • 第一种方法是在java.util.Date的幕后使用的方法。但是,这种表示通常是非人类友好的。人类使用日历日期,而不是时间戳。 Calendar将介入将时间戳转换为日期字段。这也是有趣的部分开始的地方...如果要通过其字段表示日期,则需要意识到总是有多种方法来实现。一些国家可以决定使用阴历月份,而其他国家则可以说0年只是10年前。阳历只是将实际时间转换为实际日期字段的一种方法。

    关于XMLGregorianCalendar和GregorianCalendar的一些知识:
  • XML规范明确指出,人类可读的日期是公历日期
  • Java的GregorianCalendar包含此“魔术”,如果该时刻早于定义的转换日期
  • ,则将在后台转换为Julian系统
  • ,这就是XMLGregorianCalendar在初始化期间修改GregorianCalendar以禁用此魔术开关的原因(请参见上面JavaDoc的摘录)

  • 现在有趣的部分:

    如果不会禁用Julian开关,GregorianCalendar将假定日历字段来自Julian系统,并且将其转换3天。您以为日期已改了三天,肯定出了点问题,对吧? 不,日期实际上始终是正确的,并且包含正确的时间戳记!仅日历向您提供了朱利安字段而不是格里高利字段。我会说这很令人困惑:) [JSR-310在后台笑]。

    因此,如果您要使用纯公历(即使用所谓的多义公历来表示旧日期),则需要像这样初始化日历:
    Calendar calendar = Calendar.getInstance();
    ((GregorianCalendar) calendar).setGregorianChange(new Date(Long.MIN_VALUE));
    

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

    更新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:此代码是错误的(结果瞬间与XML文件中的瞬间不同),但是OP理解了问题及其后果(请参见此答案下的讨论)。

    关于java - 从XMLGregorianCalendar转换为Calendar时的日期更改,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16321193/

    有关java - 从XMLGregorianCalendar转换为Calendar时的日期更改的更多相关文章

    1. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

      我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

    2. ruby-on-rails - Ruby on Rails 迁移,将表更改为 MyISAM - 2

      如何正确创建Rails迁移,以便将表更改为MySQL中的MyISAM?目前是InnoDB。运行原始执行语句会更改表,但它不会更新db/schema.rb,因此当在测试环境中重新创建表时,它会返回到InnoDB并且我的全文搜索失败。我如何着手更改/添加迁移,以便将现有表修改为MyISAM并更新schema.rb,以便我的数据库和相应的测试数据库得到相应更新? 最佳答案 我没有找到执行此操作的好方法。您可以像有人建议的那样更改您的schema.rb,然后运行:rakedb:schema:load,但是,这将覆盖您的数据。我的做法是(假设

    3. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

      我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

    4. ruby - 将数组的内容转换为 int - 2

      我需要读入一个包含数字列表的文件。此代码读取文件并将其放入二维数组中。现在我需要获取数组中所有数字的平均值,但我需要将数组的内容更改为int。有什么想法可以将to_i方法放在哪里吗?ClassTerraindefinitializefile_name@input=IO.readlines(file_name)#readinfile@size=@input[0].to_i@land=[@size]x=1whilex 最佳答案 只需将数组映射为整数:@land边注如果你想得到一条线的平均值,你可以这样做:values=@input[x]

    5. ruby - 将散列转换为嵌套散列 - 2

      这道题是thisquestion的逆题.给定一个散列,每个键都有一个数组,例如{[:a,:b,:c]=>1,[:a,:b,:d]=>2,[:a,:e]=>3,[:f]=>4,}将其转换为嵌套哈希的最佳方法是什么{:a=>{:b=>{:c=>1,:d=>2},:e=>3,},:f=>4,} 最佳答案 这是一个迭代的解决方案,递归的解决方案留给读者作为练习:defconvert(h={})ret={}h.eachdo|k,v|node=retk[0..-2].each{|x|node[x]||={};node=node[x]}node[

    6. ruby-on-rails - 项目升级后 Pow 不会更改 ruby​​ 版本 - 2

      我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby​​版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby​​版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘

    7. ruby - Capistrano 3 在任务中更改 ssh_options - 2

      我尝试使用不同的ssh_options在同一阶段运行capistranov.3任务。我的production.rb说:set:stage,:productionset:user,'deploy'set:ssh_options,{user:'deploy'}通过此配置,capistrano与用户deploy连接,这对于其余的任务是正确的。但是我需要将它连接到服务器中配置良好的an_other_user以完成一项特定任务。然后我的食谱说:...taskswithoriginaluser...task:my_task_with_an_other_userdoset:user,'an_othe

    8. java - 等价于 Java 中的 Ruby Hash - 2

      我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

    9. ruby-on-rails - date_field_tag,如何设置默认日期? [ rails 上的 ruby ] - 2

      我想设置一个默认日期,例如实际日期,我该如何设置?还有如何在组合框中设置默认值顺便问一下,date_field_tag和date_field之间有什么区别? 最佳答案 试试这个:将默认日期作为第二个参数传递。youcorrectlysetthedefaultvalueofcomboboxasshowninyourquestion. 关于ruby-on-rails-date_field_tag,如何设置默认日期?[rails上的ruby],我们在StackOverflow上找到一个类似的问

    10. ruby-on-rails - Ruby 检查日期时间是否为 iso8601 并保存 - 2

      我需要检查DateTime是否采用有效的ISO8601格式。喜欢:#iso8601?我检查了ruby​​是否有特定方法,但没有找到。目前我正在使用date.iso8601==date来检查这个。有什么好的方法吗?编辑解释我的环境,并改变问题的范围。因此,我的项目将使用jsapiFullCalendar,这就是我需要iso8601字符串格式的原因。我想知道更好或正确的方法是什么,以正确的格式将日期保存在数据库中,或者让ActiveRecord完成它们的工作并在我需要时间信息时对其进行操作。 最佳答案 我不太明白你的问题。我假设您想检查

    随机推荐