【问题标题】:SimpleDateFormat - how to prevent timezone being reformattedSimpleDateFormat - 如何防止时区被重新格式化
【发布时间】:2018-02-08 09:39:23
【问题描述】:

我在解析String 并格式化生成的Date 时遇到SimpleDateFormat 更改datetimestamp 外观的问题。

下面的代码 sn-p 演示了这个问题。注意:我知道用相同的SimpleDateFormat 解析并格式化相同的日期似乎是徒劳的,但这是一个人为的例子,只是为了演示原理:

DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
String dateStringToConvert = "2016-03-12T22:00:00.000-00:00";
try {
    Date date = dateFormat.parse(dateStringToConvert);
    String convertedDateString = dateFormat.format(date);
    System.out.println("Wanted : " + dateStringToConvert);
    System.out.println("Actual : " + convertedDateString);
} catch (ParseException e) {
    e.printStackTrace();
}

这个的输出如下:

通缉:2016-03-12T22:00:00.000-00:00

实际:2016-03-12T22:00:00.000Z

由于此日期将用于自动化测试以填写表格,重要的是datetimestamp 的格式在被SimpleDateFormat 解析和格式化后保持完全相同,所以我不不希望它删除-00:00 并添加Z

这似乎是一个非常简单的问题,但我找不到任何明显的答案。

【问题讨论】:

  • 无法保存文本是表示为“+00:00”、“-00:00”还是“Z”。这不是解析值的一部分。如果要保持相同的文本表示,只需保留字符串。还值得注意的是,“-00:00”实际上与 RFC 3339 中的“+00:00”有非常不同的含义。
  • 感谢乔恩的评论。我上面以字符串开头的示例可能会混淆我的要求。表达这个问题的一个更简单的方法就是:如何确保 SimpleDateFormt.parse 将返回格式为 2016-03-12T22:00:00.000-00:00 而不是 2016-03-12T22:00:00.000 的字符串Z?从您的评论来看,也许答案是您根本无法做到这一点?
  • 如果 any 格式(硬编码除外)返回 -00:00,我会感到惊讶(和担心),因为它具有奇怪的含义。 +00:00(“这与 UTC 之间没有区别”)是否可以接受,或者您的 intention 是否使用 -00:00(“本地时间,我们不知道偏移量” ) 意义?如果是这样,Date 很不幸,因为它代表了一个瞬间......
  • SimpleDateFormat 早已过时并且出了名的麻烦。它可能无法解决您的问题,但我仍然建议您改用java.time, the modern Java date and time API,
  • 答案一清二楚:你不能使用parse方法得到一个看起来像String任何东西,因为parse 方法返回Date,而不是String

标签: java datetime simpledateformat datetime-format datetime-parsing


【解决方案1】:

恐怕SimpleDateFormat 帮不了你。这个类设计的很差,也很有限,更别提它存在的所有问题:https://eyalsch.wordpress.com/2009/05/29/sdf/

如果您有 Java 8,只需使用 java.time API。不知道为什么要将String 转换为Date 只是为了将其转换回另一个String,但如果您希望最终结果为String 并以-00:00 作为偏移量,那么您可以执行以下操作:

DateTimeFormatter fmt = new DateTimeFormatterBuilder()
  .appendPattern("yyyy-MM-dd'T'HH:mm:ss.SSS")
  // offset, use "-00:00" when it's zero
  .appendOffset("+HH:MM", "-00:00")
  // create formatter, always work in UTC
  .toFormatter().withZone(ZoneOffset.UTC);
String dateStringToConvert = "2016-03-12T22:00:00.000-00:00";
Instant instant = fmt.parse(dateStringToConvert, Instant::from);
String result = fmt.format(instant);
System.out.println(result);

这将打印:

2016-03-12T22:00:00.000-00:00

【讨论】:

  • 非常优雅地使用DateTimeFormatterBuilder 及其appendOffset 方法。主要保留可能是,如果原始字符串中的偏移量为+00:00,它可能还会输出-00:00
  • 我同意奥莱的观点。这是一个非常优雅的解决方案,它正是我需要的。我知道我们真的不应该说谢谢,但我是一个有着老式价值观的老式人,所以我非常感谢你!
