2011-05-05 50 views
31

我正在阅读可能带有或不带时区调整的日期字符串:yyyyMMddHHmmsszyyyyMMddHHmmss。当一个字符串缺少一个区域时,我会把它当作GMT。我没有看到在SimpleDateFormat中创建可选部分的任何方式,但也许我错过了一些东西。有没有办法做到这一点与SimpleDateFormat,或者我应该写一个新的具体DateFormat来处理这个?SimpleDateFormat中的可选部件

+0

应该提到的是,这里只涉及一个DateFormat对象,因为我将它传递到一个将用于格式化的库中。 – traffichazard 2011-05-06 02:49:43

+0

类似:http://stackoverflow.com/questions/4515023/how-do-i-create-a-dateformat-with-an-optional-time-argument – Vadzim 2015-06-08 19:18:28

+0

仅供参考,麻烦的旧日期时间类,如[ java.util.Date'](https://docs.oracle.com/javase/9​​/docs/api/java/util/Date.html),['java.util.Calendar'](https:// docs .oracle.com/javase/9/docs/api/java/util/Calendar.html)和'java.text.SimpleDateFormat'现在是[legacy](https://en.wikipedia.org/wiki/Legacy_system) ,取而代之的是内置于Java 8和Java 9中的[* java.time *](https://docs.oracle.com/javase/9​​/docs/api/java/time/package-summary.html)类。请参阅[*由Oracle指导](https://docs.oracle.com/javase/tutorial/datetime/TOC.html)。 – 2018-03-07 23:08:51

回答

18

我会创建两个SimpleDateFormat,一个带有时区,另一个没有。您可以查看字符串的长度以确定要使用哪一个。


听起来像你需要一个DateFormat代表两个不同的SDF。

DateFormat df = new DateFormat() { 
    static final String FORMAT1 = "yyyyMMddHHmmss"; 
    static final String FORMAT2 = "yyyyMMddHHmmssz"; 
    final SimpleDateFormat sdf1 = new SimpleDateFormat(FORMAT1); 
    final SimpleDateFormat sdf2 = new SimpleDateFormat(FORMAT2); 
    @Override 
    public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) { 
     throw new UnsupportedOperationException(); 
    } 

    @Override 
    public Date parse(String source, ParsePosition pos) { 
     if (source.length() - pos.getIndex() == FORMAT1.length()) 
      return sdf1.parse(source, pos); 
     return sdf2.parse(source, pos); 
    } 
}; 
System.out.println(df.parse("20110102030405")); 
System.out.println(df.parse("20110102030405PST")); 
+0

我正在使用的库需要我传递一个dateformat对象,所以不幸的是这不起作用。 – traffichazard 2011-05-06 02:47:57

+0

很酷,这就是我希望我不需要做的事情,但是确认这是一条非常有用的途径。 – traffichazard 2011-05-06 11:08:26

+0

伟大的方法 - 一个超格式化后面的格式化器,能够处理何时使用什么.. – 2012-10-27 14:08:00

2

我会循环使用try-catch操作的潜在DateFormat对象列表,以在第一次成功解析时打破循环。

+0

您可以将字符串长度与接受它们的SDF相匹配。这会更快并且创建更少的例外。 – 2011-05-05 12:02:38

+1

@彼得,在这种特殊情况下,是的。我想这取决于他希望代码是如何通用的。 – 2011-05-05 12:04:45

0

您可以创建两个不同的SimpleDateFormats

public PWMDateTimeFormatter(String aPatternStr) 
    { 
     _formatter = DateTimeFormat.forPattern(aPatternStr); 
    } 



    public PWMDateTimeFormatter(String aPatternStr, TimeZone aZone) 
    { 
     _formatter = DateTimeFormat.forPattern(aPatternStr).withZone(XXDateTime._getTimeZone(aZone)); 
    } 
0

我已经通过扩展的SimpleDateFormat解决了类似的问题,前一段时间。下面粗略的实现来展示我的解决方案的想法。它可能不完全/优化。

public class MySimpleDateFormat extends SimpleDateFormat { 
    private static final long serialVersionUID = 1L; 
    private static String FORMAT = "   
    private static int FORMAT_LEN = "yyyyMMddHHmmss".length(); 
    private static String TZ_ID = "GMT"; 

