-2

This code example uses 3 time zones (EST, PST, EET). For each Time Zone, a Date object is created and the toString() is run to print out the format being used. Then this same String value is passed to a Constructor and used to create a new Date Object. The code does run a check to ensure the Time Zone being used is valid.

All 3 Time Zones (EST, PST, EET) are valid but when creating the object, the java.lang.IllegalArgumentException is returned only for EET.

import java.util.*;
import java.text.*;

public class DateTest
{

   public static void main (String[] args)
   {
       
       System.out.println("=======Test 1 : using EST=======");
       isValidTimeZone("EST");
       TimeZone.setDefault(TimeZone.getTimeZone("EST"));
       runTest();
       
       System.out.println("=======Test 2 : using PST=======");
       isValidTimeZone("PST");
       TimeZone.setDefault(TimeZone.getTimeZone("PST"));
       runTest();

       System.out.println("=======Test 3 : using EET=======");
       isValidTimeZone("EET");
       TimeZone.setDefault(TimeZone.getTimeZone("EET"));
       runTest();
       
   }
   private static void isValidTimeZone(String tz)
   {
       String[] validIDs = TimeZone.getAvailableIDs();
       boolean validTZ = false;
       for (String str : validIDs) {
             if (str != null && str.equals(tz)) {
               validTZ = true;
               break;
             }
       }
       
       if (validTZ)
       {
           System.out.println(tz + " is a Valid Time Zone");
       }
       else
       {
           System.out.println(tz + " is **NOT** a Valid Time Zone");
       }
   }
   
   private static void runTest()
   {
       try
       {
        String myDateString = new Date().toString();
        System.out.println("     Default Date String : " + myDateString);
        MyObjectWithADate myObject = new MyObjectWithADate(new Date(myDateString));
       }
       catch(Exception e)
       {
           System.out.println("     Object NOT Created!!!!!");
          e.printStackTrace(System.out);
       }

   }
}

    public MyObjectWithADate (Date eventDate)
    {
        System.out.println("     Passed in Date :      " + eventDate.toString());
//      this.eventDate = eventDate;
        try {
            this.eventDate = DateFormat.getInstance().parse(eventDate.toString());
            System.out.println("     Object Created");
        } catch (ParseException e) {
            System.out.println("     Object NOT Created");
            e.printStackTrace();
        }
        
    }
}   

Here is the output.

enter image description here

Based on the Java 11 docs, it does comment that Date is deprecated and that DateFormat.parse() should be used.

As a test, the code for the Object was modified to use DateFormat.parse but this only made matters worse.

    public MyObjectWithADate (Date eventDate)
    {
        System.out.println("     Passed in Date :      " + eventDate.toString());
 //     this.eventDate = eventDate;
        try {
            this.eventDate = DateFormat.getInstance().parse(eventDate.toString());
            System.out.println("     Object Created");
        } catch (ParseException e) {
            System.out.println("     Object NOT Created");
            e.printStackTrace();
        }
        
    }
}   

Here are the new results.

enter image description here

Questions 1 : From which environment variable does the JVM obtain the timezone?

Questions 2 : Using the original code, why does the Exception occur when it is using the same format as the one provided by the JVM?

Questions 3 : What specifically is it about EET that causes it to fail yet EST and PST and be swapped without issues?

Questions 4 : If I want to allow the original code to be run by anyone in any Time Zone, what needs to be changed?

EDITED TO ADD THE FOLLOWING :

The above code is a scaled down model. Unfortunately the actual code can not be modified in all places to change from using the Date object.

I did run another test using the SimpleDateFormat within the MyObjectWithADate object. This once again works for the EST and PST but not the EET.

class MyObjectWithADate 
{
        private Date        eventDate;
        
        public MyObjectWithADate (Date eventDate)
        {
            System.out.println("     Passed in Date :      " + eventDate.toString());
            String datePattern = new String ("E MMM dd HH:mm:ss z yyyy");
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePattern);
            try {
                this.eventDate = simpleDateFormat.parse(eventDate.toString());
                System.out.println("     Object Created");
            } catch (ParseException e) {
                System.out.println("     Object NOT Created");
                e.printStackTrace();
            }
            System.out.println("     Object Created");
        }
}   

I am starting to think that my original question should have been, how to take the following String

Fri Mar 26 21:42:52 EET 2021

and place it into a Date object.

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
Unhandled Exception
  • 1,427
  • 14
  • 30
  • 1) https://www.baeldung.com/java-jvm-time-zone – Andy Turner Mar 26 '21 at 18:11
  • Totally unrelated and doesn't address any of the above questions. Not interested in setting the Time Zone within the code nor passing in as a setting. – Unhandled Exception Mar 26 '21 at 18:13
  • 1
    You're saying that the section "3.1. Setting an Environment Variable" doesn't tell you which environment variable sets the time zone? Please don't be so immediately dismissive. – Andy Turner Mar 26 '21 at 18:44
  • 1 - Since the TZ is not set in my environment I was curious as to where the format was coming from but have since found this is actually the Date objects default format. – Unhandled Exception Mar 26 '21 at 18:50
  • 2 - In the above code, it explicitly sets the Time Zone for EST and PST and behaves as expected. But that link provide gives no indication as to why doing the same with EET fails. All 3 scenarios it uses the default format and prints out the default format yet only recognizes 2 or the 3 when using that value to create a new Date. – Unhandled Exception Mar 26 '21 at 18:51
  • 2
    I recommend you don’t use `TimeZone`, `Date` and `DateFormat`. Those classes are poorly designed and long outdated, the last in particular notoriously troublesome. Instead use [java.time, the modern Java date and time API](https://docs.oracle.com/javase/tutorial/datetime/). – Ole V.V. Mar 26 '21 at 19:04
  • 2
    [Real time zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) have a name in `Continent/Region` format like `America/Los_Angeles`, `Africa/Tunis`, and `Pacific/Auckland`. EST, PST, EET are not time zones. – Basil Bourque Mar 26 '21 at 20:04

2 Answers2

4

EST, PST and EET are not time zones

  • EST is an abbreviation of Eastern Standard Time, which in turn may refer to either Australian Eastern Standard Time and North American Eastern Standard Time.
    • In most of the places in North America using EST during standard time, Eastern Daylight Time (EDT) is used during summer time (DST), which is the greater part of the year. So EST in this sense may be regarded as half a time zone.
    • Also in some time zones in Australia observing EST, EDT or AEDT is used for the greater part of the year.
  • PST may be an abbreviation for Pitcairn Standard Time, Pacific Standard Time and Philippines Standard Time. All of the places in North America using Pacific Standard Time are currently on Pacific Daylight Time.
  • EET may not be ambiguous and may just refer to Eastern European Time, a common name for several time zones most of which are on Eastern European Summer Time during most of the year.

Valid time zones in Java

In Java you should use time zone IDs like Australia/Sydney, Pacific/Pitcairn and Europe/Bucharest. Always region/city format. They denote unambiguous, real time zones. You may in some cases be able to persuade Java to interpret a three letter abbreviation as a time zone, but since they are so often ambiguous and so often not real time zones, this is discouraged. Also when recognized as time zone IDs, they are deprecated as such.

To determine whether Java recognizes a given time zone ID as valid, use the ZoneId class from java.time, the modern Java date and time API, and its of method. For example:

    try {
        ZoneId.of("EST");
        System.out.println("EST is a valid time zone");
    } catch (ZoneRulesException zre) {
        System.out.println("EST is *not* a valid time zone");
    }

Output:

EST is *not* a valid time zone

This will tell you that Australia/Sydney, Pacific/Pitcairn, Europe/Bucharest and also EET are valid time zones. It will also tell you that PST is not.

For use with old code that uses deprecated time zone abbreviations ZoneId has additional support for certain of those through its SHORT_IDS constant.

    System.out.println("" + ZoneId.SHORT_IDS.containsKey("EST")
            + " -> " + ZoneId.SHORT_IDS.get("EST"));
    System.out.println("" + ZoneId.SHORT_IDS.containsKey("PST")
            + " -> " + ZoneId.SHORT_IDS.get("PST"));
    System.out.println("" + ZoneId.SHORT_IDS.containsKey("EET")
            + " -> " + ZoneId.SHORT_IDS.get("EET"));
true -> -05:00
true -> America/Los_Angeles
false -> null

We notice that inconsistently EST is understood as North American Eastern Standard Time all year while PST is understood as North American Pacific Standard Time during standard time and Pacific Daylight Time during summer. EET was supported without SHORT_IDS, so is not redundantly repeated here.

Why you observed surprising results

The abbreviations printed by Date.toString() are separate from those recognized by ZoneId and TimeZone. You noticed that PDT was printed for the Date when PST was set as default time zone. Edit: A different example: PRC is a deprecated time zone ID. TimeZone recognizes it. Date prints it as CST. PRC was for People’s Republic of China. By CST Date meant China Standard Time while TimeZone would have understood CST as North American Central Standard Time.

The only time zones documented to be recognized by the deprecated Date(String) constructor are GMT, UT, UTC, EST, CST, MST, PST, EDT, CDT, MDT, and PDT. This explains why EET did not work. I believe that this answers your questions 2. and 3.

Time zone abbreviations recognized by SimpleDateFormat are yet a different story. They are locale specific since each locale may have its own abbreviation/s for a given time zone. How ambiguous time zone abbreviations are interpreted is not documented, but it is known not to be stable. Different results for the same abbreviation have been observed on different Java installations.

Finally which syntax is expected (or produced) by DateFormat.getInstance() is locale specific too, so I would not expect this to recognize any of your strings. Which it also didn’t in your code. In my locale DateFormat.getInstance() expects a string like "27/03/2021 15.04", for example.

In any case I recommend you forget about the classes TimeZone, Date, SimpleDateFormat and DateFormat. They are all poorly designed and long outdated. And I recommend that you take this as a partial answer to question 4.

Also setting the default time zone of your JVM dynamically is very dangerous. The change affects all parts of your programs and other programs running in the same JVM, so you risk surprises that will be very hard to track back to your code. I recommend you don’t.

Questions 4 : If I want to allow the original code to be run by anyone in any Time Zone, what needs to be changed?

First of all I recommend that you use java.time for all of your date and time work.

If you can control which time zone IDs are used:

public static void test(String timeZoneId) {
    try {
        ZonedDateTime now = ZonedDateTime.now(ZoneId.of(timeZoneId));
        System.out.println(now);
    } catch (ZoneRulesException zre) {
        System.out.println("Not a valid time zone ID: " + timeZoneId);
    }
}

Try it out:

    test("Australia/Sydney");
    test("Pacific/Pitcairn");
    test("Europe/Bucharest");
2021-03-27T07:49:27.095653+11:00[Australia/Sydney]
2021-03-26T12:49:27.100300-08:00[Pacific/Pitcairn]
2021-03-26T22:49:27.101219+02:00[Europe/Bucharest]

If you need to accommodate deprecated time zone abbreviations I am very unsure what to suggest since it is so very unclear what a user would expect them to mean. If there’s any way you can, reject them. If you need to make an uncertain guess, you may try adding SHORT_IDS as a second argument to ZoneId.of():

        ZonedDateTime now = ZonedDateTime.now(ZoneId.of(timeZoneId, ZoneId.SHORT_IDS));

Keep your fingers crossed and try:

    test("EST");
    test("PST");
    test("EET");
2021-03-26T15:55:15.225727-05:00
2021-03-26T13:55:15.363421-07:00[America/Los_Angeles]
2021-03-26T22:55:15.367676+02:00[EET]

At least the result is 100 % documented and reproducible and independent of locale and default time zone. So if a user wants something else, it should be possible to change it.

If you need to toString the result and parse it back, ZonedDateTime can also do that stably. And without any formatter. And independently of locale and default time zone. For example:

    ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("America/Atikokan"));
    System.out.println("Original:    " + zdt);
    String asString = zdt.toString();
    ZonedDateTime parsedBack = ZonedDateTime.parse(asString);
    System.out.println("Parsed back: " + parsedBack);
Original:    2021-03-26T16:00:27.867880-05:00[America/Atikokan]
Parsed back: 2021-03-26T16:00:27.867880-05:00[America/Atikokan]

If you indispensably need to parse the output from Date.toString(), however uncertain such an attempt necessarily is, see the second link at the bottom. The good answer there seems to interpret EST as America/New_York, PST as America/Los_Angeles and EET as Europe/Bucharest.

Links

  1. Oracle tutorial: Date Time explaining how to use java.time.
  2. How to convert Java String “EEE MMM dd HH:mm:ss zzz yyyy”date type to Java util.Date “yyyy-MM-dd” [duplicate]
Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • I believe the underlying issue in this question is using the deprecated `new Date(string)` method. It does not throw an exception for strings containing PST, but does so for EET. Who knows why, and who really cares? – jrook Mar 26 '21 at 20:18
  • @jrook I agree that no one needs to care. Nevertheless I have edited and noted that this is documented behaviour. – Ole V.V. Mar 26 '21 at 20:30
  • 1
    Great explanation. Yet another reason to avoid `java.util.Date` in case anyone still needs one. – jrook Mar 26 '21 at 20:32
  • Outstanding. Thank you. – Unhandled Exception Mar 26 '21 at 21:20
2

Question 1:

Here is the relevant doc

