Java 带有详细时区的 DateTimeFormatterBuilder

Java DateTimeFormatterBuilder with Verbose Time Zone

假设我有这样一个日期:

Nov 30, 2013 19:00:00.001930000 Eastern Standard Time

我正在尝试使用下面的 null DateTimeFormatterBuilder, but I can't figure out what to put for the Set of generic type ZoneId 来解析输入。

String basePattern = "MMM dd, yyyy HH:mm:ss";
new DateTimeFormatterBuilder()
        .appendPattern(basePattern)
        .appendFraction(ChronoField.NANO_OF_SECOND,0,9, true)
        .appendZoneText(TextStyle.FULL, null)
        .toFormatter();

伪区

作为 class documentation explains briefly, the 3-4 letters commonly seen in mainstream media to indicate a time zone are not actually official time zones. These pseudo-zones are not standardized, and are not even unique! Many codes are re-used around the globe. For example, IST is both India Standard Time and Irish Standard Time. And CST is both China Standard Time and Central Standard Time(在北美)。

切勿使用这些伪区域。指定 proper time zone name in the format of continent/region, such as America/Montreal, Africa/CasablancaPacific/Auckland.

解决歧义

如果您的输入确实有这些伪区域,在缩写格式中,您必须处理歧义。默认情况下,格式化程序构建器将尝试通过考虑格式化程序的 Locale 来解决歧义。在 CST 的情况下,如果 LocaleLocale.CHINA,那么 CST 可能表示中国标准时间而不是中部标准时间。

不幸的是,这是一种粗略的方法。 Locale和时区的问题是正交的。您可以让讲中文的用户处理在芝加哥交付的数据,在这种情况下,格式化程序在尝试解析 CST 以覆盖考虑 Locale 的默认值时会考虑 Locale may be China but the CST found in the data means Central Standard Time. So in such cases, you can specify one or more time zones such as America/Chicago and America/Winnipeg .

Set< ZoneID > zones = new TreeSet<>() ;
zones.add( ZoneId.of( "America/Chicago" ) ;
zones.add( ZoneId.of( "America/Manitoba" ) ;
…
.appendZoneText( TextStyle.SHORT , zones ) 
…

这是一个完整的示例,将 CST 解析为我的 macOS MacBook 上的中央标准时间设置为默认时区 America/Los_Angeles 和默认区域设置 Locale.US。请注意,我们仅将一个参数传递给 appendZoneText(未传递 Set)。

String input = "Nov 30, 2013 19:00:00.001930000 CST";  
String basePattern = "MMM dd, yyyy HH:mm:ss";
DateTimeFormatter f = new DateTimeFormatterBuilder( )
        .appendPattern( basePattern )
        .appendFraction( ChronoField.NANO_OF_SECOND , 0 , 9 , true )
        .appendPattern( " " )
        .appendZoneText( TextStyle.SHORT  )
        .toFormatter( );

ZonedDateTime zdt = ZonedDateTime.parse( input , f );

System.out.println( "input: " + input );
System.out.println( "zdt.toString(): " + zdt );

input: Nov 30, 2013 19:00:00.001930000 CST

zdt.toString(): 2013-11-30T19:00:00.001930-06:00[America/Chicago]

让我们传递 ZoneId 个对象的 Set 来覆盖该行为,暗示 CST 意味着 China Standard Time。这里我们传递SetZoneId个对象。我们使用相同的输入来获得截然不同的输出。

Set < ZoneId > zones = new HashSet <>( );
zones.add( ZoneId.of( "Asia/Shanghai" ) ) ;

String input = "Nov 30, 2013 19:00:00.001930000 CST";  
String basePattern = "MMM dd, yyyy HH:mm:ss";
DateTimeFormatter f = new DateTimeFormatterBuilder( )
        .appendPattern( basePattern )
        .appendFraction( ChronoField.NANO_OF_SECOND , 0 , 9 , true )
        .appendPattern( " " )
        .appendZoneText( TextStyle.SHORT , zones )
        .toFormatter( );

ZonedDateTime zdt = ZonedDateTime.parse( input , f );

System.out.println( "input: " + input );
System.out.println( "zdt.toString(): " + zdt );

input: Nov 30, 2013 19:00:00.001930000 CST

zdt.toString(): 2013-11-30T19:00:00.001930+08:00[Asia/Shanghai]

现在,在你的例子中,你有伪区域的全名而不是缩写。所以很可能没有歧义。所以你可能可以摆脱 overloaded method not taking a second argument.

.appendZoneText( TextStyle.FULL ) 

示例:

String input = "Nov 30, 2013 19:00:00.001930000 Eastern Standard Time";  
String basePattern = "MMM dd, yyyy HH:mm:ss";
DateTimeFormatter f = new DateTimeFormatterBuilder( )
        .appendPattern( basePattern )
        .appendFraction( ChronoField.NANO_OF_SECOND , 0 , 9 , true )
        .appendPattern( " " )
        .appendZoneText( TextStyle.FULL )
        .toFormatter( );

ZonedDateTime zdt = ZonedDateTime.parse( input , f );

System.out.println( "input: " + input );
System.out.println( "zdt.toString(): " + zdt );

input: Nov 30, 2013 19:00:00.001930000 Eastern Standard Time

zdt.toString(): 2013-11-30T19:00:00.001930-05:00[America/New_York]

然而, 也可以在这里传递 SetZoneId 对象。 Set 用于将时区分配给实例化的 ZonedDateTime 对象。请注意,在上面的输出中,默认情况下分配了 America/New_York。但还有许多其他时区也被伪时区“东部标准时间”隐含,如巴哈马、America/Nassau、墨西哥的坎昆等。

然而,选择应用集合中的哪个元素对我来说是个谜。我试过用一个SortedSet想着第一个找到的自然顺序的Set可能会被选中。唉,ZoneId 没有实现 Comparable 接口,所以不能使用 SortedSet,例如 TreeSet

Set < ZoneId > zones = new HashSet <>( );
zones.add( ZoneId.of( "America/Detroit" ) );
zones.add( ZoneId.of( "America/New_York" ) );
zones.add( ZoneId.of( "America/Nassau" ) );
zones.add( ZoneId.of( "America/Cancun" ) );

String input = "Nov 30, 2013 19:00:00.001930000 Eastern Standard Time";  
String basePattern = "MMM dd, yyyy HH:mm:ss";
DateTimeFormatter f = new DateTimeFormatterBuilder( )
        .appendPattern( basePattern )
        .appendFraction( ChronoField.NANO_OF_SECOND , 0 , 9 , true )
        .appendPattern( " " )
        .appendZoneText( TextStyle.FULL , zones )
        .toFormatter( );

ZonedDateTime zdt = ZonedDateTime.parse( input , f );

System.out.println( "input: " + input );
System.out.println( "zdt.toString(): " + zdt );

input: Nov 30, 2013 19:00:00.001930000 Eastern Standard Time

zdt.toString(): 2013-11-30T19:00:00.001930-06:00[America/Cancun]