Skip to content

Instantly share code, notes, and snippets.

@simon04
Last active February 2, 2024 15:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save simon04/26f68a3f21f76dc0bc1ff012676432c9 to your computer and use it in GitHub Desktop.
Save simon04/26f68a3f21f76dc0bc1ff012676432c9 to your computer and use it in GitHub Desktop.
Migrating from Joda Time to Java 8 JSR 310
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]));
}
}
}
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));
}
}
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