Gets the default TimeZone of the Java virtual machine. If the cached default TimeZone is available, its clone is returned. Otherwise, the method takes the following steps to determine the default time zone.

  • Use the user.timezone property value as the default time zone ID if it's available.
  • Detect the platform time zone ID. The source of the platform time zone and ID mapping may vary with implementation.
  • Use GMT as the last resort if the given or detected time zone ID is unknown.

Questions 2 , 3 and 4:

The exception for EET is thrown when you use the deprecated new Date(myString) function. I am not sure about the internal working of this method. But why bother? It is deprecated for a reason.

If you want to create a date object from an input string. There are a ton of answers about this on StackOverflow already. You really don't need to use new Date(string) anyways. Here are two quick solutions to get what you want. I assume you want your date string to be formatted as java.util.Date would print it. If this is not accurate, use this link to create your own format.

1. Using java.util.Date (old and better to avoid):

SimpleDateFormat oldJavaDateFormat = new SimpleDateFormat("EEE LLL dd HH:mm:ss z yyyy");
TimeZone.setDefault(TimeZone.getTimeZone("EET"));

//Surround with try/catch or throw
Date eventDate = oldJavaDateFormat.parse(new Date().toString());
//or if you need a date from some given date string
oldJavaDateFormat.parse("Thu Jan 13 15:13:13 EET 2033");

Result:

Fri Mar 26 20:52:40 EET 2021

2. Using the new java.time (preferred):

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE LLL dd HH:mm:ss z yyyy");
ZonedDateTime eventDate = ZonedDateTime.parse(new Date().toString(), formatter).withZoneSameLocal(ZoneId.of("EET"));
//or directly from a string
eventDate = ZonedDateTime.parse("Thu Jan 13 15:13:13 EET 2033", formatter);

Result:

2021-03-26T20:52:40+02:00[EET]

or for the second case (given string):

2033-01-13T15:13:13+02:00[Europe/Bucharest]

EDIT:

Using the first solution above, one needs to be careful about timezones. Invoking parse() on a SimpleDateFormat object may not work as intended. You may lose timezone information.

jrook
  • 3,459
  • 1
  • 16
  • 33
  • Thanks, I did append my above example to use the SimpleDateFormat and included the expected format. Once again this worked for the EST and PST but not for EET. Could there be something about the EET behind the scenes that causes a conflict? – Unhandled Exception Mar 26 '21 at 19:41
  • I believe I have partially addressed this in the answer. `new Date(string)` is deprecated, meaning programmers are *strongly discouraged* from using it. If there were absolutely no other alternatives, researching to find the reason behind the oddity for EET case was worthwhile. I don't understand the point of doing so when there are much, much, much better solutions available. – jrook Mar 26 '21 at 19:50
  • Unfortunately due to constraints, the Date object still has to be used. I attempted your Item 1 above and it still fails for EET. – Unhandled Exception Mar 26 '21 at 19:53
  • Thanks. There was an issue with my code. I have it working now. – Unhandled Exception Mar 26 '21 at 20:09
  • 1
    @UnhandledException, There is actually a problem with the first solution. `java.util.Date` removes the timezone info after `parse` method, so we may have to use `setTimeZone()` anyways. I updated my answer. But there is no need for this change using `java.time` method. – jrook Mar 26 '21 at 20:11