6

由于某些游戏开发可能会使用过去或将来的日期时间,并且在日期时间仿真的任何时区中,即使我们在现实中也无法到达它们。对于这个假设,并不是说我是在假date times 上计算,而是在任何时区的任何日期时间计算的正确性,就像在现实中一样。

我之前在Java中问了一个关于中国时区问题的问题,被认为是重复问题,因此我将其删除。但是,从这个评论线程中,我知道这是 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, 1, 8, 5, 51+0);
            Date somewhereInTime=MakeTime(1900, 1, 1, 8, 5, 51+1);
            DateTimeZone zone=DateTimeZone.forID("PRC");
            ShowDuration(null, retainsOfTheDay, somewhereInTime);
            ShowDuration(zone, retainsOfTheDay, somewhereInTime);
        }
    }
    

如果我从Java 中构造了LocalDateTimeJodaTime,则会出现问题。DateJDK的版本是7u17,JodaTime是2.2。在带有 NodaTime 的 C# 中不会发生这种情况,我在这篇文章的后面放置了一个替代版本的代码以进行对比。

  • 问题

    1. 过渡是如何发生的,与 Unix 纪元一样吗?

      我可能以错误的方式使用术语转换。我的意思是在 Java中减去1900/1/1 8:5:52by的奇怪结果。1900/1/1 8:5:51那时没有时区变化。

      像这样的事情只发生在特定的时区,还是所有的时区(可能在某个不同的时刻)?

    2. 如果有人可能date time在任何时区计算任意 s 并期望结果总是正确的,那么应该Date使用Calendar吗?

      如果是,如何在不出现问题的情况下使用它们?

      一旦我们可能在 1970 年之前或 2038 年之后计算s,我们是否应该不再在 Java 中使用Dateand ?Calenderdate time


替代代码

代码中同时包含了 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, 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();
    }

    public static DateTimeZone GetZoneFromId(String id) {
        return DateTimeZone.forID(id);
    }

    public static String GetYourZoneId() {
        return DateTimeZone.getDefault().getID();
    }

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

// 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 month, int day, int hour, int minute, int second) {
        return new Date(year, month, day, hour, minute, 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, Date subtrahend, 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, 1, 1, 8, 5, 51+0);
        Date somewhereInTime=ClockCant.MakeTime(1900, 1, 1, 8, 5, 51+1);
        DateTimeZone zone=ClockCant.GetZoneFromId("PRC");
        puts("Current Time Zone: "+ClockCant.GetYourZoneId());
        puts("Language: "+Language);
        ShowDuration(null, retainsOfTheDay, somewhereInTime);
        ShowDuration(zone, retainsOfTheDay, somewhereInTime);
    }

    static String Language="C#";
}

要在 Java 中编译,/*/请在代码开头添加 a,如下所示:

/*/// Like Java, like Sharp ... 
4

1 回答 1

9

我相信你的 Joda Time 测试被打破了,因为你让它变得不必要的复杂,基本上。这是一个简单的简短但完整的程序,展示了不同之处:

import java.util.*;
import org.joda.time.*;

public class ChinaTest {
    public static void main(String[] args) {
        DateTime startOf1900 = new DateTime(1900, 1, 1, 0, 0, 0, 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 开始之前的任何日期/时间视为该时区的“标准时间”。

于 2013-04-17T09:48:11.597 回答