|
20 | 20 | package org.elasticsearch.common.time; |
21 | 21 |
|
22 | 22 | import org.elasticsearch.common.Strings; |
| 23 | +import org.elasticsearch.common.SuppressForbidden; |
23 | 24 |
|
24 | | -import java.time.DateTimeException; |
25 | 25 | import java.time.DayOfWeek; |
26 | 26 | import java.time.Instant; |
27 | 27 | import java.time.LocalDate; |
| 28 | +import java.time.LocalTime; |
| 29 | +import java.time.Year; |
28 | 30 | import java.time.ZoneId; |
29 | 31 | import java.time.ZoneOffset; |
30 | 32 | import java.time.ZonedDateTime; |
@@ -1550,105 +1552,91 @@ static JavaDateFormatter merge(String pattern, List<DateFormatter> formatters) { |
1550 | 1552 | dateTimeFormatters.toArray(new DateTimeFormatter[0])); |
1551 | 1553 | } |
1552 | 1554 |
|
1553 | | - private static final ZonedDateTime EPOCH_ZONED_DATE_TIME = Instant.EPOCH.atZone(ZoneOffset.UTC); |
| 1555 | + private static final LocalDate LOCALDATE_EPOCH = LocalDate.of(1970, 1, 1); |
1554 | 1556 |
|
1555 | | - public static ZonedDateTime toZonedDateTime(TemporalAccessor accessor) { |
1556 | | - return toZonedDateTime(accessor, EPOCH_ZONED_DATE_TIME); |
1557 | | - } |
1558 | | - |
1559 | | - public static ZonedDateTime toZonedDateTime(TemporalAccessor accessor, ZonedDateTime defaults) { |
1560 | | - try { |
1561 | | - return ZonedDateTime.from(accessor); |
1562 | | - } catch (DateTimeException e ) { |
| 1557 | + /** |
| 1558 | + * Convert a temporal accessor to a zoned date time object - as performant as possible. |
| 1559 | + * The .from() methods from the JDK are throwing exceptions when for example ZonedDateTime.from(accessor) |
| 1560 | + * or Instant.from(accessor). This results in a huge performance penalty and should be prevented |
| 1561 | + * This method prevents exceptions by querying the accessor for certain capabilities |
| 1562 | + * and then act on it accordingly |
| 1563 | + * |
| 1564 | + * This action assumes that we can reliably fall back to some defaults if not all parts of a |
| 1565 | + * zoned date time are set |
| 1566 | + * |
| 1567 | + * - If a zoned date time is passed, it is returned |
| 1568 | + * - If no timezone is found, ZoneOffset.UTC is used |
| 1569 | + * - If we find a time and a date, converting to a ZonedDateTime is straight forward, |
| 1570 | + * no defaults will be applied |
| 1571 | + * - If an accessor only containing of seconds and nanos is found (like epoch_millis/second) |
| 1572 | + * an Instant is created out of that, that becomes a ZonedDateTime with a time zone |
| 1573 | + * - If no time is given, the start of the day is used |
| 1574 | + * - If no month of the year is found, the first day of the year is used |
| 1575 | + * - If an iso based weekyear is found, but not week is specified, the first monday |
| 1576 | + * of the new year is chosen (reataining BWC to joda time) |
| 1577 | + * - If an iso based weekyear is found and an iso based weekyear week, the start |
| 1578 | + * of the day is used |
| 1579 | + * |
| 1580 | + * @param accessor The accessor returned from a parser |
| 1581 | + * |
| 1582 | + * @return The converted zoned date time |
| 1583 | + */ |
| 1584 | + public static ZonedDateTime from(TemporalAccessor accessor) { |
| 1585 | + if (accessor instanceof ZonedDateTime) { |
| 1586 | + return (ZonedDateTime) accessor; |
1563 | 1587 | } |
1564 | 1588 |
|
1565 | | - ZonedDateTime result = defaults; |
1566 | | - |
1567 | | - // special case epoch seconds |
1568 | | - if (accessor.isSupported(ChronoField.INSTANT_SECONDS)) { |
1569 | | - result = result.with(ChronoField.INSTANT_SECONDS, accessor.getLong(ChronoField.INSTANT_SECONDS)); |
1570 | | - if (accessor.isSupported(ChronoField.NANO_OF_SECOND)) { |
1571 | | - result = result.with(ChronoField.NANO_OF_SECOND, accessor.getLong(ChronoField.NANO_OF_SECOND)); |
1572 | | - } |
1573 | | - return result; |
| 1589 | + ZoneId zoneId = accessor.query(TemporalQueries.zone()); |
| 1590 | + if (zoneId == null) { |
| 1591 | + zoneId = ZoneOffset.UTC; |
1574 | 1592 | } |
1575 | | - |
1576 | | - // try to set current year |
1577 | | - if (accessor.isSupported(ChronoField.YEAR)) { |
1578 | | - result = result.with(ChronoField.YEAR, accessor.getLong(ChronoField.YEAR)); |
1579 | | - } else if (accessor.isSupported(ChronoField.YEAR_OF_ERA)) { |
1580 | | - result = result.with(ChronoField.YEAR_OF_ERA, accessor.getLong(ChronoField.YEAR_OF_ERA)); |
| 1593 | + |
| 1594 | + LocalDate localDate = accessor.query(TemporalQueries.localDate()); |
| 1595 | + LocalTime localTime = accessor.query(TemporalQueries.localTime()); |
| 1596 | + boolean isLocalDateSet = localDate != null; |
| 1597 | + boolean isLocalTimeSet = localTime != null; |
| 1598 | + |
| 1599 | + // the first two cases are the most common, so this allows us to exit early when parsing dates |
| 1600 | + if (isLocalDateSet && isLocalTimeSet) { |
| 1601 | + return of(localDate, localTime, zoneId); |
| 1602 | + } else if (accessor.isSupported(ChronoField.INSTANT_SECONDS) && accessor.isSupported(NANO_OF_SECOND)) { |
| 1603 | + return Instant.from(accessor).atZone(zoneId); |
| 1604 | + } else if (isLocalDateSet) { |
| 1605 | + return localDate.atStartOfDay(zoneId); |
| 1606 | + } else if (isLocalTimeSet) { |
| 1607 | + return of(LOCALDATE_EPOCH, localTime, zoneId); |
| 1608 | + } else if (accessor.isSupported(ChronoField.YEAR)) { |
| 1609 | + if (accessor.isSupported(MONTH_OF_YEAR)) { |
| 1610 | + return getFirstOfMonth(accessor).atStartOfDay(zoneId); |
| 1611 | + } else { |
| 1612 | + return Year.of(accessor.get(ChronoField.YEAR)).atDay(1).atStartOfDay(zoneId); |
| 1613 | + } |
1581 | 1614 | } else if (accessor.isSupported(WeekFields.ISO.weekBasedYear())) { |
1582 | 1615 | if (accessor.isSupported(WeekFields.ISO.weekOfWeekBasedYear())) { |
1583 | | - return LocalDate.from(result) |
1584 | | - .with(WeekFields.ISO.weekBasedYear(), accessor.getLong(WeekFields.ISO.weekBasedYear())) |
1585 | | - .withDayOfMonth(1) // makes this compatible with joda |
| 1616 | + return Year.of(accessor.get(WeekFields.ISO.weekBasedYear())) |
| 1617 | + .atDay(1) |
1586 | 1618 | .with(WeekFields.ISO.weekOfWeekBasedYear(), accessor.getLong(WeekFields.ISO.weekOfWeekBasedYear())) |
1587 | | - .atStartOfDay(ZoneOffset.UTC); |
| 1619 | + .atStartOfDay(zoneId); |
1588 | 1620 | } else { |
1589 | | - return LocalDate.from(result) |
1590 | | - .with(WeekFields.ISO.weekBasedYear(), accessor.getLong(WeekFields.ISO.weekBasedYear())) |
1591 | | - // this exists solely to be BWC compatible with joda |
1592 | | -// .with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY)) |
| 1621 | + return Year.of(accessor.get(WeekFields.ISO.weekBasedYear())) |
| 1622 | + .atDay(1) |
1593 | 1623 | .with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)) |
1594 | | - .atStartOfDay(defaults.getZone()); |
1595 | | -// return result.withHour(0).withMinute(0).withSecond(0) |
1596 | | -// .with(WeekFields.ISO.weekBasedYear(), 0) |
1597 | | -// .with(WeekFields.ISO.weekBasedYear(), accessor.getLong(WeekFields.ISO.weekBasedYear())); |
1598 | | -// return ((ZonedDateTime) tmp).with(WeekFields.ISO.weekOfWeekBasedYear(), 1); |
1599 | | - } |
1600 | | - } else if (accessor.isSupported(IsoFields.WEEK_BASED_YEAR)) { |
1601 | | - // special case weekbased year |
1602 | | - result = result.with(IsoFields.WEEK_BASED_YEAR, accessor.getLong(IsoFields.WEEK_BASED_YEAR)); |
1603 | | - if (accessor.isSupported(IsoFields.WEEK_OF_WEEK_BASED_YEAR)) { |
1604 | | - result = result.with(IsoFields.WEEK_OF_WEEK_BASED_YEAR, accessor.getLong(IsoFields.WEEK_OF_WEEK_BASED_YEAR)); |
| 1624 | + .atStartOfDay(zoneId); |
1605 | 1625 | } |
1606 | | - return result; |
1607 | | - } |
1608 | | - |
1609 | | - // month |
1610 | | - if (accessor.isSupported(ChronoField.MONTH_OF_YEAR)) { |
1611 | | - result = result.with(ChronoField.MONTH_OF_YEAR, accessor.getLong(ChronoField.MONTH_OF_YEAR)); |
1612 | 1626 | } |
1613 | 1627 |
|
1614 | | - // day of month |
1615 | | - if (accessor.isSupported(ChronoField.DAY_OF_MONTH)) { |
1616 | | - result = result.with(ChronoField.DAY_OF_MONTH, accessor.getLong(ChronoField.DAY_OF_MONTH)); |
1617 | | - } |
1618 | | - |
1619 | | - // hour |
1620 | | - if (accessor.isSupported(ChronoField.HOUR_OF_DAY)) { |
1621 | | - result = result.with(ChronoField.HOUR_OF_DAY, accessor.getLong(ChronoField.HOUR_OF_DAY)); |
1622 | | - } |
1623 | | - |
1624 | | - // minute |
1625 | | - if (accessor.isSupported(ChronoField.MINUTE_OF_HOUR)) { |
1626 | | - result = result.with(ChronoField.MINUTE_OF_HOUR, accessor.getLong(ChronoField.MINUTE_OF_HOUR)); |
1627 | | - } |
1628 | | - |
1629 | | - // second |
1630 | | - if (accessor.isSupported(ChronoField.SECOND_OF_MINUTE)) { |
1631 | | - result = result.with(ChronoField.SECOND_OF_MINUTE, accessor.getLong(ChronoField.SECOND_OF_MINUTE)); |
1632 | | - } |
1633 | | - |
1634 | | - if (accessor.isSupported(ChronoField.OFFSET_SECONDS)) { |
1635 | | - result = result.withZoneSameLocal(ZoneOffset.ofTotalSeconds(accessor.get(ChronoField.OFFSET_SECONDS))); |
1636 | | - } |
1637 | | - |
1638 | | - // millis |
1639 | | - if (accessor.isSupported(ChronoField.MILLI_OF_SECOND)) { |
1640 | | - result = result.with(ChronoField.MILLI_OF_SECOND, accessor.getLong(ChronoField.MILLI_OF_SECOND)); |
1641 | | - } |
1642 | | - |
1643 | | - if (accessor.isSupported(ChronoField.NANO_OF_SECOND)) { |
1644 | | - result = result.with(ChronoField.NANO_OF_SECOND, accessor.getLong(ChronoField.NANO_OF_SECOND)); |
1645 | | - } |
| 1628 | + // we should not reach this piece of code, everything being parsed we should be able to |
| 1629 | + // convert to a zoned date time! If not, we have to extend the above methods |
| 1630 | + throw new IllegalArgumentException("temporal accessor [" + accessor + "] cannot be converted to zoned date time"); |
| 1631 | + } |
1646 | 1632 |
|
1647 | | - ZoneId zoneOffset = accessor.query(TemporalQueries.zone()); |
1648 | | - if (zoneOffset != null) { |
1649 | | - result = result.withZoneSameLocal(zoneOffset); |
1650 | | - } |
| 1633 | + @SuppressForbidden(reason = "ZonedDateTime.of is fine here") |
| 1634 | + private static ZonedDateTime of(LocalDate localDate, LocalTime localTime, ZoneId zoneId) { |
| 1635 | + return ZonedDateTime.of(localDate, localTime, zoneId); |
| 1636 | + } |
1651 | 1637 |
|
1652 | | - return result; |
| 1638 | + @SuppressForbidden(reason = "LocalDate.of is fine here") |
| 1639 | + private static LocalDate getFirstOfMonth(TemporalAccessor accessor) { |
| 1640 | + return LocalDate.of(accessor.get(ChronoField.YEAR), accessor.get(MONTH_OF_YEAR), 1); |
1653 | 1641 | } |
1654 | 1642 | } |
0 commit comments