带有详细时区的Java DateTimeFormatterBuilder

squ*_*uck 4 java timezone datetime-parsing java-8 java-time

假设我有一个日期,例如:

2013年11月30日19:00:00.001930000东部标准时间

我试图解析使用输入DateTimeFormatterBuilder,但我想不出该怎么把为Set通用类型ZoneIdnull下面.

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();
Run Code Online (Sandbox Code Playgroud)

Bas*_*que 6

伪区

正如班级文件简要解释一样,主流媒体中常见的3-4个字母表示时区并不是官方时区.这些伪区域不是标准化的,甚至不是唯一的!许多代码在全球范围内重复使用.例如,IST印度标准时间和爱尔兰标准时间都是.并且CST是中国标准时间和中央标准时间(在北美).

切勿使用这些伪区域.指定适当的时区名称,格式continent/region,如America/Montreal,Africa/CasablancaPacific/Auckland.

解决歧义

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

不幸的是,这是一种粗暴的做法 问题Locale和时区是正交的.您可以让一个说汉语的用户处理芝加哥的交货数据,在这种情况下,Locale可能是中国,但CST数据中的数据是中央标准时间.因此,在这种情况下,您可以指定一个或多个时区,例如格式化程序在尝试解析时要考虑America/ChicagoAmerica/Winnipeg考虑CST的默认值Locale.

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

这是一个完整的示例,解析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 );
Run Code Online (Sandbox Code Playgroud)

输入日期:2013年11月30日19:00:00.001930000 CST

zdt.toString():2013-11-30T19:00:00.001930-06:00 [美国/芝加哥]

让我们传递SetZoneId对象来重写该行为,暗示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 );
Run Code Online (Sandbox Code Playgroud)

输入日期:2013年11月30日19:00:00.001930000 CST

zdt.toString():2013-11-30T19:00:00.001930 + 08:00 [亚洲/上海]

现在,在您的情况下,您具有伪区域的全名而不是缩写.所以可能没有歧义.因此,您可能可以使用重载方法而不是第二个参数.

.appendZoneText( TextStyle.FULL ) 
Run Code Online (Sandbox Code Playgroud)

例:

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 );
Run Code Online (Sandbox Code Playgroud)

输入日期:2013年11月30日19:00:00.001930000东部标准时间

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接口,所以SortedSetTreeSet不能使用.

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 );
Run Code Online (Sandbox Code Playgroud)

输入日期:2013年11月30日19:00:00.001930000东部标准时间

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

  • 只有一个细节:最好指定语言环境(例如`toFormatter(Locale.US)`),因为这些区域名称对语言环境敏感.在我的JVM中,默认为`pt_BR`(巴西葡萄牙语),解析失败(仅当区域名称为'Horáriodeluz natural oriental`时才有效).由于月份名称,区域设置也是必需的(在pt_BR中,它们都是小写的,在这种情况下也是失败的 - 我必须将输入更改为"nov").在我的JVM中,最后一个例子被解析为`America/Nassau`.也许它只是得到了集合返回的第一个元素(没有保证的顺序),谁知道...... (2认同)