在Java中解析日期时间字符串需要“Locale”吗?

  • 在什么条件下我需要一个Locale来解析Java中的日期时间字符串?
  • Locale与时区有什么关系?

有时我会看到问题与解答,解决问题的解决方案需要Locale。 然而在其他人中没有提到Locale。

区域设置和时区不相关

区域设置和时区是与日期时间处理相关的单独的正交问题。

  • 语言环境
    • 语言
      人类语言,如阿拉伯语 , 法语 , 波斯语 。 星期几,月份名称和序数指标的名称文本。 例如……是Monday还是Lundi
    • 文化
      常用的习语用于排列文本和数字,构成日期时间值的字符串表示。 例如……在简短forms中,是month-date-yeardate-month-year还是year-month-date ? 从长远来看,一周中的哪一天是第一位的吗? 月份名称是初始上限还是全部小写? 缩写是否具有FULL STOP(PERIOD)字符?
  • 时区
    • 抵消
      UTC ( 格林威治标准时间 )中某个地区人们使用的挂壁时间与世界调节时钟和时间的主要时间标准之间的小时和分钟数之差。
    • exception
      对偏移量进行更改的历史记录,定义偏移量的当前应用规则(包括夏令时(DST)等调整)以及近期未来变更的确认计划。

因此,您可以混合使用区域设置和时区。 一些例子如下。

  • 一位参加印度浦那会议的法国男子需要在印度的壁垒时间看会议时间表,但更喜欢将“周一”称为“本地法语”“Lundi”。
    • 法国语言
    • 印度时区
  • 在西雅图工作的一位巴西工程师希望观看芬兰图尔库的现场网络研讨会。 她需要知道何时将她的网络浏览器指向网络研讨会。 在调整到西雅图时区之后,她需要知道芬兰的开始时间,但是她的母语为葡萄牙语。
    • 用于演示的区域设置(“pt”,“BR”) (用于生成文本表示)
    • 芬兰的预定开始时间必须从Europe/Helsinki调整到America/Los_Angeles (西雅图时区)。
  • 冰岛的一家报纸可能会报道在俄罗斯发生的事件为两个日期时间,即莫斯科时区,并且为了清晰添加UTC时区。 但该文章将使用冰岛语言作为包括星期几在内的文本。
    • 莫斯科时区和冰岛地区
    • UTC时区和冰岛区域

解析/生成作为日期时间值的文本表示的字符串时,仅在以下两种情况下使用Locale:

  • 星期几的名称和/或月份名称(或序号指标,但最好避免这些)
  • 软编码的本地化格式

在第一种情况下,如果您的字符串包含“Monday”/“Lundi”或“March”/“Mars”等字词,则使用Locale来翻译这些字符串。

在第二种情况下,如果您没有明确的格式设置模式,则使用区域设置以了解星期几,日期,月份名称,年份等部分的预期顺序。 。 例如,说英语的美国人说“10月11日”,讲法语的加拿大人使用逆序“11 octobre”。 通过软编码,我们的意思是DateTimeFormatter.ofLocalizedDateTime( FormatStyle.FULL )与硬编码格式,如DateTimeFormatter.ofPattern("yyyy MM dd, EEE")

那么什么时候不需要Locale? 如果您有一个包含所有数字的输入字符串,例如“2015-01-23”,并且您将格式硬编码为“yyyy-MM-dd”…

 String input = "2015-01-23"; DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyy-MM-dd") 

…然后Locale实际上是无关紧要的。 你无法翻译,没有“星期一”或“伦迪”。 并且不要求使用需要区域设置的本地化格式化程序来了解日期是在月份之前还是之后,以及其他此类详细信息。

请注意,您仍然可以在这种情况下指定区域设置。 实际上,我建议你养成一直习惯于指定Locale (以及时区)。

隐含的区域设置和时区

那么为什么你在没有任何语言环境的StackOverflow上看到这么多与日期时间相关的问题和答案呢? 因为如果省略,JVM的当前默认语言环境将自动且无提示地应用。

因此,如果您在JVM设置为美国语言环境的情况下运行英语文本的字符串,那么没问题。 但是不推荐这种对隐式语言环境的依赖。 如果在运行时期间任何应用程序的任何线程中的任何代码调用Locale.setDefault ,并影响该JVM中的所有其他代码。 然后你的代码抛出exception。 最好养成明确指定期望/期望区域设置的习惯。

时区的建议相同。 如果省略,则会自动且无提示地应用JMV的当前默认时区。 同样, 运行时期间任何应用程序的任何线程中的任何代码都可以调用TimeZone.setDefault ,并影响该JVM中的所有其他代码。 然后您的代码抛出exception或意外行为。

