【问题标题】:Java SimpleDateFormat for time zone with a colon separator?带有冒号分隔符的时区的Java SimpleDateFormat?
【发布时间】:2011-01-23 10:20:40
【问题描述】:

我有一个格式如下的日期:2010-03-01T00:00:00-08:00

我已经抛出了以下 SimpleDateFormats 来解析它:

private static final SimpleDateFormat[] FORMATS = {
        new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"), //ISO8601 long RFC822 zone
        new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz"), //ISO8601 long long form zone
        new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"), //ignore timezone
        new SimpleDateFormat("yyyyMMddHHmmssZ"), //ISO8601 short
        new SimpleDateFormat("yyyyMMddHHmm"),
        new SimpleDateFormat("yyyyMMdd"), //birthdate from NIST IHE C32 sample
        new SimpleDateFormat("yyyyMM"),
        new SimpleDateFormat("yyyy") //just the year
    };

我有一个使用这些格式的便捷方法,如下所示:

public static Date figureOutTheDamnDate(String wtf) {
    if (wtf == null) {
        return null;
    }
    Date retval = null;
    for (SimpleDateFormat sdf : FORMATS) {
        try {
            sdf.setLenient(false)
            retval = sdf.parse(wtf);
            System.out.println("Date:" + wtf + " hit on pattern:" + sdf.toPattern());
            break;
        } catch (ParseException ex) {
            retval = null;
            continue;
        }
    }

    return retval;
}

它似乎符合yyyyMMddHHmm 模式,但返回日期为Thu Dec 03 00:01:00 PST 2009

解析此日期的正确模式是什么?

更新:我不需要时区解析。我预计不会在区域之间移动对时间敏感的问题,但是如何解析“-08:00”区域格式????

单元测试:

@Test
public void test_date_parser() {
    System.out.println("\ntest_date_parser");
    //month is zero based, are you effing kidding me
    Calendar d = new GregorianCalendar(2000, 3, 6, 13, 00, 00);
    assertEquals(d.getTime(), MyClass.figureOutTheDamnDate("200004061300"));
    assertEquals(new GregorianCalendar(1950, 0, 1).getTime(), MyClass.figureOutTheDamnDate("1950"));
    assertEquals(new GregorianCalendar(1997, 0, 1).getTime(),  MyClass.figureOutTheDamnDate("199701"));
    assertEquals(new GregorianCalendar(2010, 1, 25, 15, 19, 44).getTime(),   MyClass.figureOutTheDamnDate("20100225151944-0800"));

    //my machine happens to be in GMT-0800
    assertEquals(new GregorianCalendar(2010, 1, 15, 13, 15, 00).getTime(),MyClass.figureOutTheDamnDate("2010-02-15T13:15:00-05:00"));
    assertEquals(new GregorianCalendar(2010, 1, 15, 18, 15, 00).getTime(), MyClass.figureOutTheDamnDate("2010-02-15T18:15:00-05:00"));

    assertEquals(new GregorianCalendar(2010, 2, 1).getTime(), MyClass.figureOutTheDamnDate("2010-03-01T00:00:00-08:00"));
    assertEquals(new GregorianCalendar(2010, 2, 1, 17, 0, 0).getTime(), MyClass.figureOutTheDamnDate("2010-03-01T17:00:00-05:00"));
}

单元测试的输出:

test_date_parser
Date:200004061300 hit on pattern:yyyyMMddHHmm
Date:1950 hit on pattern:yyyy
Date:199701 hit on pattern:yyyyMM
Date:20100225151944-0800 hit on pattern:yyyyMMddHHmmssZ
Date:2010-02-15T13:15:00-05:00 hit on pattern:yyyy-MM-dd'T'HH:mm:ss
Date:2010-02-15T18:15:00-05:00 hit on pattern:yyyy-MM-dd'T'HH:mm:ss
Date:2010-03-01T00:00:00-08:00 hit on pattern:yyyy-MM-dd'T'HH:mm:ss
Date:2010-03-01T17:00:00-05:00 hit on pattern:yyyy-MM-dd'T'HH:mm:ss

【问题讨论】:

  • 只是想提醒您注意 JDK SimpleDateFormat 不是线程安全的。预实例化 SimpleDateFormat 对象在保存在静态字段中并可能暴露于多个线程时是一种反模式。只有模式本身才有资格成为常量。
  • @mwhs 非常真实!有关更多信息(和一个简单的解决方案),请参阅我关于这个主题的博客文章:How Java’s text Formats can subtly break your code

标签: java date iso8601


【解决方案1】:

JodaTimeDateTimeFormat 救援:

