All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.avro.util.TimePeriod Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.avro.util;

import java.io.Serializable;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Period;
import java.time.chrono.ChronoPeriod;
import java.time.chrono.IsoChronology;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAmount;
import java.time.temporal.TemporalUnit;
import java.time.temporal.UnsupportedTemporalTypeException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import static java.time.temporal.ChronoUnit.DAYS;
import static java.time.temporal.ChronoUnit.MILLIS;
import static java.time.temporal.ChronoUnit.MONTHS;
import static java.util.Collections.unmodifiableList;
import static java.util.Objects.requireNonNull;

/**
 * 

* A temporal amount to model an {@link org.apache.avro.LogicalTypes.Duration * Avro duration} (the logical type). *

* *

* It consists of a number of months, days and milliseconds, all modelled as an * unsigned integer. *

* *

* Compared to {@link Period java.time.Period}, this class has a smaller range * ('only' supporting a little less than 358 million years), and cannot support * negative time periods. *

* *

* Compared to {@link Duration java.time.Duration}, this class has less * precision (milliseconds compared to nanoseconds), cannot support negative * durations, and has a much smaller range. Where {@code java.time.Duration} * supports fixed ranges up to about 68 years, {@code TimePeriod} can only * handle about 49 days. *

* *

* Comparison with the regular {@code java.time} classes: *

* * * * * * * * * * * * * * * * * * * * * * * * * * *
TimePeriod{@link Period}{@link Duration}
Precisionmillisecondsdaysnanoseconds
Time range (approx.)0 - 49 daysunsupported-68 - 68 years
Date range (approx.)0 to 370 million years-2.3 to 2.3 billion yearsunsupported
* * @see Avro 1.11 * specification on duration */ public final class TimePeriod implements TemporalAmount, Serializable { private static final long MAX_UNSIGNED_INT = 0xffffffffL; private static final long MONTHS_PER_YEAR = 12; private static final long MONTHS_PER_DECADE = MONTHS_PER_YEAR * 10; private static final long MONTHS_PER_CENTURY = MONTHS_PER_DECADE * 10; private static final long MONTHS_PER_MILLENNIUM = MONTHS_PER_CENTURY * 10; private static final long MILLIS_PER_SECOND = 1_000; private static final long MILLIS_PER_MINUTE = MILLIS_PER_SECOND * 60; private static final long MILLIS_PER_HOUR = MILLIS_PER_MINUTE * 60; private static final long MILLIS_IN_HALF_DAY = MILLIS_PER_HOUR * 12; private static final long MICROS_PER_MILLI = 1_000; private static final long NANOS_PER_MILLI = 1_000_000; private final long months; private final long days; private final long millis; /** * Create a TimePeriod from another TemporalAmount, such as a {@link Period} or * a {@link Duration}. * * @param amount a temporal amount * @return the corresponding TimePeriod */ public static TimePeriod from(TemporalAmount amount) { if (requireNonNull(amount, "amount") instanceof TimePeriod) { return (TimePeriod) amount; } if (amount instanceof ChronoPeriod) { if (!IsoChronology.INSTANCE.equals(((ChronoPeriod) amount).getChronology())) { throw new DateTimeException("TimePeriod requires ISO chronology: " + amount); } } long months = 0; long days = 0; long millis = 0; for (TemporalUnit unit : amount.getUnits()) { if (unit instanceof ChronoUnit) { long unitAmount = amount.get(unit); switch ((ChronoUnit) unit) { case MILLENNIA: months = unsignedInt(months + unitAmount * MONTHS_PER_MILLENNIUM); break; case CENTURIES: months = unsignedInt(months + unitAmount * MONTHS_PER_CENTURY); break; case DECADES: months = unsignedInt(months + unitAmount * MONTHS_PER_DECADE); break; case YEARS: months = unsignedInt(months + unitAmount * MONTHS_PER_YEAR); break; case MONTHS: months = unsignedInt(months + unitAmount); break; case WEEKS: days = unsignedInt(days + unitAmount * 7); break; case DAYS: days = unsignedInt(days + unitAmount); break; case HALF_DAYS: days = unsignedInt(days + (unitAmount / 2)); // Truncates halves if (unitAmount % 2 != 0) { millis = unsignedInt(millis + MILLIS_IN_HALF_DAY); } break; case HOURS: millis = unsignedInt(millis + unitAmount * MILLIS_PER_HOUR); break; case MINUTES: millis = unsignedInt(millis + unitAmount * MILLIS_PER_MINUTE); break; case SECONDS: millis = unsignedInt(millis + unitAmount * MILLIS_PER_SECOND); break; case MILLIS: millis = unsignedInt(millis + unitAmount); break; case MICROS: if (unitAmount % MICROS_PER_MILLI != 0) { throw new DateTimeException( "Cannot add " + unitAmount + " microseconds: not a whole number of milliseconds"); } millis = unsignedInt(millis + unitAmount / MICROS_PER_MILLI); break; case NANOS: if (unitAmount % NANOS_PER_MILLI != 0) { throw new DateTimeException( "Cannot add " + unitAmount + " nanoseconds: not a whole number of milliseconds"); } millis = unsignedInt(millis + unitAmount / NANOS_PER_MILLI); break; default: throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit); } } else { throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit); } } return new TimePeriod(months, days, millis); } /** * Create a TimePeriod from a number of months, days and milliseconds * * @param months a number of months * @param days a number of days * @param millis a number of milliseconds * @return the corresponding TimePeriod * @throws ArithmeticException if any of the parameters does not fit an unsigned * long (0..4294967296) */ public static TimePeriod of(long months, long days, long millis) { return new TimePeriod(unsignedInt(months), unsignedInt(days), unsignedInt(millis)); } private static long unsignedInt(long number) { if (number != (number & MAX_UNSIGNED_INT)) { throw new ArithmeticException("Overflow/underflow of unsigned int"); } return number; } private TimePeriod(long months, long days, long millis) { this.months = months; this.days = days; this.millis = millis; } public Duration toDuration() { return Duration.from(this); } public Period toPeriod() { if (isDateBased()) { // We use unsigned ints, which have double the range of a signed int that // Period uses. We can split months to years and months to ensure there's no // overflow. But we cannot split days, as both days and months have varying // lengths. int yearsAsInt = (int) (months / MONTHS_PER_YEAR); int monthsAsInt = (int) (months % MONTHS_PER_YEAR); int daysAsInt = (int) days; if (days != daysAsInt) { throw new DateTimeException("Too many days: a Period can contain at most " + Integer.MAX_VALUE + " days."); } return Period.ofYears(yearsAsInt).withMonths(monthsAsInt).withDays(daysAsInt); } throw new DateTimeException("Cannot convert this TimePeriod to a Period: is not date based"); } /** * Determines if the TimePeriod is date based (i.e., if its milliseconds * component is 0). * * @return {@code true} iff the TimePeriod is date based */ public boolean isDateBased() { return millis == 0; } /** * Determines if the TimePeriod is time based (i.e., if its months and days * components are 0). * * @return {@code true} iff the TimePeriod is time based */ public boolean isTimeBased() { return months == 0 && days == 0; } public long getMonths() { return months; } public long getDays() { return days; } public long getMillis() { return millis; } @Override public long get(TemporalUnit unit) { if (unit == MONTHS) { return months; } else if (unit == DAYS) { return days; } else if (unit == MILLIS) { return millis; } else { throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit); } } @Override public List getUnits() { List units = new ArrayList<>(); // The zero-checks ensure compatibility with the Java Time classes Period and // Duration where possible. if (months != 0) { units.add(MONTHS); } if (days != 0) { units.add(DAYS); } if (millis != 0) { units.add(MILLIS); } return unmodifiableList(units); } @Override public Temporal addTo(Temporal temporal) { return addTo(temporal, months, days, millis); } @Override public Temporal subtractFrom(Temporal temporal) { return addTo(temporal, -months, -days, -millis); } private Temporal addTo(Temporal temporal, long months, long days, long millis) { // The zero-checks ensure we can add a TimePeriod to a Temporal even when it // does not support all fields, as long as the unsupported fields are zero. if (months != 0) { temporal = temporal.plus(months, MONTHS); } if (days != 0) { temporal = temporal.plus(days, DAYS); } if (millis != 0) { temporal = temporal.plus(millis, MILLIS); } return temporal; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } TimePeriod that = (TimePeriod) o; return months == that.months && days == that.days && millis == that.millis; } @Override public int hashCode() { return Objects.hash(months, days, millis); } @Override public String toString() { StringBuilder buffer = new StringBuilder(); buffer.append("P"); if (months != 0) { int years = (int) (months / MONTHS_PER_YEAR); int monthsLeft = (int) (months % MONTHS_PER_YEAR); if (years != 0) { buffer.append(years).append("Y"); } if (monthsLeft != 0) { buffer.append(monthsLeft).append("M"); } } if (days != 0 || (months == 0 && millis == 0)) { buffer.append(days); } if (millis != 0) { long millisLeft = millis; int hours = (int) (millisLeft / MILLIS_PER_HOUR); millisLeft -= MILLIS_PER_HOUR * hours; int minutes = (int) (millisLeft / MILLIS_PER_MINUTE); millisLeft -= MILLIS_PER_MINUTE * minutes; int seconds = (int) (millisLeft / MILLIS_PER_SECOND); millisLeft -= MILLIS_PER_SECOND * seconds; if (millisLeft != 0) { buffer.append(String.format("T%02d:%02d:%02d.%03d", hours, minutes, seconds, millisLeft)); } else if (seconds != 0) { buffer.append(String.format("T%02d:%02d:%02d", hours, minutes, seconds)); } else if (minutes != 0) { buffer.append(String.format("T%02d:%02d", hours, minutes)); } else { buffer.append(String.format("T%02d", hours)); } } return buffer.toString(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy