Helpful resources for migrating from Joda Time to Java 8 JSR 310.
Last active
February 2, 2024 15:36
-
-
Save simon04/26f68a3f21f76dc0bc1ff012676432c9 to your computer and use it in GitHub Desktop.
Migrating from Joda Time to Java 8 JSR 310
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.google.common.collect.Range; | |
import java.time.Duration; | |
import java.time.Instant; | |
import java.time.Period; | |
import java.time.ZoneId; | |
import java.time.ZonedDateTime; | |
import java.time.format.DateTimeParseException; | |
import java.time.temporal.TemporalAmount; | |
import java.util.Objects; | |
/** | |
* A {@linkplain Range range} of {@link ZonedDateTime} | |
* | |
* @author Simon Legner, 2016 | |
*/ | |
public class Interval { | |
private final Range<ZonedDateTime> range; | |
private Interval(final Range<ZonedDateTime> range) { | |
this.range = range; | |
} | |
public ZonedDateTime getStart() { | |
return range.lowerEndpoint(); | |
} | |
public ZonedDateTime getEnd() { | |
return range.upperEndpoint(); | |
} | |
public long getStartMillis() { | |
return range.lowerEndpoint().toInstant().toEpochMilli(); | |
} | |
public long getEndMillis() { | |
return range.upperEndpoint().toInstant().toEpochMilli(); | |
} | |
public static Interval create(final ZonedDateTime begin, final ZonedDateTime end) { | |
return new Interval(Range.openClosed(begin, end)); | |
} | |
public static Interval create(final TemporalAmount begin, final ZonedDateTime end) { | |
return new Interval(Range.openClosed(end.minus(begin), end)); | |
} | |
public static Interval create(final ZonedDateTime begin, final TemporalAmount end) { | |
return new Interval(Range.openClosed(begin, begin.plus(end))); | |
} | |
public static Interval create(long begin, long end, ZoneId timeZone) { | |
return create(Instant.ofEpochMilli(begin).atZone(timeZone), Instant.ofEpochMilli(end).atZone(timeZone)); | |
} | |
public Interval span(final Interval other) { | |
return new Interval(range.span(other.range)); | |
} | |
public Range<ZonedDateTime> toRange() { | |
return range; | |
} | |
public Duration toDuration() { | |
return Duration.between(getStart(), getEnd()); | |
} | |
public Period toPeriod() { | |
return Period.between(getStart().toLocalDate(), getEnd().toLocalDate()); | |
} | |
@Override | |
public String toString() { | |
return range.toString(); | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) return true; | |
if (o == null || getClass() != o.getClass()) return false; | |
final Interval interval = (Interval) o; | |
return Objects.equals(range, interval.range); | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash(range); | |
} | |
/** | |
* Parses an interval specified as 'datetime/datetime', 'datetime/period', 'period/datetime', or 'period/datetime/period'. | |
* @param text the interval to parse | |
* @return the parsed interval | |
*/ | |
public static Interval parse(String text) { | |
final String[] parts = text.split("/"); | |
if (parts.length == 3) { | |
final PeriodDuration periodMinus = PeriodDuration.parse(parts[0]); | |
final PeriodDuration periodPlus = PeriodDuration.parse(parts[2]); | |
final ZonedDateTime dateTime = ZonedDateTimeConverter.parse(parts[1]); | |
return create(dateTime.minus(periodMinus), dateTime.plus(periodPlus)); | |
} else if (parts.length != 2) { | |
throw new DateTimeParseException("Text cannot be parsed to a Interval", text, 0); | |
} else if (parts[0].startsWith("P")) { | |
return create(PeriodDuration.parse(parts[0]), ZonedDateTimeConverter.parse(parts[1])); | |
} else if (parts[1].startsWith("P")) { | |
return create(ZonedDateTimeConverter.parse(parts[0]), PeriodDuration.parse(parts[1])); | |
} else { | |
return create(ZonedDateTimeConverter.parse(parts[0]), ZonedDateTimeConverter.parse(parts[1])); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.time.Duration; | |
import java.time.Instant; | |
import java.time.OffsetDateTime; | |
import java.time.Period; | |
import java.time.ZoneOffset; | |
import java.time.format.DateTimeParseException; | |
import java.time.temporal.ChronoUnit; | |
import java.time.temporal.Temporal; | |
import java.time.temporal.TemporalAmount; | |
import java.time.temporal.TemporalUnit; | |
import java.util.List; | |
import java.util.Objects; | |
import java.util.regex.Matcher; | |
import java.util.regex.Pattern; | |
import java.util.stream.Collectors; | |
import java.util.stream.Stream; | |
/** | |
* A combination of {@link Period} and {@link Duration} in order to represent an amount of time | |
* consisting of a date and time part. | |
* | |
* @author Simon Legner, 2016 | |
*/ | |
public class PeriodDuration implements TemporalAmount { | |
private final Period period; | |
private final Duration duration; | |
private PeriodDuration(Period period, Duration duration) { | |
this.period = period; | |
this.duration = duration; | |
} | |
public static PeriodDuration create(final Period period, final Duration duration) { | |
return new PeriodDuration(period, duration); | |
} | |
@Override | |
public long get(final TemporalUnit unit) { | |
return period.getUnits().contains(unit) ? period.get(unit) : duration.get(unit); | |
} | |
@Override | |
public List<TemporalUnit> getUnits() { | |
return Stream.concat(period.getUnits().stream(), duration.getUnits().stream()).collect(Collectors.toList()); | |
} | |
@Override | |
public Temporal addTo(final Temporal temporal) { | |
return duration.addTo(period.addTo(temporal)); | |
} | |
@Override | |
public Temporal subtractFrom(final Temporal temporal) { | |
return duration.subtractFrom(period.subtractFrom(temporal)); | |
} | |
@Override | |
public String toString() { | |
return period.toString() + duration.toString().substring(1); | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) return true; | |
if (o == null) return false; | |
if (o instanceof PeriodDuration) { | |
final PeriodDuration that = (PeriodDuration) o; | |
return Objects.equals(period, that.period) && | |
Objects.equals(duration, that.duration); | |
} else if (o instanceof Period) { | |
return Objects.equals(period, o) && Duration.ZERO.equals(duration); | |
} else if (o instanceof Duration) { | |
return Period.ZERO.equals(period) && Objects.equals(duration, o); | |
} | |
return false; | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash(period, duration); | |
} | |
/** | |
* Parses an amount of time in the format {@code PyYmMwWdDThHmMsS}. | |
* @param text the amount of time to parse | |
* @return the parsed amount of time | |
*/ | |
public static PeriodDuration parse(final CharSequence text) { | |
final Matcher matcher = Pattern.compile("([+-]?)P([^T]*)T?(.*)").matcher(text); | |
if (matcher.matches()) { | |
final int negate = "-".equals(matcher.group(1)) ? -1 : 1; | |
final Period period = matcher.group(2).isEmpty() ? Period.ZERO : Period.parse("P" + matcher.group(2)); | |
final Duration duration = matcher.group(3).isEmpty() ? Duration.ZERO : Duration.parse("PT" + matcher.group(3)); | |
return new PeriodDuration(period.multipliedBy(negate), duration.multipliedBy(negate)); | |
} | |
throw new DateTimeParseException("Text cannot be parsed to a PeriodDuration", text, 0); | |
} | |
/** | |
* Calculates the amount of time in {@code unit}. | |
* <p> | |
* The start point of time is taken to be {@link Instant#EPOCH}, and the time zone is {@link ZoneOffset#UTC}. | |
* @param unit the unit | |
* @return the amount of time specified by this instance | |
*/ | |
public long getAmountOf(final ChronoUnit unit) { | |
final OffsetDateTime epoch = Instant.EPOCH.atOffset(ZoneOffset.UTC); | |
return unit.between(epoch, epoch.plus(this)); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.time.ZoneId; | |
import java.time.ZonedDateTime; | |
import java.time.format.DateTimeFormatter; | |
import java.time.format.DateTimeFormatterBuilder; | |
import java.time.format.SignStyle; | |
import java.util.function.Function; | |
import static java.time.temporal.ChronoField.DAY_OF_MONTH; | |
import static java.time.temporal.ChronoField.HOUR_OF_DAY; | |
import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; | |
import static java.time.temporal.ChronoField.MONTH_OF_YEAR; | |
import static java.time.temporal.ChronoField.NANO_OF_SECOND; | |
import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; | |
import static java.time.temporal.ChronoField.YEAR; | |
/** | |
* Parses ISO 8601 date/times where month/day/hour/minute/second/sub-second may be omitted. | |
* | |
* @author Simon Legner, 2016 | |
*/ | |
public class ZonedDateTimeConverter implements Function<String, ZonedDateTime> { | |
private static final DateTimeFormatter MONTH = new DateTimeFormatterBuilder() | |
.appendLiteral('-') | |
.appendValue(MONTH_OF_YEAR, 1, 2, SignStyle.NOT_NEGATIVE) | |
.toFormatter(); | |
private static final DateTimeFormatter DAY = new DateTimeFormatterBuilder() | |
.appendLiteral('-') | |
.appendValue(DAY_OF_MONTH, 1, 2, SignStyle.NOT_NEGATIVE) | |
.toFormatter(); | |
private static final DateTimeFormatter TIME = new DateTimeFormatterBuilder() | |
.appendLiteral('T') | |
.appendValue(HOUR_OF_DAY, 1, 2, SignStyle.NOT_NEGATIVE) | |
.optionalStart() | |
.appendLiteral(':') | |
.appendValue(MINUTE_OF_HOUR, 1, 2, SignStyle.NOT_NEGATIVE) | |
.optionalStart() | |
.appendLiteral(':') | |
.appendValue(SECOND_OF_MINUTE, 1, 2, SignStyle.NOT_NEGATIVE) | |
.optionalStart() | |
.appendFraction(NANO_OF_SECOND, 0, 9, true) | |
.optionalEnd() | |
.optionalEnd() | |
.optionalEnd() | |
.toFormatter(); | |
private static final DateTimeFormatter ZONE = new DateTimeFormatterBuilder() | |
.appendZoneOrOffsetId() | |
.optionalStart() | |
.appendLiteral('[') | |
.parseCaseSensitive() | |
.appendZoneRegionId() | |
.appendLiteral(']') | |
.toFormatter(); | |
public static final DateTimeFormatter JODA_LIKE_PARSER = new DateTimeFormatterBuilder() | |
.appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD) | |
.appendOptional(MONTH) | |
.appendOptional(DAY) | |
.appendOptional(TIME) | |
.appendOptional(ZONE) | |
.parseDefaulting(MONTH_OF_YEAR, 1) | |
.parseDefaulting(DAY_OF_MONTH, 1) | |
.parseDefaulting(HOUR_OF_DAY, 0) | |
.parseDefaulting(MINUTE_OF_HOUR, 0) | |
.parseDefaulting(SECOND_OF_MINUTE, 0) | |
.toFormatter() | |
.withZone(ZoneId.systemDefault()); | |
public static ZonedDateTime parse(String input) { | |
return ZonedDateTime.parse(input, JODA_LIKE_PARSER); | |
} | |
@Override | |
public ZonedDateTime apply(String input) { | |
return parse(input); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment