1

必须处理 SimpleDateFormat 但我对一周中的年份值有疑问。

为了缩小问题的范围,我在下面编写了简单的 Java 代码,发现它返回了两个不同的结果,并且设置明显相同(只是在命令行上强制本地)。问题仅出在 Windows(美国配置)机器上:如果我在 Linux(CentOS)机器上运行相同的测试,一切正常。

Windows 上的 JVM 是 zulu8 1.8.0_282 openjdk(但似乎我对 oracle 8 jdk 有相同的行为),而它是 Linux 上的 Red Hat 1.8.0_272 openjdk。

这是源代码:

import java.util.Locale;
import java.util.Calendar;
import java.util.TimeZone;
import java.text.SimpleDateFormat;
import java.text.DateFormat;
import java.text.ParseException;

import java.time.LocalDate;
import java.time.temporal.WeekFields;

public class TestDate {
    public static void main(String args[]) throws ParseException {
        Locale currentLocale = Locale.getDefault();

        System.out.println(System.getProperty("java.vendor"));
        System.out.println(System.getProperty("java.version"));
        System.out.println("==============");
        System.out.printf("%20s = %s%n", "getDisplayLanguage", currentLocale.getDisplayLanguage());
        System.out.printf("%20s = %s%n", "getDisplayCountry", currentLocale.getDisplayCountry());
        System.out.printf("%20s = %s%n", "getDisplayVariant", currentLocale.getDisplayVariant());

        System.out.printf("%20s = %s%n", "getLanguage", currentLocale.getLanguage());
        System.out.printf("%20s = %s%n", "getCountry", currentLocale.getCountry());

        System.out.printf("%20s = %s%n", "user.country", System.getProperty("user.country"));
        System.out.printf("%20s = %s%n", "user.language", System.getProperty("user.language"));
        System.out.printf("%20s = %s%n", "user.variant", System.getProperty("user.variant"));

        System.out.println("==============");

        Calendar c = Calendar.getInstance();
        System.out.println("1st day of week / minimal days in 1st week : " + c.getFirstDayOfWeek() + " / " + c.getMinimalDaysInFirstWeek());

        System.out.println("==============");

        LocalDate date1 = LocalDate.of(2020, 12, 31);
        LocalDate date2 = LocalDate.of(2021, 1, 1);

        DateFormat df_date = new java.text.SimpleDateFormat("dd/MM/yyyy");
        DateFormat df_week = new java.text.SimpleDateFormat("YYYY-ww");

        System.out.printf("%20s | %10s | %10s%n", "", df_date.format(java.sql.Date.valueOf(date1)), df_date.format(java.sql.Date.valueOf(date2)));
        System.out.printf("%20s | %10s | %10s%n", "SimpleDateFormat", df_week.format(java.sql.Date.valueOf(date1)), df_week.format(java.sql.Date.valueOf(date2)));

        System.out.printf("%20s | %7d-%02d | %7d-%02d%n", "WeekFields",
                                        date1.get(WeekFields.ISO.weekBasedYear()), date1.get(WeekFields.ISO.weekOfWeekBasedYear()),
                                        date2.get(WeekFields.ISO.weekBasedYear()), date2.get(WeekFields.ISO.weekOfWeekBasedYear()));

    }
}

这是结果(第二个是预期的):

>java TestDate
Azul Systems, Inc.
1.8.0_282
==============
  getDisplayLanguage = English
   getDisplayCountry = United States
   getDisplayVariant =
         getLanguage = en
          getCountry = US
        user.country = US
       user.language = en
        user.variant =
==============
1st day of week / minimal days in 1st week : 2 / 4
==============
                     | 31/12/2020 | 01/01/2021
    SimpleDateFormat |    2020-53 |    2020-53
          WeekFields |    2020-53 |    2020-53

>java -Duser.language=en -Duser.country=US -Duser.variant= TestDate
Azul Systems, Inc.
1.8.0_282
==============
  getDisplayLanguage = English
   getDisplayCountry = United States
   getDisplayVariant =
         getLanguage = en
          getCountry = US
        user.country = US
       user.language = en
        user.variant =
==============
1st day of week / minimal days in 1st week : 1 / 1
==============
                     | 31/12/2020 | 01/01/2021
    SimpleDateFormat |    2021-01 |    2021-01
          WeekFields |    2020-53 |    2020-53

两者似乎都使用相同的语言环境设置,但 SimpleDateFormat 返回不同的周/年。我错过了一些语言环境设置吗?

感谢您的帮助。

使用 Oracle JDK 编辑:

>java TestDate
Oracle Corporation
1.8.0_202
==============
  getDisplayLanguage = English
   getDisplayCountry = United States
   getDisplayVariant =
         getLanguage = en
          getCountry = US
        user.country = US
       user.language = en
        user.variant =
