如何使用 DateTimeFormatter 在 java8 中处理 yyyy-mm 和 yyyy

How to process yyyy-mm and yyyy in java8 using DateTimeFormatter

我正在使用 SimpleDateFormat 来格式化或验证日期,但我想通过使用 java 8 DateTimeFormatter 使其线程安全。我无法达到某些要求。

我的申请只接受三种格式。 "yyyy-MM-dd"、"yyyy-MM"、"yyyy"

Existing Code gives me desired output:
/*simple date format to process yyyy-MM-dd format
SimpleDateFormat simpleDateFormat1 = new SimpleDateFormat("yyyy-MM-dd")
/*simple date format to process yyyy-MM format
SimpleDateFormat simpleDateFormat2 = new SimpleDateFormat("yyyy-MM")

/*simple date format to process yyyy format
SimpleDateFormat simpleDateFormat3 = new SimpleDateFormat("yyyy")

/* to parse input
simpleDateFormat.parse(input)
/* to format
simpleDateFormat.format(simpleDateFormat1)

这是输入和预期输出:

  input             expected
'2018-03-19'       '2018-03-19'
'2018-03'          '2018-03'
'2018'             '2018'
'2017-02-54'       '2017-02'
'2016-13-19'       '2016'
  1. 如何在 java 8 DateTimeFormaenter code heretter 中获得相同的结果?

    /* java 8 日期时间格式器 DateTimeFormatter dateTimeFormatter = new DateTimeFormatter("yyyy-MM-dd")

当所有年、月和日期值都正确时,以上代码段有效。任何帮助将不胜感激。

Am using SimpleDateFormat to format or validate the dates

永远不要使用 SimpleDateFormat.

与 Java 的最早版本捆绑在一起的可怕日期时间 类 几年前被现代 java.time 类 定义在 JSR 310.

thread-safe by using java 8 DateTimeFormatter

是的,与传统日期时间 类 不同,java.time 类 在设计上使用 immutable objects and are thread-safe

Here is the input and expected output:

您的一些输入可以简单地通过它们的长度来检测。

// Ten-digits long, assume ISO 8601 date.
LocalDate ld = LocalDate.parse( "2018-03-19" ) ;

// Seven digits long, assume ISO 8601 year-month.
YearMonth ym = YearMonth.parse( "2018-03" ) ;

// Four digits, assume year.
Year y = Year.parse( "2018" ) ;

注意以上输入均符合ISO 8601。当 parsing/generating 字符串时,java.time 类 默认使用 ISO 8601 格式。因此无需指定格式化模式。因此不需要明确的 DateTimeFormatter 对象。

'2017-02-54' '2017-02'

这个例子让我很困惑。如果您的意思是“当遇到日期无效的日期时,只需使用年份和月份而忽略日期”,我想您可以这样做。查看 DateTimeFormatter 上的“宽松”模式。也许使用 DateTimeFormatterBuilder 构建一个灵活的 DateTimeFormatter。但坦率地说,我会拒绝这样的数据 作为错误输入。生成可靠数据应该是数据的 发布者 的工作,而不是 消费者 的工作来猜测错误数据背后的意图。

input expected

'2016-13-19' '2016'

再次尝试在我不会玩的危险游戏中猜测无效输入的有效部分。如果月和日无效,你怎么知道年有效?更糟糕的是,如果此数据的发布者可以发出此类错误数据,您怎么知道表面上有效的 2018-03-19 输入实际上是正确的?如果月份 13 是错误的,如何知道月份 03 的输入也不是错误?

向这些有问题的数据的发布者传授有关 ISO 8601 标准的知识,并要求他们修复错误。

就像其他答案中的 Basil Bourque 一样,我不一定相信您所要求的也是最能为您服务的。无论如何,作为对这个好答案的一个小补充,我想提出一种方法来处理最后两种情况,即无效日期,就像你说的那样。

为了验证格式,我首先解析字符串而不验证数字。这将拒绝不属于三种格式中任何一种的字符串,例如 2016-03-19T12:002016 and some nonsenseDateTimeFormatter.parseUnresolved 方法负责这部分。如果出现解析错误,此方法会在我们传递给它的 ParsePosition 对象和 returns null 中设置一个错误索引(因此不会抛出任何异常)。所以我检查是否返回 null

private static DateTimeFormatter yearMonthFormatter = DateTimeFormatter.ofPattern("uuuu-MM")
        .withResolverStyle(ResolverStyle.STRICT);
private static DateTimeFormatter yearFormatter = DateTimeFormatter.ofPattern("uuuu")
        .withResolverStyle(ResolverStyle.STRICT);

public static Temporal parse(String input) {
    // First try the three formats, uuuu-MM-dd, uuuu-MM, uuuu, in turn without resolving
    TemporalAccessor parsed = null;
    for (DateTimeFormatter formatter : Arrays.asList(DateTimeFormatter.ISO_LOCAL_DATE,
            yearMonthFormatter, yearFormatter)) {
        ParsePosition position = new ParsePosition(0);
        TemporalAccessor parseAttempt = formatter.parseUnresolved(input, position);
        if (parseAttempt != null && position.getIndex() == input.length()) {
            // Success; exit loop
            parsed = parseAttempt;
            break;
        }
    }
    if (parsed == null) { // didn’t match any of the three formats
        throw new IllegalArgumentException("Invalid format: " + input);
    }

    // Try resolving to either LocalDate, YearMonth or Year
    try {
        return LocalDate.of(parsed.get(ChronoField.YEAR),
                parsed.get(ChronoField.MONTH_OF_YEAR),
                parsed.get(ChronoField.DAY_OF_MONTH));
    } catch (DateTimeException dteRLd) {
        try {
            return YearMonth.of(parsed.get(ChronoField.YEAR),
                    parsed.get(ChronoField.MONTH_OF_YEAR));
        } catch (DateTimeException dteRYm) {
            return Year.of(parsed.get(ChronoField.YEAR));
        }
    }
}

让我们试试你的例子:

    String[] inputs = {
            "2018-03-19",
            "2018-03",
            "2018",
            "2017-02-54",
            "2016-13-19"
    };

    for (String input : inputs) {
        Temporal parsed = parse(input);
        System.out.format("%-10s  %-10s  %s%n", input, parsed, parsed.getClass().getName());
    }

输出为:

2018-03-19  2018-03-19  java.time.LocalDate
2018-03     2018-03     java.time.YearMonth
2018        2018        java.time.Year
2017-02-54  2017-02     java.time.YearMonth
2016-13-19  2016        java.time.Year