在运行时的惊喜变化应该是足够的理由养成始终指定区域设置和时区的习惯。 但另一个好处是它还使您的代码自我记录。 此外,在编程时有意识地指定区域设置和时区可能会提醒您错误或未经证实的假设。

示例场景

想象一下魁北克的商人。 她向土耳其的一位客户确认他将在中午时分发货。 因此,她使用壁挂时间创建一个物体,在土耳其接受交付。

 ZoneId zoneIdIstanbul = ZoneId.of( "Europe/Istanbul" ); ZonedDateTime zdtIstanbul = ZonedDateTime.of( 2015, 10, 11, 12, 30, 00, 0, zoneIdIstanbul ); // Half-past noon in Turkey. 

为方便客户,她使用土耳其语和海关格式化文本。 她定义了一个格式化程序对象来处理日期时间值的文本表示的生成。 我们还可以在生成文本表示时为要应用的格式化程序分配时区。 但是ZonedDateTime对象已经分配了时区,因此格式化程序将在该时区上进行拾取。

 Locale locale_tr_TR = new Locale( "tr", "TR" ); DateTimeFormatter formatter_tr_TR = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.FULL ).withLocale( locale_tr_TR ); String outputTurkish = formatter_tr_TR.format( zdtIstanbul ); 

我们的商人知道客户在芬兰使用物流协调员,所以她用芬兰语打印相同的日期时间值。 所以我们有一个芬兰语区域的土耳其时区 。

 Locale locale_fi_FI = new Locale( "fi", "FI" ); DateTimeFormatter formatter_fi_FI = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.FULL ).withLocale( locale_fi_FI ); String outputFinnish = formatter_fi_FI.format( zdtIstanbul ); 

对于她自己来说,她需要一个在她自己的墙上时间预期交付的字符串,这样她就可以设置警报以提醒她检查成功完成。 她本地读法语,而不是土耳其语。

所以下一个代码的不同之处在于我们需要调整时区语言环境。 时间轴中的相同时刻,即预期交付的日期时间,但在文本中表示不同。 注意这次我们如何在链的末尾添加一个额外的withZone调用来创建一个格式化程序,我们指定一个时区调整来覆盖ZonedDateTime对象的指定区域。

 Locale locale_fr_CA = Locale.CANADA_FRENCH; ZoneId zoneId_Montréal = ZoneId.of( "America/Montreal" ); DateTimeFormatter formatter_fr_CA_Adjusted = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.FULL ).withLocale( locale_fr_CA ).withZone( zoneId_Montréal ); String outputQuébec = formatter_fr_CA_Adjusted.format( zdtIstanbul ); 

最后,为了我们讲英语的StackOverflow.com读者,让我们用英语做一个版本。 但请注意,我们回收魁北克格式化程序,保留已设置的时区,但将语言环境替换为美国的语言环境。 (技术上不是回收,但可以这么说。使用不可变对象意味着使用基于旧对象的值来实例化新对象。)

 Locale locale_en_US = Locale.US; DateTimeFormatter formatter_US_Unadjusted = formatter_fr_CA_Adjusted.withLocale( locale_en_US ); String output_US_Unadjusted = formatter_US_Unadjusted.format( zdtIstanbul ); 

让我们看看这些值的输出。 转储到控制台。

首先,我们在ZonedDateTime对象上隐式调用toString方法。 默认情况下,此方法使用ISO 8601定义的标准格式之一。 但java.time通过在方括号中附加时区名称[Europe/Istanbul]来扩展该格式。 在交换数据时,请使用这些明确的标准格式,而不是任何更人性化的格式。

 System.out.println( "zdtIstanbul : " + zdtIstanbul ); System.out.println( "outputTurkish : " + outputTurkish ); System.out.println( "outputFinnish : " + outputFinnish ); System.out.println( "outputQuébec : " + outputQuébec ); System.out.println( "output_US_Unadjusted : " + output_US_Unadjusted ); 

输出结果告诉我们,土耳其的午餐时间表示我们在魁北克省的女性发出的凌晨5:30的警报。

 zdtIstanbul : 2015-10-11T12:30+03:00[Europe/Istanbul] outputTurkish : 11 Ekim 2015 Pazar 12:30:00 EEST outputFinnish : sunnuntai, 11. lokakuuta 2015 12.30.00 EEST outputQuébec : dimanche 11 octobre 2015 5 h 30 EDT output_US_Unadjusted : Sunday, October 11, 2015 5:30:00 AM EDT