【解决方案2】:

正如 Jon Skeet 在 a comment 中所说,没有办法绕过硬编码这个特殊要求。

    DateTimeFormatter formatter;
    String dateStringToConvert = "2016-03-12T22:00:00.000-00:00";
    if (dateStringToConvert.endsWith("-00:00")) {
        formatter = new DateTimeFormatterBuilder()
                .appendPattern("uuuu-MM-dd'T'HH:mm:ss.SSS'-00:00'")
                .parseDefaulting(ChronoField.OFFSET_SECONDS, 0)
                .toFormatter();
    } else {
        formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSxxx");
    }
    OffsetDateTime dateTime = OffsetDateTime.parse(dateStringToConvert, formatter);
    String convertedDateString = dateTime.format(formatter);
    System.out.println("Wanted : " + dateStringToConvert);
    System.out.println("Actual : " + convertedDateString);

打印出来

Wanted : 2016-03-12T22:00:00.000-00:00
Actual : 2016-03-12T22:00:00.000-00:00

正如已经说过的,-00:00(负零)的偏移量并不等同于零偏移量,因此像我一样将字符串解析为OffsetDateTime 确实是不正确的。在这种情况下,正确版本的代码将解析为LocalDateTime,并使用相同的格式化程序格式化LocalDateTime。总体而言,它会更长一些,另一方面,您将不再需要 parseDefaulting 调用。

我正在使用并热烈推荐 java.time,这是现代 Java 日期和时间 API。因为DateDateFormatSimpleDateFormat 早已过时,并且已被证明在不同程度上难以使用。现代 API 要好得多。

链接:Oracle tutorial: Date Time解释如何使用java.time

【讨论】:

  • RFC2822(和 RFC3339)用文字说:“-0000”也表示世界时间“但本地偏移量是未知的。我解释这个语句,以便我们可以解析为Instant-like 类型(喜欢OffsetDateTime) 但不是LocalDateTime。请参阅RFC2822 的第3.3 节
  • java.time 的另一个出色应用,我发现它通过各种类型的日期/时间操作让我的生活更轻松。我很感谢你们提供的代码示例。从现在开始我将使用 java.time api。
  • 小心... -00:00 偏移量(负零)是 not allowed is the ISO 8601 standard。这样的值是无效的。 RFC 3339 是 ISO 8601 的配置文件,但覆盖此规则是第 4.3 节,将 -00:00 定义为“未知偏移量”。这种重新定义具有可疑的价值,违反直觉、模棱两可且完全没有必要,因为 ISO 8601 已经将没有任何偏移量定义为未知。所以使用2016-03-12T22:00:00.000 而不是2016-03-12T22:00:00.000-00:00
【解决方案3】:

试试这个:

String pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
SimpleDateFormat df = new SimpleDateFormat(pattern) {
    public StringBuffer format(Date date, StringBuffer prefix, FieldPosition fieldPosition) {
        StringBuffer finalStr = super.format(date, prefix, fieldPosition);
        finalStr = finalStr.insert(finalStr.length()-2, ':');
        finalStr = finalStr.deleteCharAt(finalStr.length() - 6);
        finalStr = finalStr.insert(finalStr.length() - 5, '-');
        return finalStr;
    };
};
System.out.println(df.format(yourDate));

【讨论】:

  • 我非常感谢辛杜的这种努力。它几乎完全符合我们的要求。它为我返回了这个:2016-03-12T22:00:00.000+00:00。唯一的区别是“+”而不是所需的“-”。但这是一个很好的起点。谢谢。
  • @JonH 我已经编辑了代码以包含操作 - 到 +。你可以试试这个吗?
  • 是的,就是这样 sindhu - 伟大的工作。最后我选择了 sbs 的解决方案,因为它使用了更新的 API - java.time
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-27
  • 1970-01-01
  • 1970-01-01
  • 2018-04-27
  • 2019-02-27
  • 2011-09-03
相关资源
最近更新 更多