代码之家  ›  专栏  ›  技术社区  ›  bvdb

神奇的00:09时区和DatatypeFactory

  •  2
  • bvdb  · 技术社区  · 7 年前

    在java中,有一个 javax.xml.datatype.DatatypeFactory 可用于导入和导出xml日期,如下所示。

      String xmlDateIn = "1900-01-01T12:00:00";
      DatatypeFactory df = DatatypeFactory.newInstance();
      XMLGregorianCalendar xmlCalendar = df.newXMLGregorianCalendar(xmlDateIn);
      String xmlDateOut = xmlCalendar.toXMLFormat();
    

    在这个简单的例子中 xmlDateIn 等于 xmlDateOut ,如预期。但如果我想把它当作 java.lang.Date 事情变得有趣起来。

      GregorianCalendar gregorianCalendar = xmlCalendar.toGregorianCalendar();
      Date dts = gregorianCalendar.getTime();
      System.out.println(dts);  // prints Mon Jan 01 12:00:00 CET 1900
    

    乍一看还是不错的,但实际上内部似乎有些东西坏了。通过我的IDE,我可以看到 Date 对象。 (如果你想知道的话,我住在CET时区。) 看看这个奇怪的时区。

    Timezone is broken

      DatatypeFactory df2 = DatatypeFactory.newInstance();
      GregorianCalendar gc2 = new GregorianCalendar();
      gc2.setTime(dts);
      XMLGregorianCalendar xc2 = df2.newXMLGregorianCalendar(gc2);
      System.out.println(xc2.toXMLFormat()); // prints 1900-01-01T12:00:00.000+00:09 
    

    为了解决这个问题,如果我手动设置时区,事情会变得非常糟糕。看看这个神奇的时刻:

      String xmlDateIn = "1900-01-01T12:00:00";
      DatatypeFactory df = DatatypeFactory.newInstance();
      XMLGregorianCalendar xmlCalendar = df.newXMLGregorianCalendar(xmlDateIn);
      xmlCalendar.setTimezone(0);  // <--- ONLY CHANGE
      GregorianCalendar gregorianCalendar = xmlCalendar.toGregorianCalendar();
      Date dts = gregorianCalendar.getTime();
    

    Unable to fix.

    实际上,对于我的特定程序,我有一个解决方法:现在对我有效的是,我在导入xml时不设置时区。这个 然后在内部携带错误的时区,即9分钟。当我最终想要导出 日期

    但真的,我想知道有没有什么好的解释来解释这种疯狂的行为。

    2 回复  |  直到 7 年前
        1
  •  2
  •   VGR    7 年前

    我对日历历史和官方计时了解不多,所以我首先测试了一下,确保我使用的是您的时区:

    int offset = (int) TimeUnit.HOURS.toMillis(1);
    String[] ids = TimeZone.getAvailableIDs(offset);
    TimeZone cet = Arrays.stream(ids).map(TimeZone::getTimeZone)
        .filter(tz -> tz.getDisplayName(false, TimeZone.SHORT).equals("CET"))
        .findFirst().orElseThrow(
            () -> new RuntimeException("No CET timezone found"));
    
    TimeZone.setDefault(cet);
    

    System.out.println("Transitions:");
    cet.toZoneId().getRules().getTransitions().forEach(
        t -> System.out.println("  " + t));
    

    前两个这样的转换打印为:

    Transition[Overlap at 1891-03-15T00:01+00:12:12 to +00:09:21]
    Transition[Overlap at 1911-03-11T00:00+00:09:21 to Z]
    

    所以,1900年的午夜实际上比1912年的午夜晚了9分21秒。

    String xmlDateIn = "1912-01-01T12:00:00";
    

    我还没有找到12:12或9:21转变的历史原因。我想这只是一个科学追赶的问题,因为天文测量越来越精确。

        2
  •  0
  •   Community Mohan Dere    6 年前

    XMLGregorianCalendarImpl

    看看构造器:

    public XMLGregorianCalendarImpl(GregorianCalendar cal) {
    
        int year = cal.get(Calendar.YEAR);
        if (cal.get(Calendar.ERA) == GregorianCalendar.BC) {
            year = -year;
        }
        this.setYear(year);
    
        // Calendar.MONTH is zero based, XSD Date datatype's month field starts
        // with JANUARY as 1.
        this.setMonth(cal.get(Calendar.MONTH) + 1);
        this.setDay(cal.get(Calendar.DAY_OF_MONTH));
        this.setTime(
                cal.get(Calendar.HOUR_OF_DAY),
                cal.get(Calendar.MINUTE),
                cal.get(Calendar.SECOND),
                cal.get(Calendar.MILLISECOND));
    
        // Calendar ZONE_OFFSET and DST_OFFSET fields are in milliseconds.
        int offsetInMinutes = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / (60 * 1000);
        this.setTimezone(offsetInMinutes);
    }
    

    基本上,它所做的,是映射 GregorianCalendar 到内部字段。e、 年、月、日、时、分、秒、毫秒。

    最后它绘制了时区。请注意 格雷戈里安卡伦达 对象以毫秒为单位存储其时区,而 XMLGregorianCalendarImpl格式 几分钟内就能储存。 在这个转换过程中,它只会删除剩余的秒和毫秒。 这就是问题所在,日历实际上可以有一个带秒的时区。

    这就引出了下一个日期的例子 "1900-01-01T12:00:00" 在ECT时区。它的区域偏移量实际上是561000毫秒。i、 9分21秒。但是xml gregorian忽略了21秒。

    如果时区有秒,最好输出没有时区的日期。

    private String toXml(Date dts) {
    
      DatatypeFactory df = DatatypeFactory.newInstance();
      GregorianCalendar gc = new GregorianCalendar();
      gc.setTime(dts);
      XMLGregorianCalendar xc = df.newXMLGregorianCalendar(gc2);
    
      int zoneOffsetInMillis = gc.get(Calendar.ZONE_OFFSET);
      boolean zoneHasMillis zoneOffsetInMillis % (60 * 1000) != 0;
      if (zoneHasMillis) xc.setTimezone(0);
    
      return xc.toXMLFormat();
    }
    

    1900-01-01T12:00:00+02:00 变成 1900-01-01T10:09:21.000

    实际上,我找到了这9分21秒时间变化的历史原因:

    英国在19世纪40年代引入了铁路时间,以使当地时钟与铁路时刻表同步,1880年被单一时间GMT所取代。1891年,法国采用巴黎平均时间作为标准国家时间。火车站内的时钟和火车时刻表被设置为晚点5分钟,以防止乘客错过火车。

    1911年,巴黎的平均时间被 9分21秒 与格林威治标准时间同步。它仍然被称为巴黎平均时间,避免了使用格林威治一词。

    https://vanessafrance.wordpress.com/2012/03/25/a-brief-history-of-french-time/