==============
1st day of week / minimal days in 1st week : 2 / 4
==============
                     | 31/12/2020 | 01/01/2021
    SimpleDateFormat |    2020-53 |    2020-53
          WeekFields |    2020-53 |    2020-53

>java -Duser.language=en -Duser.country=US -Duser.variant= TestDate
Oracle Corporation
1.8.0_202
==============
  getDisplayLanguage = English
   getDisplayCountry = United States
   getDisplayVariant =
         getLanguage = en
          getCountry = US
        user.country = US
       user.language = en
        user.variant =
==============
1st day of week / minimal days in 1st week : 1 / 1
==============
                     | 31/12/2020 | 01/01/2021
    SimpleDateFormat |    2021-01 |    2021-01
          WeekFields |    2020-53 |    2020-53

编辑日历默认语言环境: 正如 Scratte 所指出的,日历和 SimpleDateFormat 使用默认语言环境。我查看了SimpleDateFormat 源代码,它Locale.getDefault(Locale.Category.FORMAT)用作默认 Local ,结果与Locale.getDefault()我在代码中使用的不同。

我终于明白了为什么我在两个代码之间有两种不同的行为:我没有显示正确的区域设置(我不知道 3 个不同的区域设置;感谢 Ole VV 澄清这一点)。

TL;博士

SimpleDateFormat使用Locale.getDefault(Locale.Category.FORMAT)并且我的 Java 代码显示Locale.getDefault(). 后者总是en_US,但前者是fr_FRen_US取决于我使用的命令行。这就是为什么我每周/每年有两个不同的输出。

最后,JVM 参数-Duser.language= / -Duser.country= / -Duser.variant=是解决方案(它们强制所有三种不同的语言环境)!

这段新代码显示了三种不同语言环境的区别:

import java.sql.Date;
import java.util.Locale;
import java.util.Calendar;
import java.util.TimeZone;
import java.text.SimpleDateFormat;
import java.text.DateFormat;
import java.text.ParseException;

import java.time.LocalDate;
import java.time.temporal.WeekFields;

public class TestDate {
    public static void main(String args[]) throws ParseException {
        Locale cL = Locale.getDefault();
        Locale cLD = Locale.getDefault(Locale.Category.DISPLAY);
        Locale cLF = Locale.getDefault(Locale.Category.FORMAT);

        System.out.println(System.getProperty("java.vendor"));
        System.out.println(System.getProperty("java.version"));
        System.out.println("==============");
        System.out.printf("%20s | %15s | %15s | %15s%n", "Locale.getDefault(.)", "", "DISPLAY", "FORMAT");
        System.out.printf("%20s | %15s | %15s | %15s%n", "getDisplayLanguage", cL.getDisplayLanguage(), cLD.getDisplayLanguage(), cLF.getDisplayLanguage());
        System.out.printf("%20s | %15s | %15s | %15s%n", "getDisplayCountry", cL.getDisplayCountry(), cLD.getDisplayCountry(), cLF.getDisplayCountry());
        System.out.printf("%20s | %15s | %15s | %15s%n", "getDisplayVariant", cL.getDisplayVariant(), cLD.getDisplayVariant(), cLF.getDisplayVariant());
        System.out.printf("%20s | %15s | %15s | %15s%n", "getLanguage", cL.getLanguage(), cLD.getLanguage(), cLF.getLanguage());
        System.out.printf("%20s | %15s | %15s | %15s%n", "getCountry", cL.getCountry(), cLD.getCountry(), cLF.getCountry());
        System.out.printf("%20s | %15s | %15s | %15s%n", "getVariant", cL.getVariant(), cLD.getVariant(), cLF.getVariant());

        System.out.printf("%20s = %s%n", "user.country", System.getProperty("user.country"));
        System.out.printf("%20s = %s%n", "user.language", System.getProperty("user.language"));
        System.out.printf("%20s = %s%n", "user.variant", System.getProperty("user.variant"));

        System.out.println("==============");

        Calendar c = Calendar.getInstance();
        System.out.println("1st day of week / minimal days in 1st week : " + c.getFirstDayOfWeek() + " / " + c.getMinimalDaysInFirstWeek());

        System.out.println("==============");

        LocalDate date1 = LocalDate.of(2020, 12, 31);
        LocalDate date2 = LocalDate.of(2021, 1, 1);

        DateFormat df_date = new java.text.SimpleDateFormat("dd/MM/yyyy");
        DateFormat df_week = new java.text.SimpleDateFormat("YYYY-ww");

        System.out.printf("%20s | %10s | %10s%n", "", df_date.format(java.sql.Date.valueOf(date1)), df_date.format(java.sql.Date.valueOf(date2)));
        System.out.printf("%20s | %10s | %10s%n", "SimpleDateFormat", df_week.format(java.sql.Date.valueOf(date1)), df_week.format(java.sql.Date.valueOf(date2)));

        System.out.printf("%20s | %7d-%02d | %7d-%02d%n", "WeekFields",
                                        date1.get(WeekFields.ISO.weekBasedYear()), date1.get(WeekFields.ISO.weekOfWeekBasedYear()),
                                        date2.get(WeekFields.ISO.weekBasedYear()), date2.get(WeekFields.ISO.weekOfWeekBasedYear()));

    }
}

