【问题标题】:Some Time Zones return IllegalArgumentException某些时区返回 IllegalArgumentException
【发布时间】:2021-03-28 03:59:59
【问题描述】:

此代码示例使用 3 个时区(EST、PST、EET)。 对于每个时区,创建一个 Date 对象并运行 toString() 以打印出正在使用的格式。 然后这个相同的字符串值被传递给构造函数并用于创建一个新的日期对象。 代码会运行检查以确保所使用的时区有效。

所有 3 个时区(EST、PST、EET)均有效,但在创建对象时,仅为 EET 返回 java.lang.IllegalArgumentException。

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

这是输出。

基于 Java 11 文档,它确实注释了 Date 已被弃用并且应该使用 DateFormat.parse()。

作为测试,对象的代码被修改为使用 DateFormat.parse 但这只会让事情变得更糟。

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

这是新结果。

问题 1: JVM从哪个环境变量获取时区?

问题 2: 使用原始代码,为什么使用与JVM提供的格式相同的格式时会出现异常?

问题 3: EET 的具体原因是什么导致它在 EST 和 PST 中失败并在没有问题的情况下进行交换?

问题 4: 如果我想允许任何时区的任何人运行原始代码,需要更改什么?

编辑添加以下内容:

上面的代码是一个缩小的模型。不幸的是,不能在所有地方修改实际代码以更改使用 Date 对象。

我确实使用 MyObjectWithADate 对象中的 SimpleDateFormat 运行了另一个测试。这再次适用于 EST 和 PST,但不适用于 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");
        }
}   

我开始认为我最初的问题应该是,如何获取以下字符串

2021 年 3 月 26 日星期五 21:42:52 EET

并将其放入 Date 对象中。

【问题讨论】:

  • 完全不相关,不解决上述任何问题。对在代码中设置时区不感兴趣,也不对作为设置传入。
  • 您是说“3.1.设置环境变量”部分没有告诉您哪个环境变量设置时区?请不要这么立即不屑一顾。
  • 我建议你不要使用TimeZoneDateDateFormat。这些类设计不良且过时,尤其是最后一个以麻烦着称。而是使用java.time, the modern Java date and time API
  • Real time zones 具有Continent/Region 格式的名称,例如America/Los_AngelesAfrica/TunisPacific/Auckland。 EST、PST、EET 不是时区。

标签: java date exception timezone java.util.date


【解决方案1】:

EST、PST 和 EET 不是时区

  • EST 是东部标准时间的缩写,它又可以指澳大利亚东部标准时间和北美东部标准时间。
    • 在北美大部分地区的标准时间使用东部标准时间,而夏季时间 (DST) 使用东部夏令时间 (EDT),这是一年中的大部分时间。所以这个意义上的EST可以看作是半个时区。
    • 此外,在澳大利亚的某些时区,一年中的大部分时间都在使用 EST、EDT 或 AEDT。
  • PST 可能是皮特凯恩标准时间、太平洋标准时间和菲律宾标准时间的缩写。 所有北美使用太平洋标准时间的地方目前都是太平洋夏令时间。
  • EET 可能没有歧义,可能只是指东欧时间,这是几个时区的通用名称,其中大部分时间在一年中的大部分时间都在东欧夏令时。

Java 中的有效时区

在 Java 中,您应该使用时区 ID,例如澳大利亚/悉尼、太平洋/皮特凯恩和欧洲/布加勒斯特。总是地区/城市格式。它们表示明确的实时时区。在某些情况下,您可能能够说服 Java 将三个字母的缩写解释为时区,但由于它们经常模棱两可且通常不是实时时区,因此不鼓励这样做。此外,当被识别为时区 ID 时,它们也会被弃用。

要确定 Java 是否将给定的时区 ID 识别为有效,请使用 java.time 中的 ZoneId 类、现代 Java 日期和时间 API 及其 of 方法。例如:

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

输出:

EST is *not* a valid time zone

这将告诉您澳大利亚/悉尼、太平洋/皮特凯恩、欧洲/布加勒斯特以及 EET 是有效的时区。它还会告诉您 PST 不是。

对于使用不推荐使用的时区缩写的旧代码,ZoneId 通过其SHORT_IDS 常量对其中的某些提供了额外的支持。

    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

我们注意到,美国东部标准时间全年被理解为北美东部标准时间,而PST 在标准时间被理解为北美太平洋标准时间,而在夏季则被理解为太平洋夏令时间。没有SHORT_IDS 也支持 EET,所以这里不再赘述。

为什么您观察到令人惊讶的结果

Date.toString() 打印的缩写与ZoneIdTimeZone 识别的缩写是分开的。您注意到当PST 设置为默认时区时,Date 打印了PDT。编辑:另一个例子:PRC 是一个已弃用的时区 ID。 TimeZone 识别它。 Date 将其打印为 CST。 PRC代表中华人民共和国。 CST Date 表示中国标准时间,而TimeZoneCST 理解为北美中部标准时间。

已弃用的 Date(String) 构造函数可识别的唯一时区是 GMT、UT、UTC、EST、CST、MST、PST、EDT、CDT、MDT 和 PDT。这解释了为什么 EET 不起作用。 我相信这可以回答您的问题 2. 和 3。

