【问题标题】:Why does SimpleDateFormat.format(Date) ignore the configured TimeZone?为什么 SimpleDateFormat.format(Date) 会忽略配置的 TimeZone?
【发布时间】:2016-08-04 09:21:23
【问题描述】:

鉴于我的默认时区是欧洲/巴黎:

System.out.println("Current Timezone: " + TimeZone.getDefault());
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");

String dateStrIn = "2016-08-01T00:00:00Z";
Date date = dateFormat.parse(dateStrIn);
String dateStrOut = dateFormat.format(date);

System.out.println("Input date String: "+dateStrIn);
System.out.println("Date.toString() "+date);
System.out.println("Output date String: "+dateStrOut);

输出是:

Current Timezone: sun.util.calendar.ZoneInfo[id="Europe/Paris",offset=3600000,dstSavings=3600000,useDaylight=true,transitions=184,lastRule=java.util.SimpleTimeZone[id=Europe/Paris,offset=3600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]]
Input date String: 2016-08-01T00:00:00Z
Date.toString() Mon Aug 01 02:00:00 CEST 2016
Output date String: 2016-08-01T02:00:00+02

现在,我重复执行但设置不同的默认时区(UTC):

TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
System.out.println("Current Timezone: " + TimeZone.getDefault());
...

这是输出:

Current Timezone: sun.util.calendar.ZoneInfo[id="UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]
Input date String: 2016-08-01T00:00:00Z
Date.toString() Mon Aug 01 00:00:00 UTC 2016
Output date String: 2016-08-01T02:00:00+02

Date.toString() 已正确考虑时区更改。但是,从dateFormat.format(date); 得到的字符串仍然显示 +02 而不是 Z 或 +00,为什么?

使用标准 Java API,有没有办法根据选定的 TimeZone 强制格式化?

更新:

Jesper 的解决方案几乎在所有情况下都有效,但我遇到过这种情况(使用加那利岛的夏季时区 WEST),但它不起作用:

SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss z");
dateFormat.setTimeZone(TimeZone.getTimeZone("Europe/Paris"));
TimeZone.setDefault(TimeZone.getTimeZone("Europe/Paris"));
System.out.println("Current Timezone: " + TimeZone.getDefault());

String dateStrIn = "2016-08-01T08:00:00 WEST";
Date date = dateFormat.parse(dateStrIn);
String dateStrOut = dateFormat.format(date);

System.out.println("Input date String: "+dateStrIn);
System.out.println("Date.toString() "+date);
System.out.println("Output date String: "+dateStrOut);

输出:

Current Timezone: sun.util.calendar.ZoneInfo[id="Europe/Paris",offset=3600000,dstSavings=3600000,useDaylight=true,transitions=184,lastRule=java.util.SimpleTimeZone[id=Europe/Paris,offset=3600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]]
Input date String: 2016-08-01T08:00:00 WEST
Date.toString() Mon Aug 01 09:00:00 CEST 2016
Output date String: 2016-08-01T08:00:00 WEST

可以看到,Output date String 还是用 WEST 表示的。

例如,如果我将初始 dateStrIn 更改为:String dateStrIn = "2016-08-01T08:00:00 GMT";,则输出日期字符串以 CEST 表示:

Output date String: 2016-08-01T10:00:00 CEST

这可能是一个错误吗?

更新 2:

另一个例子

Default TimeZone for both Date and SimpleDateFormat: "Europe/Paris"
Input String: "2016-08-01T08:00:00 WET"

输出:

Date.toString() Mon Aug 01 10:00:00 CEST 2016
Output date String: 2016-08-01T09:00:00 WEST

请注意,dateFormat.format(date); 已生成 WEST 日期字符串。从 WET -> WEST 应该是 WET -> CEST。

【问题讨论】:

    标签: java date date-format simpledateformat


    【解决方案1】:

    不要设置默认时区,而是在 SimpleDateFormat 对象上设置时区:

    dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    

    这将使SimpleDateFormat 对象格式成为您在UTC 时区中的Date 对象。

    如果您使用的是 Java 8,请考虑使用包 java.time 中的新日期和时间 API,而不是旧的 java.util.Datejava.text.SimpleDateFormat

    【讨论】:

    • 感谢 Jesper,它可以正常工作...嗯,几乎总是,你能看看更新的答案吗?
    • 我刚刚提交了
    • @codependent 我现在没有时间详细查看它,但在 Java 的旧日期和时间 API 中不太可能存在这样一个在过去 20 年没有人注意到的错误年。
    • @codependent 要记住的事情:(1) java.util.Date 对象不知道它的时区,(2) Date.toString 使用系统的默认时区。
    • 知道了。但是由于某些奇怪的原因,当使用 WEST 时区作为输入时,SimpleDateFormat 中配置的时区不起作用。
    【解决方案2】:

    您使用 java.time 框架已经过时的麻烦的旧日期时间类来折磨自己。

    tl;博士

    ZonedDateTime.ofInstant( Instant.parse( "2016-08-01T00:00:00Z" ) , ZoneId.of( "Europe/Paris" ) )
    

    java.time

    java.time 框架内置于 Java 8 及更高版本中。这些类取代了旧的麻烦的日期时间类,例如java.util.Date.Calendarjava.text.SimpleDateFormatJoda-Time 团队还建议迁移到 java.time。

    要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。

    大部分 java.time 功能在ThreeTen-Backport 中向后移植到 Java 6 和 7,并在 ThreeTenABP 中进一步适应 Android。

    Instant

    输入字符串2016-08-01T00:00:00Z 末尾的ZZulu 的缩写,表示UTC。在 java.time 中由 Instant 类表示,分辨率高达纳秒。

    Instant instant = Instant.parse ( "2016-08-01T00:00:00Z" );
    

    ZonedDateTime

    通过ZoneId 指定时区以获取ZonedDateTime

    在夏季,Paris time 比 UTC 早两个小时。因此,在巴黎墙上的时钟上看到的同一时刻是 8 月 1 日同一天的凌晨 2 点而不是午夜。

    ZoneId zoneId_Paris = ZoneId.of ( "Europe/Paris" );
    ZonedDateTime zdt_Paris = ZonedDateTime.ofInstant ( instant , zoneId_Paris );
    

    您可以调整到另一个时区,例如Atlantic/Canary。对于夏季的这个日期,时间比 UTC 早一小时。结果是凌晨 1 点而不是午夜。

    ZoneId zoneId_Canary = ZoneId.of ( "Atlantic/Canary" );
    ZonedDateTime zdt_Canary = zdt_Paris.withZoneSameInstant ( zoneId_Canary );
    

    转储到控制台。

    System.out.println ( "instant: " + instant + " | zdt_Paris: " + zdt_Paris + " | zdt_Canary: " + zdt_Canary );
    

    即时:2016-08-01T00:00:00Z | zdt_Paris: 2016-08-01T02:00+02:00[欧洲/巴黎] | zdt_Canary: 2016-08-01T01:00+01:00[大西洋/金丝雀]

    所有这三个对象(UTC、巴黎、金丝雀)代表历史上完全相同的同时时刻,时间轴上的同一个点。每个都是通过不同wall-clock time 的镜头观看的。

    实时时区

    避免使用 3-4 个字母的区域缩写,例如 WETWEST。这些不是实时时区,没有标准化,甚至不是唯一的(!)。

    Europe/ParisAtlantic/Canaryproper time zone names,格式为continent/region

    【讨论】:

    • Basil 感谢您的建议,我已经使用过这个新的 API。不幸的是,这不是一种自我折磨的激情:-),我必须使用 JDK 1.6,因为我们的应用服务器不支持 JDK 1.8。
    【解决方案3】:

    这个谜团的答案在 Javadoc 上:

    DateFormat.parse(String source, ParsePosition pos)

    此解析操作使用日历来生成日期。作为一个 结果,日历的日期时间字段和 TimeZone 值可能 已被覆盖,具体取决于子类实现。任何 之前通过调用 setTimeZone 设置的 TimeZone 值 可能需要恢复以进行进一步操作。

    解析后设置时区就可以了:

    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss z");
    TimeZone.setDefault(TimeZone.getTimeZone("Europe/Paris"));
    
    System.out.println("Current Timezone: " + TimeZone.getDefault());
    
    String dateStrIn = "2016-08-01T08:00:00 WET";
    Date date = dateFormat.parse(dateStrIn);
    dateFormat.setTimeZone(TimeZone.getTimeZone("Europe/Paris"));
    String dateStrOut = dateFormat.format(date);
    
    System.out.println("Input date String: "+dateStrIn);
    System.out.println("Date.toString() "+date);
    System.out.println("Output date String: "+dateStrOut);
    

    右输出:

    Current Timezone: sun.util.calendar.ZoneInfo[id="Europe/Paris",offset=3600000,dstSavings=3600000,useDaylight=true,transitions=184,lastRule=java.util.SimpleTimeZone[id=Europe/Paris,offset=3600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]]
    Input date String: 2016-08-01T08:00:00 WET
    Date.toString() Mon Aug 01 10:00:00 CEST 2016
    Output date String: 2016-08-01T10:00:00 CEST
    

    【讨论】:

      【解决方案4】:

      ,这不是错误。

      您必须首先了解Date 类的工作原理。它只不过是对自 epoch 以来的毫秒数的包装,以 long 表示。因此,无论它可能是哪个时区,日期对象的基础值都保持不变。您永远无法真正更改 Date 类的时区。您只能使用 SimpleDateFormat 类表示日期实例的 String 格式。此表示可能具有不同的时区,具体取决于您在创建 SimpleDateFormatobject 时使用的时区。

      再次,您需要检查 Date 类的toString 方法。它总是使用默认时区打印日期。

      编辑

      您还应该查看SimpleDateFormat.parse() 定义。 JDK 是这么说的,

      TimeZone 值可能会被覆盖,具体取决于给定模式和文本中的时区值。之前通过调用 setTimeZone 设置的任何 TimeZone 值可能需要恢复以进行进一步操作。

      【讨论】:

      • 这并不能解释 dateFormat.format(date) 中发生了什么。我将 SimpleDateFormat 的时区配置为欧洲/巴黎(当前为 CEST),但它没有按预期输出格式化的字符串。
      猜你喜欢
      • 2013-08-12
      • 1970-01-01
      • 2013-01-01
      • 2016-11-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-04
      • 2012-02-03
      相关资源
      最近更新 更多