SimpleDateFormat 中的可选部分

发布于 2024-11-05 09:27:11 字数 262 浏览 0 评论 0原文

我正在读取可能有或没有时区调整的日期字符串:yyyyMMddHHmmsszyyyyMMddHHmmss。当字符串缺少时区时,我会将其视为 GMT。我没有看到任何在 SimpleDateFormat 中创建可选部分的方法,但也许我错过了一些东西。有没有办法使用 SimpleDateFormat 来做到这一点,或者我应该编写一个新的具体 DateFormat 来处理这个问题?

I'm reading in date strings that could be with or without a time zone adjustment: yyyyMMddHHmmssz or yyyyMMddHHmmss. When a string is missing a zone, I'll treat it as GMT. I'm not seeing any way to create optional sections in a SimpleDateFormat, but maybe I'm missing something. Is there a way to do this with a SimpleDateFormat, or should I just write a new concrete DateFormat to handle this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

请恋爱 2024-11-12 09:27:12

如果您可以使用 Joda Datetime,它支持格式化程序中的可选部分,例如“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();

If you can use Joda Datetime it supports optional parts in formatter, for example, "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();
无敌元气妹 2024-11-12 09:27:12

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

I would loop over the list of potential DateFormat objects using a try-catch operation to break the loop on the first successful parse.

眼眸里的快感 2024-11-12 09:27:12

您可以创建两个不同的 SimpleDateFormats 就像

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



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

You can create two different SimpleDateFormats like

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



    public PWMDateTimeFormatter(String aPatternStr, TimeZone aZone)
    {
        _formatter =   DateTimeFormat.forPattern(aPatternStr).withZone(XXDateTime._getTimeZone(aZone));
    }
浅语花开 2024-11-12 09:27:12

我前段时间通过扩展 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) 完成其余的工作。上面的实现没有根据 SimpleDateFormat#parse(String, ParsePosition) 中的 javadoc 中的约定更新 ParsePosition

该类具有单个默认构造函数,因为只允许一种格式。

方法 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]

I have solved a similar problem some time ago by extending SimpleDateFormat. Below an crude implementation to show the idea of my solution. It may not be fully complete/optimised.

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);
    }
}

The gist is that you need to check the input string and "guess" somehow that the TZ field is missing, add it if so and then let the SimpleDateFormat#parse(String, ParsePosition) do the rest. The implementation above isn't updating ParsePosition according to the contract in javadoc in SimpleDateFormat#parse(String, ParsePosition)

The class has a single default ctor as there's only one format allowed.

The method MySimpleDateFormat#parse(String, ParsePosition) is invoked by SimpleDateFormat#parse(String) so it's sufficient to cover both cases.

Running the main() this is the output (as expected)

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]
若有似无的小暗淡 2024-11-12 09:27:11

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"));
    }

}

这里的 DateTimeFormatter 模式 "yyyy-MM-dd[[ ]['T']HH:mm[:ss][XXX]]" 允许方括号内的选项,也可以嵌套。模式也可以从 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

请注意,我们可以重用现有格式化程序作为当前格式的一部分,这一功能非常巧妙。

JSR-310 has been delivered with Java 8 which provides enhanced support for parsing temporal values where components may now be optional. Not only can you make the zone optional, but you can also make the time component optional and return the correct temporal unit for the given string.

Consider the following test cases.

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"));
    }

}

Here the DateTimeFormatter pattern of "yyyy-MM-dd[[ ]['T']HH:mm[:ss][XXX]]" allows optionals within the square parentheses which can also be nested. Patterns can also be constructed from a DateTimeFormatterBuilder, which the above pattern is demonstrated here:

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();

This would translate to an expression which looks like the following:

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

Optional values can be nested and are also auto closed at the end if still open. Note however that there is no way to provide an exclusive OR on optional parts, thus the above format would actually parse the following value quite fine:

2018-03-08 T11:12

Note the really neat capability that we can reuse existing formatter's as parts of our current format.

时光病人 2024-11-12 09:27:11

我知道这是一篇旧文章,但只是为了记录...

Apache 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 存储库中的 Apache Commons Lang 库,您可能想检查最新版本是什么:
http://mvnrepository.com/artifact/org.apache.commons/commons-lang3

v3.4

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

摇篮 v3.4

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

I know this is an old post but just for the record...

Apache DateUtils class can help you with that.

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);

Link to the Apache Commons Lang library in Maven repository, you might want to check what's the latest version:
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'
时间海 2024-11-12 09:27:11

我将创建两种 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"));

I would create two SimpleDateFormat, one with a time zone and one without. You can look at the length of the String to determine which one to use.


Sounds like you need a DateFormat which delegates to two different 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"));
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文