使用新的日期时间 API 格式化日期

我正在使用新的日期时间 API,但是当运行这个时:

public class Test {
public static void main(String[] args){
String dateFormatted = LocalDate.now()
.format(DateTimeFormatter
.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(dateFormatted);
}
}

它抛出:

Exception in thread "main" java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: HourOfDay
at java.time.LocalDate.get0(LocalDate.java:680)
at java.time.LocalDate.getLong(LocalDate.java:659)
at java.time.format.DateTimePrintContext.getValue(DateTimePrintContext.java:298)
at java.time.format.DateTimeFormatterBuilder$NumberPrinterParser.format(DateTimeFormatterBuilder.java:2543)
at java.time.format.DateTimeFormatterBuilder$CompositePrinterParser.format(DateTimeFormatterBuilder.java:2182)
at java.time.format.DateTimeFormatter.formatTo(DateTimeFormatter.java:1745)
at java.time.format.DateTimeFormatter.format(DateTimeFormatter.java:1719)
at java.time.LocalDate.format(LocalDate.java:1685)
at Test.main(Test.java:23)

在查看 LocalDate 类的源代码时,我看到:

  private int get0(TemporalField field) {
switch ((ChronoField) field) {
case DAY_OF_WEEK: return getDayOfWeek().getValue();
case ALIGNED_DAY_OF_WEEK_IN_MONTH: return ((day - 1) % 7) + 1;
case ALIGNED_DAY_OF_WEEK_IN_YEAR: return ((getDayOfYear() - 1) % 7) + 1;
case DAY_OF_MONTH: return day;
case DAY_OF_YEAR: return getDayOfYear();
case EPOCH_DAY: throw new UnsupportedTemporalTypeException("Invalid field 'EpochDay' for get() method, use getLong() instead");
case ALIGNED_WEEK_OF_MONTH: return ((day - 1) / 7) + 1;
case ALIGNED_WEEK_OF_YEAR: return ((getDayOfYear() - 1) / 7) + 1;
case MONTH_OF_YEAR: return month;
case PROLEPTIC_MONTH: throw new UnsupportedTemporalTypeException("Invalid field 'ProlepticMonth' for get() method, use getLong() instead");
case YEAR_OF_ERA: return (year >= 1 ? year : 1 - year);
case YEAR: return year;
case ERA: return (year >= 1 ? 1 : 0);
}
throw new UnsupportedTemporalTypeException("Unsupported field: " + field);
}

正如文件中所描述的:

此方法将基于 类别文件中所描述的字母和符号。

这些字母都是 定义

那么为什么 DateTimeFormatter.ofPattern不允许我们使用一些模式字母呢?

111027 次浏览

LocalDate只表示日期,而不是 DateTime。所以“ HH: mm: ss”在格式化 LocalDate时没有意义。而是使用 LocalDateTime,假设您想同时表示日期和时间。

我想对@James _ D 的正确答案补充以下细节:

背景: 大多数日期和时间库(Java 中的 java.util.Calendar,另见。Net-dateTime 或者 JavaScript 中的 Date或者佩尔的 DateTime)是基于一个通用的独特的时间类型的概念(在德语中有一个诗意的表达“ eierlegende Wollmilchsau”)。在此设计中,不能有不受支持的字段。但是代价很高: 许多时间问题不能用这种不灵活的方法来充分处理,因为很难找到各种时间对象的公分母。

JSR-310选择了另一种方式 ,即允许不同的时间类型,这些时间类型由受支持的内置字段的特定类型集合组成。自然的结果是,并非每个可能的字段都受到每种类型的支持(用户甚至可以定义自己的专门字段)。对于 TemporalAccessor类型的每个对象,对于它所支持的特定字段集,也可以使用 程序化地询问。对于 LocalDate,我们发现:

•DAY_OF_WEEK
•ALIGNED_DAY_OF_WEEK_IN_MONTH
•ALIGNED_DAY_OF_WEEK_IN_YEAR
•DAY_OF_MONTH
•DAY_OF_YEAR
•EPOCH_DAY
•ALIGNED_WEEK_OF_MONTH
•ALIGNED_WEEK_OF_YEAR
•MONTH_OF_YEAR
•PROLEPTIC_MONTH
•YEAR_OF_ERA
•YEAR
•ERA

没有小时 _ OF _ Day-field 来解释 UnsupportedTemporalTypeException的问题。如果我们查看 JSR-310-模式符号到字段的映射,我们会看到符号 H 被映射到不受支持的 HOUR _ OF _ DAY:

/** Map of letters to fields. */
private static final Map<Character, TemporalField> FIELD_MAP = new HashMap<>();
static {
FIELD_MAP.put('G', ChronoField.ERA);
FIELD_MAP.put('y', ChronoField.YEAR_OF_ERA);
FIELD_MAP.put('u', ChronoField.YEAR);
FIELD_MAP.put('Q', IsoFields.QUARTER_OF_YEAR);
FIELD_MAP.put('q', IsoFields.QUARTER_OF_YEAR);
FIELD_MAP.put('M', ChronoField.MONTH_OF_YEAR);
FIELD_MAP.put('L', ChronoField.MONTH_OF_YEAR);
FIELD_MAP.put('D', ChronoField.DAY_OF_YEAR);
FIELD_MAP.put('d', ChronoField.DAY_OF_MONTH);
FIELD_MAP.put('F', ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH);
FIELD_MAP.put('E', ChronoField.DAY_OF_WEEK);
FIELD_MAP.put('c', ChronoField.DAY_OF_WEEK);
FIELD_MAP.put('e', ChronoField.DAY_OF_WEEK);
FIELD_MAP.put('a', ChronoField.AMPM_OF_DAY);
FIELD_MAP.put('H', ChronoField.HOUR_OF_DAY);
FIELD_MAP.put('k', ChronoField.CLOCK_HOUR_OF_DAY);
FIELD_MAP.put('K', ChronoField.HOUR_OF_AMPM);
FIELD_MAP.put('h', ChronoField.CLOCK_HOUR_OF_AMPM);
FIELD_MAP.put('m', ChronoField.MINUTE_OF_HOUR);
FIELD_MAP.put('s', ChronoField.SECOND_OF_MINUTE);
FIELD_MAP.put('S', ChronoField.NANO_OF_SECOND);
FIELD_MAP.put('A', ChronoField.MILLI_OF_DAY);
FIELD_MAP.put('n', ChronoField.NANO_OF_SECOND);
FIELD_MAP.put('N', ChronoField.NANO_OF_DAY);
}

这种字段映射并不意味着具体类型支持该字段。解析分几个步骤进行。字段映射只是第一步。第二步是解析为类型为 TemporalAccessor的原始对象。最后,解析委托给目标类型(这里是 LocalDate) ,并让它决定是否接受已解析中间对象中的所有字段值。

对我来说正确的类是 ZonedDateTime,其中包括时间和时区。

LocalDate没有时间信息,所以你得到一个 UnsupportedTemporalTypeException: Unsupported field: HourOfDay

您可以使用 LocalDateTime,但是您没有时区信息,所以如果您尝试访问它(即使使用一个预定义的格式化程序) ,您将得到一个 UnsupportedTemporalTypeException: Unsupported field: OffsetSeconds

我对这个代码也有类似的问题:

DateTimeFormatter.RFC_1123_DATE_TIME.format(new Date().toInstant())

不支持字段: DayOfMonth

我是这样解决的:

DateTimeFormatter.RFC_1123_DATE_TIME.format(
ZonedDateTime.ofInstant(new Date().toInstant(), ZoneId.systemDefault()))

Kotlin 2022年

fun Long.formatEpochMs(): String {
val date = ZonedDateTime.ofInstant(Instant.ofEpochMilli(this), ZoneId.systemDefault())
val pattern = "d, MMMM yyyy"
return runCatching { DateTimeFormatter.ofPattern(pattern, Locale.ENGLISH).format(date) }
.onFailure { Timber.e(it) }
.getOrNull()
.orEmpty()
}