Java DateFormat parse() 不尊重时区
Posted
技术标签:
【中文标题】Java DateFormat parse() 不尊重时区【英文标题】:Java DateFormat parse() doesn't respect the timezone 【发布时间】:2011-11-23 21:36:27 【问题描述】:Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("America/New_York"));
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
df.setTimeZone(TimeZone.getTimeZone("America/New_York"));
try
System.out.println(df.format(cal.getTime()));
System.out.println(df.parse(df.format(cal.getTime())));
catch (ParseException e)
e.printStackTrace();
结果如下:
2011-09-24 14:10:51 -0400
2011 年 9 月 24 日星期六 20:10:51 CEST
为什么当我解析从 format() 获得的日期时,它不遵守时区?
【问题讨论】:
【参考方案1】:您正在打印调用Date.toString()
的结果,始终使用默认时区。基本上,您不应该将Date.toString()
用于除调试以外的任何事情。
不要忘记Date
没有时区 - 它代表一个瞬间,以 Unix 纪元(UTC 1970 年 1 月 1 日午夜)以来的毫秒数为单位。
如果您再次使用格式化程序格式化日期,那应该会得出与以前相同的答案。
顺便说一句,如果您在 Java 中进行大量日期/时间工作,我建议使用 Joda Time 而不是 Date
/Calendar
;这是一个非常更好的 API。
【讨论】:
没错...尝试打印System.out.println(df.format(df.parse(df.format(cal.getTime()))));
,它会按预期工作:-)
好吧,显然问题出在 toString() 上?感谢您的建议。
@MaximeLaval:特别是来自Date.toString
,是的 - 您需要注意Date
只是一个瞬间,而不是任何特定的时区或日历。
除了显示/调试之外,永远不要依赖.toString
方法。另请注意,SimpleDateFormatter 对区域设置敏感。
@ChristopheRoussy:当然,除非行为被记录为你想做的事。【参考方案2】:
DateFormat 是日期/时间格式子类的抽象类 它以独立于语言的方式格式化和解析日期或时间 方式。日期/时间格式化子类,例如 SimpleDateFormat, 允许格式化(即日期 -> 文本),解析(文本 -> 日期), 和规范化。 日期表示为 Date 对象或 自 1970 年 1 月 1 日 00:00:00 GMT 以来的毫秒数。
从spec返回EPOCH时间
【讨论】:
【参考方案3】:DateFormat.parse()
不是查询(返回值且不改变系统状态的东西)。这是一个具有更新内部Calendar
对象的副作用的命令。调用parse()
后,您必须通过访问DateFormat
的Calendar
或调用DateFormat.getTimeZone()
来访问时区。除非您想丢弃原始时区并使用本地时间,否则不要使用从parse()
返回的Date
值。而是在解析后使用日历对象。格式方法也是如此。如果要格式化日期,请在调用format()
之前将带有时区信息的日历传递到DateFormat
对象。以下是如何将一种格式转换为另一种保留原始时区的格式:
DateFormat originalDateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy");
DateFormat targetDateFormat = new SimpleDateFormat("EEE., MMM. dd, yyyy");
originalDateFormat.parse(origDateString);
targetDateFormat.setCalendar(originalDateFormat.getCalendar());
return targetDateFormat.format(targetDateFormat.getCalendar().getTime());
这很麻烦但很有必要,因为parse()
不返回保留时区的值,而format()
不接受定义时区的值(Date
类)。
【讨论】:
【参考方案4】:java.util.Date
对象不是像 modern Date-Time types 这样的真实日期时间对象;相反,它表示自称为“纪元”的标准基准时间以来的毫秒数,即January 1, 1970, 00:00:00 GMT
(或 UTC)。由于它不包含任何格式和时区信息,因此它应用格式 EEE MMM dd HH:mm:ss z yyyy
和 JVM 的时区来返回从该毫秒值派生的 Date#toString
的值。如果您需要以不同的格式和时区打印日期时间,则需要使用具有所需格式和适用时区的SimpleDateFormat
,例如
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.ENGLISH);
sdf.setTimeZone(TimeZone.getTimeZone("America/New_York"));
String strDate = sdf.format(date);
System.out.println(strDate);
请注意,java.util
日期时间 API 及其格式化 API SimpleDateFormat
已过时且容易出错。建议完全停止使用,改用modern Date-Time API*。
使用现代 API java.time
的解决方案:
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class Main
public static void main(String[] args)
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("America/New_York"));
System.out.println(zdt);
// With timezone offset
OffsetDateTime odt = zdt.toOffsetDateTime();
System.out.println(odt);
// In a custom format
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss Z", Locale.ENGLISH);
String formatted = dtf.format(odt);
System.out.println(formatted);
输出:
2021-06-04T14:25:08.266940-04:00[America/New_York]
2021-06-04T14:25:08.266940-04:00
2021-06-04 14:25:08 -0400
在这里,您可以使用yyyy
代替uuuu
但I prefer u
to y
。
从Trail: Date Time了解更多关于java.time
的modern Date-Time API*。
* 出于任何原因,如果您必须坚持使用 Java 6 或 Java 7,您可以使用 ThreeTen-Backport,它将大部分 java.time 功能向后移植到 Java 6 和 7 . 如果您正在为一个 android 项目工作并且您的 Android API 级别仍然不符合 Java-8,请检查 Java 8+ APIs available through desugaring 和 How to use ThreeTenABP in Android Project。
【讨论】:
以上是关于Java DateFormat parse() 不尊重时区的主要内容,如果未能解决你的问题,请参考以下文章
java_DateFormat类中format方法parse方法使用