Java 神奇的00:09时区和DatatypeFactory
在java中,有一个Java 神奇的00:09时区和DatatypeFactory,java,xml,calendar,Java,Xml,Calendar,在java中,有一个javax.xml.datatype.DatatypeFactory,可用于导入和导出xml日期,如下所示 String xmlDateIn = "1900-01-01T12:00:00"; DatatypeFactory df = DatatypeFactory.newInstance(); XMLGregorianCalendar xmlCalendar = df.newXMLGregorianCalendar(xmlDateIn); String xml
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时区。)看看这个奇怪的时区
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();
}
当我尝试将其转换回XML时,9分钟的时区实际上也会被打印出来。所以,这不仅仅是一个内在的东西
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();
String xmlDateIn=“1900-01-01T12:00:00”;
DatatypeFactory df=DatatypeFactory.newInstance();
XMLGregorianCalendar xmlCalendar=df.newXMLGregorianCalendar(xmlDateIn);
xmlCalendar.setTimezone(0);// 我对日历历史和官方计时了解不多,所以我首先测试了一下,确定我使用的是您的时区:
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]
然后是Zulu(Z)和UTC+01:00之间的各种“手动”转换
因此,1900年的午夜实际上比1912年相应日期的午夜晚了9分21秒
事实上,如果你将年份改为1912年,你不会看到9分钟的差异:
String xmlDateIn = "1912-01-01T12:00:00";
我无法找到12:12或9:21转换的历史原因。我想这只是一个科学追赶的问题,因为天文测量变得更加精确。多亏了VGR的答案,我成功地确定了xmlgoriancalendarimpl内部的错误
看看构造函数:
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);
}
它的基本功能是将gregorianalendar
的所有内部字段映射到内部字段。e、 年、月、日、时、分、秒、毫秒
最后,它绘制了时区图。请注意,GregorianCalendar
对象以毫秒为单位存储其时区,而XMLGregorianCalendarImpl
以分钟为单位存储它在这个转换过程中,它只会删除剩余的秒和毫秒。这就是问题所在,日历实际上可以有一个带秒的时区
这就引出了以下例子:ECT时区中的日期为1900-01-01T12:00:00。它实际上有一个561000毫秒的区域偏移量。i、 e.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年,法国采用巴黎平均时间作为标准国家时间。火车站内的时钟和火车时刻表被设置为晚点五分钟,以防止乘客错过火车
1911年,巴黎的平均时间被修改为9分21秒,与格林威治的平均时间同步。它仍然被称为巴黎平均时间,避免了使用“格林威治”一词
来源:我无法使用Java 10.0.2重现您的结果。您使用的是哪个版本的Java?@VGR运行的是1.8.0 e u版本它似乎是该时区特有的。我在EST/EDT上,但当我将默认时区更改为CET时,我看到了您在Java 1.8.0_161和Java 10.0.2中描述的行为。有一件事困扰我进行更多的调试,那就是即使我在Date
的Date(long)
构造函数中设置了一个断点,cdate
字段被神奇地实例化。我找不到实例化的点。这可能是某种JVM优化吗?很好的分析(+1)。请注意,CET实际上是许多地区共享一个共同的缩写,大约每个中欧国家一个。哪一个表现出你的行为我不知道,但肯定不是全部。我更喜欢使用现代的ZoneId
和ZoneRules
类,而不是过时的TimeZone
(尽管它有更吸引人的名字)。现代的也更可靠。你的分析很出色。-这当然解释了魔法数字的来源。@OleV.V。我认为每年这个时候CET与GMT+2相当。但在冬天它变成了GMT+1。然后,欧洲人换表一小时。所以,在某种程度上,你可以说它可以是2个时区中的1个,但一次只能是1个。-这确实意味着多个欧洲国家都在效仿。e、 法国、荷兰、德国、意大利……和比利时(我居住的地方)的地图:@bvdb偏移量1900-01-01T12:00:巴黎+00:09:21,阿姆斯特丹+00:19:32,柏林+01:00,罗马相同,布鲁塞尔+00:00。它们不是同一时区。