    public MySimpleDateFormat() { 
      this(TimeZone.getTimeZone(TZ_ID)); 
    } 

    public MySimpleDateFormat(TimeZone tz) { 
      super(FORMAT); 
      setTimeZone(tz); 
    } 

    @Override 
    public Date parse(String source, ParsePosition pos) { 
      // TODO: args validation 
      int offset = pos.getIndex() + FORMAT_LEN; 
      Date result; 
      if (offset < source.length()) { 
        // there maybe is a timezone 
        result = super.parse(source, pos); 
        if (result != null) { 
          return result; 
        } 
        if (pos.getErrorIndex() >= offset) { 
          // there isn't a TZ after all 
          String part0 = source.substring(0, offset); 
          String part1 = source.substring(offset); 
          ParsePosition anotherPos = new ParsePosition(pos.getIndex()); 
          result = super.parse(part0 + TZ_ID + part1, anotherPos); 
          if(result == null) { 
            pos.setErrorIndex(anotherPos.getErrorIndex()); 
          } else { 
            // check SimpleDateFormat#parse javadoc to implement correctly the pos updates 
            pos.setErrorIndex(-1); 
            pos.setIndex(offset); 
          } 
          return result; 
        } 
        // there's something wrong with the first FORMAT_LEN chars 
        return null; 
      } 
      result = super.parse(source + TZ_ID, pos); 
      if(result != null) { 
        pos.setIndex(pos.getIndex() - TZ_ID.length()); 
      } 
      return result; 
    } 

    public static void main(String [] args) { 
      ParsePosition pos = new ParsePosition(0); 
      MySimpleDateFormat mySdf = new MySimpleDateFormat(); 
      System.out.println(mySdf.parse("20120622131415", pos) + " -- " + pos); 
      pos = new ParsePosition(0); 
      System.out.println(mySdf.parse("20120622131415GMT", pos) + " -- " + pos); 
      pos = new ParsePosition(0); 
      System.out.println(mySdf.parse("20120622131415xxx", pos) + " -- " + pos); 
      pos = new ParsePosition(0); 
      System.out.println(mySdf.parse("20120x22131415xxx", pos) + " -- " + pos); 
    } 
} 

要点是,你需要检查输入字符串和“猜测”不知何故该场TZ丢失,如果是添加它,然后让SimpleDateFormat#parse(String, ParsePosition)做休息。上面的实现并没有根据javadoc中的合约更新ParsePosition SimpleDateFormat#parse(String, ParsePosition)

该类只有一个默认ctor,因为只有一种格式是允许的。

方法MySimpleDateFormat#parse(String, ParsePosition)SimpleDateFormat#parse(String)调用,因此它足以涵盖两种情况。

运行的main(),这是输出(如预期)

Fri Jun 22 14:14:15 BST 2012 -- java.text.ParsePosition[index=14,errorIndex=-1] 
Fri Jun 22 14:14:15 BST 2012 -- java.text.ParsePosition[index=17,errorIndex=-1] 
Fri Jun 22 14:14:15 BST 2012 -- java.text.ParsePosition[index=14,errorIndex=-1] 
null -- java.text.ParsePosition[index=0,errorIndex=5] 
24

我知道这是一个老的文章,但仅仅是为了记录...

阿帕奇DateUtils类可以帮助你那。

String[] acceptedFormats = {"dd/MM/yyyy","dd/MM/yyyy HH:mm","dd/MM/yyyy HH:mm:ss"}; 
Date date1 = DateUtils.parseDate("12/07/2012", acceptedFormats); 
Date date2 = DateUtils.parseDate("12/07/2012 23:59:59", acceptedFormats); 

链接到Maven仓库的阿帕奇共享郎库,你可能要检查什么是最新版本:
http://mvnrepository.com/artifact/org.apache.commons/commons-lang3

Maven的V3.4

<dependency> 
    <groupId>org.apache.commons</groupId> 
    <artifactId>commons-lang3</artifactId> 
    <version>3.4</version> 
</dependency> 

Gradle v3。4

'org.apache.commons:commons-lang3:3.4' 
+0

只是一个警告:我用'DateUtils.parseDate'方法关于时区有奇怪的结果。它一直在调整它解析的时间,就像我以UTC时间读取日期一样,实际上我在当地时间读取它。 – Diederik 2013-02-26 13:31:07

+2

@Diederik,你能详细说明一下吗?你的格式是否有时区?行为是否记录在案?这是一个错误吗?如果是这样,你提交了一个错误报告? – aioobe 2013-09-18 16:10:49

+0

@aioobe如果我记得正确,我解析的日期有时区设置为GMT + 2。然而,解析器保持从时间减去2小时。这不是预期的行为。我不想要UTC时间,我想全日制,并且时区信息全部解析正确。例如,我分析了时间字符串“08:43:53.594 + 0200”,但是当我查询解析的日期对象时,它的小时字段为'6'。 – Diederik 2013-09-19 06:49:35

3

如果可以使用乔达日期时间,它支持在格式化可选部分,例如, “YYYY-MM-DD [HH:MM:SS]”

private DateTimeFormatter fmt = new DateTimeFormatterBuilder() 
.append(DateTimeFormat.forPattern("yyyy-MM-dd"))            
.appendOptional(
    new DateTimeFormatterBuilder() 
    .appendLiteral(' ') 
    .append(DateTimeFormat.forPattern("HH:mm:ss")) 
    .toParser() 
.toFormatter(); 
32

JSR-310已经随Java 8一起提供,它为组件现在可选的解析时间值提供了增强的支持。您不仅可以使区域成为可选项,还可以使时间成分可选,并返回给定字符串的正确时间单位。

请考虑以下测试用例。

public class DateFormatTest { 

    private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(
      "yyyy-MM-dd[[ ]['T']HH:mm[:ss][XXX]]"); 

    private TemporalAccessor parse(String v) { 
     return formatter.parseBest(v, 
            ZonedDateTime::from, 
            LocalDateTime::from, 
            LocalDate::from); 
    } 

    @Test public void testDateTime1() { 
     assertEquals(LocalDateTime.of(2014, 9, 23, 14, 20, 59), 
        parse("2014-09-23T14:20:59")); 
    } 

    @Test public void testDateTime2() { 
     assertEquals(LocalDateTime.of(2014, 9, 23, 14, 20), 
        parse("2014-09-23 14:20")); 
    } 

    @Test public void testDateOnly() { 
     assertEquals(LocalDate.of(2014, 9, 23), parse("2014-09-23")); 
    } 

    @Test public void testZonedDateTime() { 
     assertEquals(ZonedDateTime.of(2014, 9, 23, 14, 20, 59, 0, 
             ZoneOffset.ofHoursMinutes(10, 30)), 
        parse("2014-09-23T14:20:59+10:30")); 
    } 

} 

这里"yyyy-MM-dd[[ ]['T']HH:mm[:ss][XXX]]"DateTimeFormatter模式允许方括号其也可以嵌套在自选。模式也可以从一个DateTimeFormatterBuilder,其中,上述图案这里展示构造:

private final DateTimeFormatter formatter = new DateTimeFormatterBuilder() 
     .parseCaseInsensitive() 
     .append(DateTimeFormatter.ISO_LOCAL_DATE) 
     .optionalStart() 
     .optionalStart() 
     .appendLiteral(' ') 
     .optionalEnd() 
     .optionalStart() 
     .appendLiteral('T') 
     .optionalEnd() 
     .appendOptional(DateTimeFormatter.ISO_TIME) 
     .toFormatter(); 

这将转化为它看起来像下面的表达式:

yyyy-MM-dd[[' ']['T']HH:mm[':'ss[.SSS]]]. 

可选值可以嵌套,并且如果仍然打开,也会在最后自动关闭。但是请注意,有没有办法提供一个额外的或选装件,因此上述格式实际上解析以下值相当精致:

2018-03-08 T11:12 

注意的很整洁功能,我们可以重用现有的格式化的作为部分我们目前的格式。

+0

这是两次在行调用'.optionalStart()'一个错误吗? – 2018-03-07 23:08:06

+1

不,多个选项是正确的,它将等同于'yyyy-MM-dd [[''] ['T'] HH:mm [':'ss [.SSS]]]''。可选值可以嵌套,如果仍然打开,它们也会在最后自动关闭。 – 2018-03-07 23:19:19

+1

感谢您的信息!我建议你将这个解释添加到你的答案中。 – 2018-03-07 23:40:40