我之前曾问过一个关于中国在Java中的时区问题的问题,这被认为是一个重复的问题,我就删除了它.但是,从这个comment thread开始,我知道Java中存在某种时间倒带(转换?)问题,而不仅仅是时区变化.
现在,我以不同的方式重新发布此问题,以便通过以下方式提出此问题:
> Java代码
import org.joda.time.*; import java.util.*; class PandaTest { static long Subtract( Date minuend,Date subtrahend,DateTimeZone zone) { long millis; if(null==zone) millis=minuend.getTime()-subtrahend.getTime(); else { long rhs= (new LocalDateTime(subtrahend)).toDateTime(zone) .getMillis(); long lhs= (new LocalDateTime(minuend)).toDateTime(zone) .getMillis(); millis=lhs-rhs; } return millis/1000; } static Date MakeTime( int year,int month,int day,int hour,int minute,int second ) { Calendar calendar= Calendar.getInstance(TimeZone.getTimeZone("PRC")); calendar.set(year,month-1,day,hour,minute,second); return calendar.getTime(); } static void puts(String arg0) { System.out.println(arg0); } static void ShowDuration(DateTimeZone zone,Date ... args) { int argc=args.length; puts("--- "); puts("Time Zone: "+(null!=zone?zone.toString():"unspecified")); for(int i=0; argc-->0; ++i) { puts("Time "+i+": "+args[i]); if(i>0) { long duration=Subtract(args[i],args[i-1],zone); puts("Duration = "+duration); } } } public static void main(String[] args) { Date retainsOfTheDay=MakeTime(1900,1,8,5,51+0); Date somewhereInTime=MakeTime(1900,51+1); DateTimeZone zone=DateTimeZone.forID("PRC"); ShowDuration(null,retainsOfTheDay,somewhereInTime); ShowDuration(zone,somewhereInTime); } }
如果我从Java的日期构造了JodaTime的LocalDateTime,则会出现问题. JDK的版本是7u17,JodaTime是2.2.在NodaTime的C#中不会发生这种情况,我在这篇文章的后面添加了替代版本的代码以进行对比.
>问题
>转换是如何发生的,它与Unix Epoch一样精确吗?
我可能以错误的方式使用术语转换.我的意思是在Java中以1900/1/1 8:5:51减去1900/1/1 8:5:52的奇怪结果.那时没有时区变化.
这样的事情只发生在特定时区或所有时区(可能在某个不同时刻)吗?
>如果可以在任何时区的任意日期时间计算,期望结果总是正确的,是否应该使用日期和日历?
如果是,如何在没有问题的情况下使用它们?
一旦我们计算出1970年之前或2038年之后的日期时间,我们是否应该再使用Java中的日期和日历?
替代准则
代码包含C#和Java中的内容,我们可以方便地对比C#和Java中的结果:
// Like Java,like Sharp ... the code contains content either in Java or C# // An odd number of `slash-star-slash` at the beginning to compile in Java // An even number of `slash-star-slash` at the beginning to compile in C# // p.s.: zero would be treated as an even number using Date=System.DateTime; using NodaTime.TimeZones; using NodaTime; using System.Collections.Generic; using System.Linq; using System; /*/ import org.joda.time.*; import java.util.*; // ClockCant in Java class ClockCant { public static Date MakeTime( int year,second); return calendar.getTime(); } public static DateTimeZone GetZoneFromId(String id) { return DateTimeZone.forID(id); } public static String GetYourZoneId() { return DateTimeZone.getDefault().getID(); } public static long Subtract( Date minuend,DateTimeZone zone) { long millis; if(null==zone) millis=minuend.getTime()-subtrahend.getTime(); else { long rhs= (new LocalDateTime(subtrahend)).toDateTime(zone) .getMillis(); long lhs= (new LocalDateTime(minuend)).toDateTime(zone) .getMillis(); millis=lhs-rhs; } return millis/1000; } } // a minimum implementation of C#-like List<T> for Java class List<T> { public T[] ToArray() { return _items; } public int Count() { return _items.length; } public List(T ... args) { _items=args; } T[] _items; } /*/// ClockCant in C# class ClockCant { public static Date MakeTime( int year,int second) { return new Date(year,month,second); } public static DateTimeZone GetZoneFromId(String id) { return DateTimeZoneProviders.Tzdb[id]; } public static String GetYourZoneId() { return DateTimeZoneProviders.Tzdb.GetSystemDefault().Id; } public static long Subtract( Date minuend,DateTimeZone zone) { long ticks; if(null==zone) ticks=minuend.Subtract(subtrahend).Ticks; else { var rhs= LocalDateTime.FromDateTime(subtrahend) .InZoneLeniently(zone); var lhs= LocalDateTime.FromDateTime(minuend) .InZoneLeniently(zone); ticks=(lhs.ToInstant()-rhs.ToInstant()).Ticks; } return ticks/TimeSpan.TicksPerSecond; } } // extension for Java-like methods in C# static partial class JavaExtensions { public static String toString(this Object x) { return x.ToString(); } } class PandaTest { /*/ class PandaTest { // in Java public static void main(String[] args) { Language="Java"; Main(args); } static void puts(String arg0) { System.out.println(arg0); } static void ShowDuration(DateTimeZone zone,Date ... args) { ShowDuration(zone,new List<Date>(args)); } /*/// in C# static void puts(String arg0) { Console.WriteLine(arg0); } static void ShowDuration(DateTimeZone zone,params Date[] args) { ShowDuration(zone,args.ToList()); } /**/// the following code are for both C# and Java static void ShowDuration(DateTimeZone zone,List<Date> args) { int argc=args.Count(); Date[] argv=args.ToArray(); puts("--- "); puts("Time Zone: "+(null!=zone?zone.toString():"unspecified")); for(int i=0; argc-->0; ++i) { puts("Time "+i+": "+argv[i]); if(i>0) { long duration=ClockCant.Subtract(argv[i],argv[i-1],zone); puts("Duration = "+duration); } } } static void Main(String[] args) { Date retainsOfTheDay=ClockCant.MakeTime(1900,51+0); Date somewhereInTime=ClockCant.MakeTime(1900,51+1); DateTimeZone zone=ClockCant.GetZoneFromId("PRC"); puts("Current Time Zone: "+ClockCant.GetYourZoneId()); puts("Language: "+Language); ShowDuration(null,somewhereInTime); } static String Language="C#"; }
要在Java中编译,请在代码的开头添加/ * /,如下所示:
/*/// Like Java,like Sharp ...
解决方法
import java.util.*; import org.joda.time.*; public class ChinaTest { public static void main(String[] args) { DateTime startOf1900 = new DateTime(1900,DateTimeZone.UTC); DateTime endOf1899 = startOf1900.minusMillis(1); DateTimeZone jodaZone = DateTimeZone.forID("Asia/Shanghai"); System.out.println("Joda at start of 1900: " + jodaZone.getOffset(startOf1900)); System.out.println("Joda at end of 1899: " + jodaZone.getOffset(endOf1899)); TimeZone javaZone = TimeZone.getTimeZone("Asia/Shanghai"); System.out.println("Java at start of 1900: " + javaZone.getOffset(startOf1900.getMillis())); System.out.println("Java at end of 1899: " + javaZone.getOffset(startOf1900.getMillis() - 1)); } }
输出:
Joda at start of 1900: 29152000 Joda at end of 1899: 29152000 Java at start of 1900: 29152000 Java at end of 1899: 28800000
所以基本上,Java的时区认为在1900年初有一个过渡,而Joda Time则没有.
正如我之前所写的那样,Java的时区实现基本上假设在1900年UTC开始之前不存在夏令时 – 所以在夏令时在1900年开始生效的任何时区,这代表了转换.
Joda Time和Noda Time都没有做出这样的假设,这就是你看到差异的原因.
这与Unix时代无关,也与2038无关.这意味着你应该期望java.util.TimeZone将1900 UTC开始之前的任何日期/时间视为该时区的“标准时间”.