Why Is Subtracting These Two Times (In 1927) Giving a Strange Result

Why is subtracting these two times (in 1927) giving a strange result?

It's a time zone change on December 31st in Shanghai.

See this page for details of 1927 in Shanghai. Basically at midnight at the end of 1927, the clocks went back 5 minutes and 52 seconds. So "1927-12-31 23:54:08" actually happened twice, and it looks like Java is parsing it as the later possible instant for that local date/time - hence the difference.

Just another episode in the often weird and wonderful world of time zones.

EDIT: Stop press! History changes...

The original question would no longer demonstrate quite the same behaviour, if rebuilt with version 2013a of TZDB. In 2013a, the result would be 358 seconds, with a transition time of 23:54:03 instead of 23:54:08.

I only noticed this because I'm collecting questions like this in Noda Time, in the form of unit tests... The test has now been changed, but it just goes to show - not even historical data is safe.

EDIT: History has changed again...

In TZDB 2014f, the time of the change has moved to 1900-12-31, and it's now a mere 343 second change (so the time between t and t+1 is 344 seconds, if you see what I mean).

EDIT: To answer a question around a transition at 1900... it looks like the Java timezone implementation treats all time zones as simply being in their standard time for any instant before the start of 1900 UTC:

import java.util.TimeZone;

public class Test {
public static void main(String[] args) throws Exception {
long startOf1900Utc = -2208988800000L;
for (String id : TimeZone.getAvailableIDs()) {
TimeZone zone = TimeZone.getTimeZone(id);
if (zone.getRawOffset() != zone.getOffset(startOf1900Utc - 1)) {
System.out.println(id);
}
}
}
}

The code above produces no output on my Windows machine. So any time zone which has any offset other than its standard one at the start of 1900 will count that as a transition. TZDB itself has some data going back earlier than that, and doesn't rely on any idea of a "fixed" standard time (which is what getRawOffset assumes to be a valid concept) so other libraries needn't introduce this artificial transition.

Why is the time difference off when subtracting the same date from two different time zones?

EET is not a time zone

EET is a common abbreviation of some 20 time zones in Europe and Northern Africa that are at offset +02:00 from UTC during standard time. Many, not all of them are using summer time (daylight saving time) abbreviated EEST for Eastern European Summer Time and hence are at offset +03:00 during the 7 “summer” months. So what you get when asking for EET, I can’t tell. So don’t do that. Use a proper time zone ID like Europe/Uzhgorod, always in region/city format. So that you know what you get.

So to answer your question:

Why does the EET & UTC actual time show 2 hour difference yet the Java
code shows a 3 hour difference?

Because they interpret EET differently. Both interpretations are fully valid and none is authoritative.

This is nothing special for EET. Many two, three, four and five letter time zone abbreviations are in common use. Very many of them are ambiguous, like EET. And very many of them are not time zones. It may be that there are situations where you can use one for informal communication to the user, for example just to distinguish between EET and EEST in a situation where the user knows well enough what each of those means in the context. You certainly cannot rely on them for defining a time zone in your program.

java.time

I recommend that you use java.time, the modern Java date and time API, for your date and time work. To find the difference between some time zone and UTC at a moment in time:

    ZoneId zone = ZoneId.of("Europe/Uzhgorod");
Instant when = Instant.now();

ZoneOffset differenceFromUtc = when.atZone(zone).getOffset();

System.out.format("%s & UTC Time Difference : %s%n", zone, differenceFromUtc);

Output when running just now:

Europe/Uzhgorod & UTC Time Difference : +03:00

It’s summer now in Europe/Uzhgorod, and the difference of 3 hours comes from that time zone using summer time. Try with an instant in winter:

    Instant when = Instant.parse("2022-01-01T00:00:00Z");

Europe/Uzhgorod & UTC Time Difference : +02:00

As I said, some Eastern European time zones do not use summer time. Try with one of those:

    ZoneId zone = ZoneId.of("Europe/Kaliningrad");
Instant when = Instant.now();

Europe/Kaliningrad & UTC Time Difference : +02:00

Minor comments to your code

While UTC works for defining a time zone, the canonical ID is Etc/UTC. Etc is a pseudo-region for time zones that don’t belong in a well-defined geographical region including Etc/UTC, Etc/GMT and also the IDs for mere whole-hour offsets like Etc/GMT-2 (for offset +02:00, the sign is inverted).

Assigning the result of your division to a float does not help you. You are dividing a long values by an int value, and the result is always truncated to an long number. When assigning to a float you will still always have a whole number. If you wanted a fraction as result, you would need to convert at least one of the operands to float (or double) before dividing.

And a funny detail, your code measures the difference in hours between when it’s 11 o’clock in EET and 11 o’clock in UTC. You get a result exactly because there are 3 hours between those two moments. What I am not so pleased about is that you are not measuring the difference at a well-defined point in time. Instead in my code I am choosing one point in time and finding the difference at that point.

Oracle tutorial: Date Time explaining how to use java.time.

Standard for specifying times that happened twice?

How do I tell you which 01:50 AM (EST) I am interested in on November 3rd this year?

By qualifying it as EST, you are already specifying that you mean "Eastern Standard Time" which is UTC-05:00. That is the second instance of 1:50am. The first instance occurred in "Eastern Daylight Time" (EDT) which is UTC-04:00. Of course, had you just said "Eastern Time", then I wouldn't know which one you meant.