SimpleDateFormat 识别的时区缩写是另一回事。它们是特定于语言环境的,因为每个语言环境对于给定的时区可能有自己的缩写。没有记录如何解释模棱两可的时区缩写,但已知它不稳定。在不同的 Java 安装中观察到相同缩写的不同结果。

最后,DateFormat.getInstance() 期望(或产生)的语法也是特定于语言环境的,所以我不希望它能够识别您的任何字符串。您的代码中也没有。例如,在我的语言环境中,DateFormat.getInstance() 需要一个类似 "27/03/2021 15.04" 的字符串。

无论如何,我建议您忘记 TimeZoneDateSimpleDateFormatDateFormat 类。它们都设计得很糟糕,而且已经过时了。我建议您将此作为问题 4 的部分答案。

动态设置 JVM 的默认时区也是非常危险的。该更改会影响您的程序的所有部分以及在同一 JVM 中运行的其他程序,因此您会冒着难以追踪到您的代码的意外风险。我建议你不要。

问题4:如果我想允许任何时区的任何人运行原始代码,需要更改什么?

首先,我建议您将 java.time 用于所有日期和时间工作。

如果您可以控制使用哪些时区 ID:

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

试试看:

    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]

如果您需要适应已弃用的时区缩写,我非常不确定该建议什么,因为非常不清楚用户期望它们是什么意思。如果有任何方法可以,请拒绝它们。如果您需要做出不确定的猜测,您可以尝试将SHORT_IDS 作为第二个参数添加到ZoneId.of()

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

请保持手指交叉并尝试:

    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]

至少结果是 100% 记录在案且可重现,并且独立于语言环境和默认时区。所以如果用户想要别的东西,应该可以改变它。

如果你需要 toString 将结果解析回来,ZonedDateTime 也可以稳定地做到这一点。并且没有任何格式化程序。并且独立于语言环境和默认时区。例如:

    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]

如果您不可避免地需要解析来自Date.toString() 的输出,但不确定这种尝试是否必然,请参阅底部的第二个链接。那里的好答案似乎将 EST 解释为 America/New_York,PST 解释为 America/Los_Angeles,EET 解释为 Europe/Bucharest。

链接

  1. Oracle tutorial: Date Time 解释如何使用 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]

【讨论】:

  • 我相信这个问题的根本问题是使用了已弃用的new Date(string) 方法。它不会为包含 PST 的字符串抛出异常,但会为 EET 抛出异常。谁知道为什么,谁真正在乎?
  • @jrook 我同意没有人需要关心。尽管如此,我已经编辑并指出这是记录在案的行为。
  • 很好的解释。另一个避免使用java.util.Date 的理由,以防有人仍然需要它。
  • 优秀。谢谢。
【解决方案2】:

问题 1:

这里是相关的doc

获取 Java 虚拟机的默认时区。如果缓存 默认时区可用,它的克隆被返回。否则,该 方法通过以下步骤来确定默认时区。

  • 使用 user.timezone 属性值作为默认时区 ID,如果 它可用。
  • 检测平台时区 ID。的来源 平台时区和 ID 映射可能因实施而异。
  • 使用 如果给定或检测到的时区 ID 是 GMT 作为最后的手段 未知。

问题 2、3 和 4:

当您使用已弃用的 new Date(myString) 函数时,将引发 EET 异常。我不确定这种方法的内部工作。但是为什么要打扰呢?它已被弃用是有原因的。

如果您想从输入字符串创建日期对象。 StackOverflow 上已经有很多关于这个的答案。无论如何,您真的不需要使用new Date(string)。这里有两个快速的解决方案来获得你想要的。我假设您希望将日期字符串格式化为 java.util.Date 将打印它。如果这不准确,请使用this link 创建您自己的格式。

1.使用java.util.Date(旧的,最好避免使用):

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

结果:

Fri Mar 26 20:52:40 EET 2021

2。使用新的java.time(首选):

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

结果:

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

或者对于第二种情况(给定字符串):

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

编辑

使用上面的第一个解决方案,需要注意时区。在 SimpleDateFormat 对象上调用 parse() 可能无法按预期工作。您可能会丢失时区信息。

【讨论】:

  • 谢谢,我确实附加了上面的示例以使用 SimpleDateFormat 并包含预期的格式。这再次适用于 EST 和 PST,但不适用于 EET。会不会是 EET 在幕后引发了冲突?
  • 我相信我已经在答案中部分解决了这个问题。 new Date(string) 已弃用,这意味着程序员强烈建议不要使用它。如果绝对没有其他选择,那么研究找出 EET 案件奇怪背后的原因是值得的。当有很多很多更好的解决方案可用时,我不明白这样做的意义。
  • 不幸的是,由于限制,仍然必须使用 Date 对象。我尝试了上面的第 1 项,但 EET 仍然失败。
  • 谢谢。我的代码有问题。我现在可以工作了。
  • @UnhandledException,其实第一种方案有问题。 java.util.Dateparse 方法之后删除时区信息,所以我们可能不得不使用setTimeZone()。我更新了我的答案。但是使用java.time 方法不需要进行此更改。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-26
  • 2020-07-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多