String dateString = "2010-03-01T00:00:00-08:00";
String pattern = "yyyy-MM-dd'T'HH:mm:ssZ";
DateTimeFormatter dtf = DateTimeFormat.forPattern(pattern);
DateTime dateTime = dtf.parseDateTime(dateString);
System.out.println(dateTime); // 2010-03-01T04:00:00.000-04:00

toString() 的时间和时区差异只是因为我在 GMT-4 并且没有明确设置语言环境)

如果您想以java.util.Date 结尾,只需使用DateTime#toDate()

Date date = dateTime.toDate();

等待 JDK7 (JSR-310) JSR-310,如果您想在标准 Java 中使用更好的格式化程序,参考实现称为 ThreeTen(希望它会成为 Java 8)东南 API。当前的SimpleDateFormat 确实不吃时区符号中的冒号。

更新:根据更新,您显然不需要时区。这应该适用于SimpleDateFormat。只需在模式中省略它(Z)即可。

String dateString = "2010-03-01T00:00:00-08:00";
String pattern = "yyyy-MM-dd'T'HH:mm:ss";
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
Date date = sdf.parse(dateString);
System.out.println(date); // Mon Mar 01 00:00:00 BOT 2010

(根据我的时区,这是正确的)

【讨论】:

  • 伙计,如果我能更新到 JDK7,我会在天堂。还有我想要的 7 中的其他东西。我会看看区域解析是否是必需的。我不断听到关于 Joda 的好消息,应该试试看。
  • 试试看。这很值得。特别是如果您想对日期/时间做更多的事情而不仅仅是存储,例如解析、格式化、更改、计算等。
  • @BalusC 我和 Freiheit 有同样的问题,省略时区就可以了。然后SimpleDateFormat 使用“starts with”而不是“equal”之类的模式(因此字符串中写入的其他任何内容都将被忽略)?
  • 使用 Joda-Time 2.3(可能更早),您不需要格式化程序和解析器。 Joda-Time 内置了对 ISO 8601 格式的支持,用于解析和生成字符串。只需将字符串传递给DateTime 构造函数:DateTime dateTime = new DateTime( "2010-03-01T00:00:00-08:00" );。您可能还想通过传递带有 DateTimeZone 实例的第二个参数来指定时区。
  • @BalusC JodaTime docs 建议末尾的“ZZ”使用冒号,而“Z”不使用。您的代码会产生冒号,但使用毫秒的代码不会:final String pattern = "yyyy-MM-dd'T'HH:mm:ssZ"; final DateTimeFormatter dateFormatter = DateTimeFormat.forPattern(pattern); final String dateString = dateFormatter.print(1474068823000L); System.out.println(dateString); 仅使用 ZZ 会产生冒号。这看起来不矛盾吗?
【解决方案2】:

如果您使用的是 java 7,您可以使用以下日期时间模式。似乎早期版本的 java 不支持这种模式。

String dateTimeString  = "2010-03-01T00:00:00-08:00";
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
Date date = df.parse(dateTimeString);

有关更多信息,请参阅SimpleDateFormat documentation

【讨论】:

  • 啊,我希望它能工作,但如果您的语言环境时区是 GMT,它会改为格式化 Z。这个 Z 是个问题,因为我们要在 IE 中解析生成的日期字符串,而 IE 不喜欢 Z
  • 非常感谢,XXX 为+03:00,Z 为+0300
【解决方案3】:

这是我使用的一个 sn-p - 纯 SimpleDateFormat。希望其他人可以从中受益:

public static void main(String[] args) {
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") {
        public StringBuffer format(Date date, StringBuffer toAppendTo, java.text.FieldPosition pos) {
            StringBuffer toFix = super.format(date, toAppendTo, pos);
            return toFix.insert(toFix.length()-2, ':');
        };
    };
    // Usage:
    System.out.println(dateFormat.format(new Date()));
}

输出:

- Usual Output.........: 2013-06-14T10:54:07-0200
- This snippet's Output: 2013-06-14T10:54:07-02:00

或者...更好,使用更简单、不同的模式:

SimpleDateFormat dateFormat2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
// Usage:
System.out.println(dateFormat2.format(new Date()));

输出:

- This pattern's output: 2013-06-14T10:54:07-02:00

请参阅docs for that

【讨论】:

  • 太糟糕了。阅读docs并使用三重X格式yyyy-MM-dd'T'HH:mm:ssXXX
  • @PetrÚjezdský 你是对的,谢谢!我已经更新了答案以供将来参考。
【解决方案4】:

试试这个,它对我有用:

