【问题标题】:Java SimpleDateFormat doesn't Give Constant ResultJava SimpleDateFormat 不给出常量结果
【发布时间】:2018-07-11 08:54:28
【问题描述】:

我需要将 Java Date 对象格式化为 yyyyMMdd 之类的字符串(逐日取整)。例如,20180129。我有以下实现:

SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
sdf.format(roundedDate);

该代码大部分时间都有效,但有时它会生成像2018129 这样的结果,这不是我想要的。所以我的数据库中会同时包含201801292018129

应用程序使用来自 MQ 的消息并将消息中的时间戳汇总到 Java Date 对象中。并将日期格式化为上述字符串。

问题是我无法在调试模式下重现该问题。它总是在调试器中产生预期的结果。但是当我在服务器(在 Docker 中)上运行它一段时间后,我看到了这些损坏的数据。

我想知道为什么 SimpleDateFormat 在给定一个有效的 Date 对象的情况下会有这种不确定的行为?任何想法将不胜感激。

【问题讨论】:

  • 你不可能同时得到20180129 2018129的格式与你所展示的相同。要么你有线程问题,要么在一个地方你有错字,格式是yyyyMdd
  • @JimGarrison 谢谢。让我再看一遍代码。但我确信我没有线程问题,因为我创建了一个 SimpleDateFormat 的新实例来在每次调用该方法时完成这项工作。
  • 您为什么要使用早已过时且臭名昭著的麻烦 SimpleDateFormat 类? java.time, the modern Java date and time API, 和它的 DateTimeFormatter 使用起来更好。
  • 出于好奇,我一直在研究SimpleDateFormat 的源代码。不能保证我做对了,但似乎在格式模式字符串中使用非负月份值和MM,我们最终以a method called zeroPaddingNumberminDigits 等于2,然后我看不到任何办法你可以在输出中得到1 而不是01
  • @OleV.V.谢谢你的信息。我使用的是旧版本,因为代码库是 java 1.7。我想对于新应用,我会改用新的 API。

标签: java simpledateformat


【解决方案1】:

SimpleDateFormat 不是线程安全的,请参阅this 优秀文章。

java.time.format.DateTimeFormatter 是核心 Java 中此功能的现代线程安全实现。

【讨论】:

  • 上面的代码在一个 bean 中,被许多 Java 线程同时使用。但是在每个方法调用中,我都会创建一个 SimpleDateFormat 的新实例来完成这项工作,如您所见。所以我认为这不是线程安全问题。
  • @JFreebird 你是不是时不时地得到“yyyyMdd”模式,或者你有时会得到完全乱码?
  • 我只是不时得到yyyyMdd 模式。这是相当随机的,这是唯一的其他格式。
  • @JFreebird 嗯,这可能与在 Docker 中通常不正确的 Locale 和 TimeZone 或宽大处理有关。您可以尝试在构造函数中显式设置 Locale,但老实说,对于 Java 1.8+,我会立即切换到使用 DateTimeFormatter,而不是浪费时间处理令人讨厌的旧 API。
  • @JFreebird 既然您已经接受了这个答案,请您详细说明一下到底是什么原因导致的问题?
【解决方案2】:

tl;博士

改用线程安全的java.time 类。

具体来说,使用LocalDateDateTimeFormatter.BASIC_ISO_DATE

LocalDate.parse(
    "2018129" ,
    DateTimeFormatter.BASIC_ISO_DATE
)

2018-01-29

LocalDate.now()
    .format( DateTimeFormatter.BASIC_ISO_DATE )

20180129

线程安全

您没有提供足够的信息来诊断您的问题。我猜要么:

  • 您正在跨线程使用这些旧的日期时间对象,并且它们并非设计为线程安全的。而是使用 java.time 类,这些类通过immutable objects 模式设计为​​线程安全的。
  • 在您提到但忽略解释的神秘“日期舍入”中,无论您在做什么,都会出现问题。

错误的数据类型

将消息中的时间戳转换为 Java Date 对象。

您正在将仅日期值放入日期时间类型中。方钉,圆孔。

相反,对仅日期值使用仅日期类型:LocalDate

ISO 8601

您所需的格式 YYYYMMDD 恰好在 ISO 8601 标准中定义,作为“基本”变体,其中分隔符的使用最少。

Java 为此提供了一个DateTimeFormatter 对象:DateTimeFormatter.BASIC_ISO_DATE。所以不需要定义格式化模式。

String input = "2018129" ;
LocalDate ld = LocalDate.parse( input , DateTimeFormatter.BASIC_ISO_DATE ) ;

要生成这样的字符串,请使用相同的格式化程序。

LocalDate today = LocalDate.now( ZoneId.of( "Africa/Tunis" ) ) ;
String output = today.format( DateTimeFormatter.BASIC_ISO_DATE ) ;

顺便说一句,我建议使用 ISO 8601 格式的完整版本,而不是紧凑的“基本”变体。根据我的经验,节省的几个字节不值得放弃可读性和减少歧义。另外,java.time 类在解析/生成String 对象时默认使用全长ISO 8601 格式,因此您可以完全省去DateTimeFormatter 对象。


关于java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

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

您可以直接与您的数据库交换 java.time 对象。使用符合JDBC 4.2 或更高版本的JDBC driver。不需要字符串,不需要java.sql.* 类。

从哪里获得 java.time 类?

ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-10-27
    • 1970-01-01
    • 1970-01-01
    • 2019-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多