سؤال

As some game developing might use the date time which is in the past or in the future, and in any time zone of a date time emulation, even we never reach them in reality. For this assumption, not saying that I'm calculating on fake date times, but for the correctness of calculation on any date time in any time zone like it was in reality.

I previously asked a question about the time zone issue of China in Java, which was regarded as a duplicate question, and I so deleted it. However, from this comment thread, I'm aware that it's some kind of time rewind(transition?) issue in Java, not just about the time zone change.

And now, I re-post this question in a different manner, to present this issue with the following:

  • Code in 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);
        }
    }
    

The problem occurs if I constructed a LocalDateTime of JodaTime from a Date of Java. The version of JDK is 7u17 and JodaTime is 2.2. It doesn't happen in C# with NodaTime, I put an alternative version of code at the rear of this post for contrasting.

  • Question

    1. How the transition occurs, is it exact as the Unix Epoch?

      I possibly use the term transition in a wrong way. What I mean is the strange result of subtracting 1900/1/1 8:5:52 by 1900/1/1 8:5:51 in Java. There isn't a time zone change at that time.

      Does something like this only occur in the specific time zone, or all of the time zones(maybe at some different instant)?

    2. If one would possibly calculate on arbitrary date times in any time zone expecting the result would always be correct, should Date and Calendar be used?

      If yes, how to use them without the issue occurs?

      Should we no longer to use Date and Calender in Java, once we possibly calculate on date times before 1970 or after 2038?


Alternative Code

The code contains content both in C# and Java, that we can contrast the result in C# and Java conveniently:

// 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#";
}

To compile in Java, add a /*/ at the beginning of the code as the following:

/*/// Like Java, like Sharp ... 
هل كانت مفيدة؟

المحلول

I believe your Joda Time test is broken because you've made it unnecessarily complex, basically. Here's a simple short but complete program demonstrating the difference:

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

Output:

Joda at start of 1900: 29152000
Joda at end of 1899: 29152000
Java at start of 1900: 29152000
Java at end of 1899: 28800000

So basically, Java's time zone thinks there's a transition at the start of 1900, whereas Joda Time doesn't.

As I've written previously, Java's time zone implementation basically assumes that daylight saving time didn't exist before the start of 1900 UTC - so in any time zone where daylight saving time is in effect at the start of 1900, that represents a transition.

Neither Joda Time nor Noda Time make this assumption, which is why you see a difference.

This has nothing to do with the Unix epoch, nor 2038. All it means is that you should expect java.util.TimeZone to treat any date/time before the start of 1900 UTC to be in "standard time" for that time zone.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top