Date date = javax.xml.bind.DatatypeConverter.parseDateTime("2013-06-01T12:45:01+04:00").getTime();

在 Java 8 中:

OffsetDateTime dt = OffsetDateTime.parse("2010-03-01T00:00:00-08:00");

【讨论】:

  • Android 中缺少 :(
  • @MooingDuck java.time 的大部分功能在ThreeTen-Backport 项目中被反向移植到Java 6 和7。并在ThreeTenABP 项目中进一步适应Android
  • @Rustam 很高兴看到 Java 8 示例,但我建议 OffsetDateTimeZonedDateTime 更好,因为输入缺少完整的时区。我在my answer 中展示了这个。
【解决方案5】:

如果您可以使用 JDK 1.7 或更高版本,请尝试以下操作:

public class DateUtil {
    private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");

    public static String format(Date date) {
        return dateFormat.format(date);
    }

    public static Date parse(String dateString) throws AquariusException {
        try {
            return dateFormat.parse(dateString);
        } catch (ParseException e) {
            throw new AquariusException(e);
        }
    }
}

文档:https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html 支持新的时区格式“XXX”(例如-3:00)

而 JDK 1.6 仅支持其他时区格式,即“z”(例如 NZST)、“zzzz”(例如新西兰标准时间)、“Z”(例如 +1200)等。

【讨论】:

    【解决方案6】:

    tl;博士

    OffsetDateTime.parse( "2010-03-01T00:00:00-08:00" )
    

    详情

    answer by BalusC 是正确的,但从 Java 8 开始已经过时了。

    java.time

    java.time 框架是 Joda-Time 库和与 Java 的最早版本(java.util.Date/.Calendar 和 java.text.SimpleDateFormat)捆绑在一起的旧日期时间类的继承者。

    ISO 8601

    您的输入数据字符串恰好符合ISO 8601 标准。

    在解析/生成日期时间值的文本表示时,java.time 类默认使用 ISO 8601 格式。所以不需要定义格式化模式。

    OffsetDateTime

    OffsetDateTime 类代表时间线上的一个时刻,已调整为某个特定的offset-from-UTC。在您的输入中,偏移量比 UTC 晚 8 小时,通常用于北美西海岸的大部分地区。

    OffsetDateTime odt = OffsetDateTime.parse( "2010-03-01T00:00:00-08:00" );
    

    您似乎只需要日期,在这种情况下使用LocalDate 类。但请记住,您正在丢弃数据、(a) 时间和 (b) 时区。确实,没有时区的上下文,日期就没有意义。对于任何给定的时刻,日期在世界各地都会有所不同。例如,巴黎的午夜刚过,蒙特利尔仍然是“昨天”。因此,虽然我建议坚持使用日期时间值,但如果您坚持,您可以轻松转换为 LocalDate

    LocalDate localDate = odt.toLocalDate();
    

    时区

    如果您知道预期的时区,请应用它。时区是一个偏移量加上用于处理异常的规则,例如Daylight Saving Time (DST)。应用 ZoneId 会得到一个 ZonedDateTime 对象。

    ZoneId zoneId = ZoneId.of( "America/Los_Angeles" );
    ZonedDateTime zdt = odt.atZoneSameInstant( zoneId );
    

    生成字符串

    要生成 ISO 8601 格式的字符串,请调用 toString

    String output = odt.toString();
    

    如果您需要其他格式的字符串,请搜索 Stack Overflow 以使用 java.util.format 包。

    转换为java.util.Date

    最好避免java.util.Date,但如果必须,您可以转换。调用添加到旧类的新方法,例如 java.util.Date.from,在其中传递 InstantInstantUTC 时间线上的一个时刻。我们可以从OffsetDateTime 中提取Instant

    java.util.Date utilDate = java.util.Date( odt.toInstant() );
    

    关于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

    【讨论】:

      【解决方案7】:

      感谢 acdcjunior 为您提供解决方案。这是格式化和解析的一点优化版本:

      public static final SimpleDateFormat XML_SDF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.FRANCE)
      {
          private static final long serialVersionUID = -8275126788734707527L;
      
          public StringBuffer format(Date date, StringBuffer toAppendTo, java.text.FieldPosition pos)
          {            
              final StringBuffer buf = super.format(date, toAppendTo, pos);
              buf.insert(buf.length() - 2, ':');
              return buf;
          };
      
          public Date parse(String source) throws java.text.ParseException {
              final int split = source.length() - 2;
              return super.parse(source.substring(0, split - 1) + source.substring(split)); // replace ":" du TimeZone
          };
      };
      

      【讨论】:

      • 请注意,您可能想在parse 上添加一个条件,这样如果source 为空或小于3 个字符,您可以做一些别的事情,而不是尝试使用substring 否定索引...
      【解决方案8】:

      您可以在 Java 7 中使用 X。

      https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html

      static final SimpleDateFormat DATE_TIME_FORMAT = 
              new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
      
      static final SimpleDateFormat JSON_DATE_TIME_FORMAT = 
              new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
      
      private String stringDate = "2016-12-01 22:05:30";
      private String requiredDate = "2016-12-01T22:05:30+03:00";
      
      @Test
      public void parseDateToBinBankFormat() throws ParseException {
          Date date = DATE_TIME_FORMAT.parse(stringDate);
          String jsonDate = JSON_DATE_TIME_FORMAT.format(date);
      
          System.out.println(jsonDate);
          Assert.assertEquals(jsonDate, requiredDate);
      }
      

      【讨论】:

        【解决方案9】:

        试试setLenient(false)

        附录:您似乎正在识别各种格式的 Date 字符串。如果你必须进入,你可能会喜欢看这个扩展InputVerifierexample

        【讨论】:

        • 嗯。更接近一点,它至少对于它所遇到的模式是有意义的。稍后将编辑帖子以反映这些更改。
        • 处理 XML 数据。 Spec 说 应该 对每个日期字段使用 ISO8601,但这不是电线上的东西。系统的其余部分是尽力而为,所以我需要得到任何合理的格式来解析。
        • @Freiheit:其他方式'在那里。 -08:00 是 ISO8601 的正确方法,但 Java 6 没有任何东西可以正确解析 ISO8601。
        【解决方案10】:

        由于此处缺少 Apache FastDateFormat 的示例(单击以获取版本的文档:2.6and3.5),因此我为可能需要的人添加了一个示例。这里的关键是模式ZZ(2 大写Zs)。

        import java.text.ParseException
        import java.util.Date;
        import org.apache.commons.lang3.time.FastDateFormat;
        public class DateFormatTest throws ParseException {
            public static void main(String[] args) {
                String stringDateFormat = "yyyy-MM-dd'T'HH:mm:ssZZ";
                FastDateFormat fastDateFormat = FastDateFormat.getInstance(stringDateFormat);
                System.out.println("Date formatted into String:");
                System.out.println(fastDateFormat.format(new Date()));
                String stringFormattedDate = "2016-11-22T14:30:14+05:30";
                System.out.println("String parsed into Date:");
                System.out.println(fastDateFormat.parse(stringFormattedDate));
            }
        }
        

        这是代码的输出:

        Date formatted into String:
        2016-11-22T14:52:17+05:30
        String parsed into Date:
        Tue Nov 22 14:30:14 IST 2016
        

        注意:以上代码是 Apache Commons 的 lang3。 org.apache.commons.lang.time.FastDateFormat 类不支持解析,只支持格式化。例如以下代码的输出:

        import java.text.ParseException;
        import java.util.Date;
        import org.apache.commons.lang.time.FastDateFormat;
        public class DateFormatTest {
            public static void main(String[] args) throws ParseException {
                String stringDateFormat = "yyyy-MM-dd'T'HH:mm:ssZZ";
                FastDateFormat fastDateFormat = FastDateFormat.getInstance(stringDateFormat);
                System.out.println("Date formatted into String:");
                System.out.println(fastDateFormat.format(new Date()));
                String stringFormattedDate = "2016-11-22T14:30:14+05:30";
                System.out.println("String parsed into Date:");
                System.out.println(fastDateFormat.parseObject(stringFormattedDate));
            }
        }
        

        会是这样的:

        Date formatted into String:
        2016-11-22T14:55:56+05:30
        String parsed into Date:
        Exception in thread "main" java.text.ParseException: Format.parseObject(String) failed
            at java.text.Format.parseObject(Format.java:228)
            at DateFormatTest.main(DateFormatTest.java:12)
        

        【讨论】:

          【解决方案11】:

          如果日期字符串类似于 2018-07-20T12:18:29.802Z 用这个

          SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
          

          【讨论】:

          • 仅供参考,非常麻烦的旧日期时间类,如 java.util.Datejava.util.Calendarjava.text.SimpleDateFormat 现在是 legacy,被 Java 8 中内置的 java.time 类所取代,之后。见Tutorial by Oracle
          • 这个答案没有解决这个问题。问题明确地是关于与 UTC 的数字偏移量 (-08:00),而不是表示 UTC 的 Z Zulu 字符。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-02-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-03-10
          • 1970-01-01
          相关资源
          最近更新 更多