如何从Locale获取当前时间和时区?

28

我有一个奇怪的问题,当我使用某个语言环境创建日历时,TimeZone 会自动回到本地时区。

public void start(Locale locale){
    String s = locale.getDisplayName(); 
    System.err.println(s);
    Calendar c = new GregorianCalendar(locale);
    System.err.println(c.getTimeZone());
}

这是输出结果:

 español (Argentina)
 sun.util.calendar.ZoneInfo[id="Europe/Bucharest", //etc more useless date here....

我该如何从特定的语言环境中获取正确的时间?


2
可能是重复的问题:https://dev59.com/N0rSa4cB1Zd3GeqPU0f6 - Moritz Petersen
1
Locale和TimeZone不一定是1:1相关的。想想en_US这个地区设置。美国有不同的时区(-> HST,AKDT,PDT,MDT,CDT,EDT)。 - Javaguru
并将其复制到另一个位置(https://dev59.com/lnM_5IYBdhLWcg3wmkeu)[重复] - S.R.I
所以无法从语言环境中提取时区...这相当愚蠢。 - Alexandru Chirila
1
@Rex:这并不愚蠢,实际上很有道理。而且说实话,你不需要那样做——看看我的答案就好了。 - Paweł Dyda
2个回答

54
简短的回答是:你不能。
详细的回答是:没有所谓“特定区域的正确时区”的概念。这是因为有一些国家有多个时区(例如美国)。时区是一个不同的概念。
无论如何,你正在寻求解决你的问题。我猜测你正在编写一个Web应用程序,并且你发现时区正在恢复到服务器默认值。这是一个典型的情况。Locale.getDefault()和TimeZone.getDefault()都将返回与服务器相关的信息。JVM无法知道“正确”的时区。那么你能做些什么呢?
  1. 你可以在用户个人资料中添加时区信息(如果有),或创建一个时区组合框(以便用户可以在运行时切换)。然后,您可以将适当的对象分配给 DateFormat 实例,它会自动转换时区。
  2. 您可以通过 JavaScript Date 对象的 getTimezoneOffset() 函数从客户端读取当前时区偏移量,并通过某种方式(AJAX)将其发送到服务器。这种方法的问题是有几个时区具有相同的偏移量,所选的时区可能不适用于其他日期。当然,您可以通过轮询时间更改日期周围的数据来猜测时区,但这可能不是您想要做的。
  3. 您可以将未格式化的时间发送到客户端(例如写成 ISO 8601 date-time format 或作为与 UTC 相关的 Unix time of the epoch),并由 GlobalizeDojo 为您格式化日期和时间。

在这三个可能的选择中,我总是选择第1个。通过将时区信息放入用户配置文件中,您可以确定他/她的首选时区,而不受当前网络浏览器等因素的影响。请记住,有些用户可能希望在访问其他国家时使用您的应用程序...


2
还有另一种可能性,比如您的网站列出餐厅的营业时间。在这种情况下,访问者会期望时间以餐厅所在地的时区为准。 - herman
1
@herman:没错。一些商店、餐厅、图书馆或其他场所的营业时间应该列在当地时区。然而,通常这将是自由格式文本,而不是格式化日期,因此在此处转换时区没有意义。此外,如果该地方经常被游客访问,添加“(当地)”一词是有意义的... - Paweł Dyda

16

Locale和时间区是两个不同的问题

Locale

Locale表示以下两个内容的组合:

  • 人类语言,例如法语、英语或中文。
  • 文化规范,用于决定格式设置问题,如首字母大写、缩写、日期中日月年元素的顺序以及使用逗号还是句点作为小数分隔符等。

Locale与地理位置无关。虽然Locale可能会使用国家或区域作为表示文化规范的一种方式,但这并不意味着用户实际上位于特定的地理区域。例如,参加在东京举办的会议的魁北克工程师将使用Locale.CANADA_FRENCH,但她的时区可能在整个旅行期间为Asia/Tokyo

ZoneId

时间区 ZoneId表示特定区域的人们使用的相对于UTC的偏移量过去、现在和未来的变化历史。偏移量仅是UTC使用的本初子午线之前或之后的小时数-分钟数-秒数。世界各地的政客经常重新定义其时区并更改其辖区使用的偏移量。实际上,那些采纳了夏令时(DST)愚蠢做法的地区每年大约六个月会更改其偏移量两次。

➥ 因此,您不能仅从给定的Locale获取当前时间或时区。


当我使用locale创建日历时,TimeZone会恢复为本地时区

您正在使用已过时且被现代的由JSR 310定义的java.time类所替代的可怕的日期时间类。

要获取UTC的当前时刻(零小时数-分钟数-秒数的偏移量),请使用Instant。根据定义,Instant始终处于UTC中。

Instant instant = Instant.now() ;  // Capture the current moment in UTC.

如果您想获取特定时区的当前时间,请指定ZoneId以获取ZonedDateTime对象。

ZoneId z = ZoneId.of( "Asia/Tokyo" ) ;
ZonedDateTime zdt = ZonedDateTime.now( z ) ;  // Capture the current moment as seen in the wall-clock time used by the people of a particular region.

关于确定要应用的时区,如我上面所说,您必须:

您可以通过调用 ZoneId.getAvailableZoneIds 来获取已知的时区列表。

Calendar c = new GregorianCalendar( locale ) ;

您可能会对 GregorianCalendar 的这个带有 Locale 参数的构造函数感到困惑,这是可以理解的。

这个构造函数是遗留日期时间类中众多糟糕和不正确的设计决策之一。说实话,这些遗留类相当糟糕,是由不了解日期时间处理复杂性和微妙之处的人设计的。Sun、Oracle 和 JCP 社区决定 使用 java.time 取代它们,有很充分的理由。

在生成表示日期时间对象的值以供用户显示时,Locale 与时区相交。 Locale 指定在翻译月份名称、星期几名称等方面使用的人类语言。并且 Locale 指定了决定缩写月份或星期几(名称是否大写?是否附加 FULL STOP?),订购日、月和年以及其他此类方面的文化规范。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接