However - keep in mind that "EST" by itself is not unique. I made a guess that you meant Eastern Standard Time in the USA - but you could have meant one of three different zones all called the same thing. See here for a list of time zone abbreviations. Now it happens to be that all "EST" zones are in UTC-05:00, but that is just coincidence. There are several zones that have the same name or abbreviation and have different offsets or different DST start/stop dates.

The "standard" you are looking for is part of ISO8601, which allows for dates and times to include an offset. For example, the two points mentioned would be:

2013-11-03T01:50:00-04:00

2013-11-03T01:50:00-05:00

Keep in mind that the offset uniquely identifies the moment/instant in time - but it it does not uniquely identify a time zone. There are several other time zones that use the -04:00 and -05:00 offsets at different times. This point is often forgotten, because the offset is sometimes referred to as the "zone" erroneously.

Weird date in post request when sending a date before 1927

You are using old dates, when timezones were not so standardized.

In you case, from Time Zone Database:

# Zone  NAME            GMTOFF  RULES   FORMAT  [UNTIL]
Zone Europe/Minsk 1:50:16 - LMT 1880
1:50 - MMT 1924 May 2 # Minsk Mean Time
2:00 - EET 1930 Jun 21
3:00 - MSK 1941 Jun 28
1:00 C-Eur CE%sT 1944 Jul 3
3:00 Russia MSK/MSD 1990
3:00 - MSK 1991 Mar 31 2:00s
2:00 1:00 EEST 1991 Sep 29 2:00s
2:00 - EET 1992 Mar 29 0:00s
2:00 1:00 EEST 1992 Sep 27 0:00s
2:00 Russia EE%sT 2011 Mar 27 2:00s
3:00 - FET 2014 Oct 26 1:00s
3:00 - MSK

As you see for Minsk, there were 1:50 differences between 1924 and 1930, but 1:50:16 from 1880 and 1924. Before that, there were not standardized time, usually every town had own solar time.

# Zone  NAME            GMTOFF  RULES   FORMAT  [UNTIL]
Zone Europe/Tallinn 1:39:00 - LMT 1880
1:39:00 - TMT 1918 Feb # Tallinn Mean Time
1:00 C-Eur CE%sT 1919 Jul
1:39:00 - TMT 1921 May
2:00 - EET 1940 Aug 6
3:00 - MSK 1941 Sep 15
1:00 C-Eur CE%sT 1944 Sep 22
3:00 Russia MSK/MSD 1989 Mar 26 2:00s
2:00 1:00 EEST 1989 Sep 24 2:00s
2:00 C-Eur EE%sT 1998 Sep 22
2:00 EU EE%sT 1999 Oct 31 4:00
2:00 - EET 2002 Feb 21
2:00 EU EE%sT


Zone Europe/Riga 1:36:34 - LMT 1880
1:36:34 - RMT 1918 Apr 15 2:00 # Riga MT
1:36:34 1:00 LST 1918 Sep 16 3:00 # Latvian ST
1:36:34 - RMT 1919 Apr 1 2:00
1:36:34 1:00 LST 1919 May 22 3:00
1:36:34 - RMT 1926 May 11
2:00 - EET 1940 Aug 5
(...)

# Zone NAME GMTOFF RULES FORMAT [UNTIL]
Zone Europe/Vilnius 1:41:16 - LMT 1880
1:24:00 - WMT 1917 # Warsaw Mean Time
1:35:36 - KMT 1919 Oct 10 # Kaunas Mean Time
1:00 - CET 1920 Jul 12
2:00 - EET 1920 Oct 9
1:00 - CET 1940 Aug 3
(...)
(etc. for other zones)

So just you get the correct offset from GMT.

java String == shown strange result

This is an implementation detail, but for a top level class, the result of getSimpleName() is the result of a substring operation, like

Class<?> cl = String.class;
String s = cl.getName();
s = s.substring(s.lastIndexOf('.') + 1);

Results of such string operations are never part of the string pool. So, to add it to the pool, an explicit invocation of intern() would be needed.

But intern() is not a cheap operation. This answer lists some of the problems, like requiring thread safe updates or (in case of the HotSpot JVM) having a fixed hash table size, hence becoming inefficient due to collisions when too many elements are added.

Since nothing is known about the caller’s intentions, it’s not known whether these costs would pay off. Prior to JDK 11, there was not even a caching so even String.class.getSimpleName() == String.class.getSimpleName() would evaluate to false. Now, there’s a cache (softly reachable, so it still could get garbage collected), but adding the string to the pool would still only pay off if there are other occurrences of string constants or explicitly interned strings of the same contents, which can not be assumed in advance.

This is a different situation with the qualified name or, more formally, the Binary Name to which the class will be permanently associated. This is the name used for lookups at class loaders and the likelihood of encountering Class.forName("full.qualified.ClassName") is higher than encountering the specific "ClassName" somewhere. This string is constructed by native code anyway, which goes a similar code path than the code resolving string constants.

So, String.class.getName() == "java.lang.String" always resolves to true with this implementation, but since this has not been specified in the documentation, you should not rely on it.



Related Topics



Leave a reply



Submit