将日期在Java和.NET之间转换 - 减少2天

3

我需要将一个 .NET 的 DateTime 转换为等价的 Java Calendar 表示。

.NET 的 DateTime 使用自 0001 年 1 月 1 日(.NET 纪元)以来的 Ticks 作为底层表示。

Java 的 GregorianCalendar 使用自 1970 年 1 月 1 日(Java(或 Unix)纪元)以来的毫秒数。对于 Java 纪元之前的日期,该值为负数,这是预期的。

在这里,我将 DateTime 表示转换为自 Java 纪元以来的毫秒数:

var dt = new DateTime(1,2,3);  //way, way back.
var javaEpoch = new DateTime(1970, 1, 1);

var javaMillis = (dt - javaEpoch).Ticks / TimeSpan.TicksPerMillisecond;

dt.ToString("MM/dd/yyyy").Dump();          // .Dump() is provided by LinqPad. 
javaMillis.Dump();                         // Use Console.WriteLine(...)
                                           // for a regular console app.

这将输出:

02/03/0001
-62132745600000

现在将毫秒值复制粘贴到此Java代码片段中:

java.util.Calendar cal = new java.util.GregorianCalendar();
cal.setTimeInMillis(-62132745600000L);
java.text.SimpleDateFormat df = new java.text.SimpleDateFormat();
df.applyPattern("MM/dd/yyyy");
System.out.println(df.format(cal.getTime()));

这将输出:

02/05/0001

我的问题是:我应该如何从DateTime获取有效的毫秒值,以正确地构建Java日历?同时也有一个隐含的子问题:“这到底是怎么回事?”

编辑:我尝试使用DateTimeValues解决儒略历到格里高利历之间缺失日期范围的问题(1582年10月4日“后面”是1582年10月15日)。

对于比1582年10月15日更近的日期,转换似乎正常工作。

...但在缺失的范围周围,DateTime开始(或者说,没有开始)表现得很奇怪:

var timespan = new DateTime(1582, 10, 15) - new DateTime(1582, 10, 4);

返回一个TimeSpan,表示11天的时间间隔,因此DateTime运算符不考虑这个空洞。这是怎么回事呢?我以为底层实现基于System.Globalization.GregorianCalendar


如果您使用Joda Time会怎样呢?Joda Time可以改善标准Java日期,并且可能以一种简洁的方式解决您的问题。它还提供了转换为java.util.Date和java.util.Calendar的功能,因此,如果您确实需要使用标准Java日期,则可以使用Joda Time转换来解决您的问题。 - ThanksForAllTheFish
这有点像用大炮打苍蝇。我可以将Java位改为调用calendar.Set(year, month, day, ...)而不是calendar.SetTimeInMillis(),并从.NET DateTime对象的属性中获取每个字段。但我也想了解发生了什么。 - Cristian Diaconescu
顺便说一句,问题似乎出在.NET方面 - 伟大的JS已将他的库移植到.NET上:https://code.google.com/p/noda-time/(v1.1于几天前发布!) - Cristian Diaconescu
这有点像用大炮打苍蝇:是也不是。使用joda而不是java.util正在变得越来越普遍。从这个意义上说,使用joda总是好的。但我完全同意你句子的第二部分:“我也想了解发生了什么”。 - ThanksForAllTheFish
我曾认为Java 8关于日期处理的标准化过程正在朝着包括JSR 310 - http://threeten.sourceforge.net/的方向发展,而不是JodaTime。 - Cristian Diaconescu
这很有趣。我不知道。我更多地是在谈论行业中的事实标准。至少在我工作过或与之交谈过的公司中,joda time 是使用的日期库。 - ThanksForAllTheFish
3个回答

1
惊人的是,为什么在公历1582年10月4日至10月15日期间存在一个“空洞”,目前还没有人解答。有趣的答案在于1582年将公历改革为儒略历的历史中。详情请参见维基百科关于公历历法的文章。历史:格雷戈里改革段落的最后一段陈述:
  • 格雷戈里(教皇格雷戈里十三世)抛弃了10天,使得新历法重新和季节相符合。因此,当新历法开始使用时,自尼西亚公会议以来13个世纪累积的误差通过删除10天进行了修正。儒略历1582年10月4日(星期四)之后,第一天公历即为1582年10月15日(星期五),但星期循环未受影响。
实际上,公历1582年10月5日至10月14日这些日期不存在。我猜测.NET框架使用通用公式来处理1582年10月15日之前的日期,没有考虑到这种差异,而Java库则考虑了这一点。

+1 感谢您花时间添加解释。我知道这个漏洞,并认为它是常识。然而,我的问题不是“为什么有这个漏洞?”,而是“为什么.NET DateTime没有这个漏洞?”- 猜想这就是为什么历史方面直到现在才被提及的原因。 - Cristian Diaconescu
哦,顺便说一下,这些东西真是迷人 - Cristian Diaconescu

0
回答“为什么”的问题:
从(反编译-感谢dotPeek!).NET 4源代码中(注释是我的):
public static DateTime operator -(DateTime d, TimeSpan t)
{
  //range checks
  long internalTicks = d.InternalTicks;
  long num = t._ticks;
  if (internalTicks < num || internalTicks - 3155378975999999999L > num)
    throw new ArgumentOutOfRangeException("t", 
            Environment.GetResourceString("ArgumentOutOfRange_DateArithmetic"));

    else

    //plain arithmetic using the Ticks property of the two dates.
    return new DateTime((ulong) (internalTicks - num) | d.InternalKind);
}

所以,DateTime运算符绝对没有特殊的“公历”处理。

关于“如何修复”:

我最终使用了类似以下代码(伪Java)的东西:

Calendar cal = new GregorianCalendar();
cal.set(dt.Year, dt.Month - 1 /*it's 0-based*/, dt.Day, dt.Hour, dt.Minute, dt.Second);
cal.set(Calendar.MILLISECOND, dt.Millisecond);

0

来自Java文档

[...] 使用GregorianCalendar获取的日期仅从公元4年3月1日起历史准确,当时采用了现代儒略历规则。

考虑到您的DateTime减法,这只是一个Ticks差异,这里没有任何特定日历的概念。实现基本上是return new TimeSpan(x.Ticks - y.Ticks)

您可能最好只输出然后解析ISO-8061日期/时间,例如0001-02-03T00:00:00Z,它没有歧义,而不是依赖于内部表示。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接