以及相应的输出:

>java TestDate
Azul Systems, Inc.
1.8.0_282
==============
Locale.getDefault(.) |                 |         DISPLAY |          FORMAT
  getDisplayLanguage |         English |         English |          French
   getDisplayCountry |   United States |   United States |          France
   getDisplayVariant |                 |                 |
         getLanguage |              en |              en |              fr
          getCountry |              US |              US |              FR
          getVariant |                 |                 |
        user.country = US
       user.language = en
        user.variant =
==============
1st day of week / minimal days in 1st week : 2 / 4
==============
                     | 31/12/2020 | 01/01/2021
    SimpleDateFormat |    2020-53 |    2020-53
          WeekFields |    2020-53 |    2020-53
>java -Duser.language=en -Duser.country=US -Duser.variant= TestDate
Azul Systems, Inc.
1.8.0_282
==============
Locale.getDefault(.) |                 |         DISPLAY |          FORMAT
  getDisplayLanguage |         English |         English |         English
   getDisplayCountry |   United States |   United States |   United States
   getDisplayVariant |                 |                 |
         getLanguage |              en |              en |              en
          getCountry |              US |              US |              US
          getVariant |                 |                 |
        user.country = US
       user.language = en
        user.variant =
==============
1st day of week / minimal days in 1st week : 1 / 1
==============
                     | 31/12/2020 | 01/01/2021
    SimpleDateFormat |    2021-01 |    2021-01
          WeekFields |    2020-53 |    2020-53
4

1 回答 1

0

我不明白 Talend ETL 的实施如何成为您的任何业务。如果他们还没有找到升级到 java.time(现代 Java 日期和时间 API)的机会,那是他们的问题,而不是您的问题。您不应该在自己的代码中使用SimpleDateFormatnor 。Calendar

Java 有 3 个默认语言环境

Java 不仅有一个,它还有三个默认语言环境,部分原因是历史原因。它们可以单独设置。展示:

    Locale.setDefault(Locale.FRANCE);
    Locale.setDefault(Locale.Category.DISPLAY, Locale.JAPAN);
    Locale.setDefault(Locale.Category.FORMAT, Locale.GERMANY);
    
    System.out.println(Locale.getDefault());
    System.out.println(Locale.getDefault(Locale.Category.DISPLAY));
    System.out.println(Locale.getDefault(Locale.Category.FORMAT));

此片段的输出是:

fr_FR
ja_JP
de_DE

输出依次反映法国、日本和德国(deutsch/Deutschland)。

您的评论指出代码SimpleDateFormat使用默认的 FORMAT 语言环境作为其默认语言环境(在我的示例中是德国)。也就是说,当您不指定时它使用的语言环境(您不应该使用SimpleDateFormat,如果您这样做,您应该始终明确指定语言环境)。

正如我所说,这三个可以单独设置。但是,单参数Locale.setDefault()设置了所有三个。

这个观察解释了吗?在我的 Java 11 上,似乎在命令行上设置语言环境会设置所有三个默认语言环境(直到被 更改Locale.setDefault())。我试过了

    System.out.println(Locale.getDefault());
    System.out.println(Locale.getDefault(Locale.Category.DISPLAY));
    System.out.println(Locale.getDefault(Locale.Category.FORMAT));

-Duser.language=en -Duser.country=US我在命令行上运行了这个片段,输出是:

en_US
en_US
en_US

其他语言和国家/地区设置也出现在所有三个语言环境中。所以不,这并不能单独解释为什么您SimpleDateFormat在一种情况下似乎没有从命令行获取语言环境。

这个观察是否提供了解决方案?

我仍然不明白你真正的最终目标是什么。第一个建议是:您的代码不应依赖于 JVM 的默认语言环境。在您的区域设置敏感操作中使用显式区域设置。

如果您确实需要为 Talend ETL 设置默认的 FORMAT 语言环境以按照您的要求工作,那么Locale.setDefault(Locale.Category.FORMAT, Locale.US);应该这样做。

关联

相关问题:哪个“默认语言环境”是哪个?

于 2021-09-25T07:33:26.173 回答