PackedFields.java
/*
* Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of JSR-310 nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.threeten.extra;
import static java.time.temporal.ChronoField.EPOCH_DAY;
import static java.time.temporal.ChronoField.HOUR_OF_DAY;
import static java.time.temporal.ChronoField.MINUTE_OF_DAY;
import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
import static java.time.temporal.ChronoField.SECOND_OF_DAY;
import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
import static java.time.temporal.ChronoUnit.DAYS;
import static java.time.temporal.ChronoUnit.FOREVER;
import static java.time.temporal.ChronoUnit.MINUTES;
import static java.time.temporal.ChronoUnit.SECONDS;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.chrono.ChronoLocalDate;
import java.time.chrono.Chronology;
import java.time.format.ResolverStyle;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalField;
import java.time.temporal.TemporalUnit;
import java.time.temporal.ValueRange;
import java.util.Map;
/**
* Temporal fields based on a packed representation.
* <p>
* This provides three fields that use a packed integer representation for dates and times.
*/
public final class PackedFields {
/**
* Packed date field.
* <p>
* This returns the date as a single integer value.
* Only dates from year 1000 to year 9999 are supported.
* The output is always an 8 digit integer.
* For example, the date 2015-12-03 is packed to the integer 20151203.
* <p>
* This field has invalid values within the range of value values.
* For example, 20121301 is invalid as it implies month 13.
* <p>
* When parsing in {@linkplain ResolverStyle#LENIENT lenient mode}, invalid
* dates will be accepted. For example, 20121301 will result in 2013-01-01.
*/
public static final TemporalField PACKED_DATE = PackedDate.INSTANCE;
/**
* Packed hour-minute time field.
* <p>
* This returns the time as a single integer value.
* The output is an integer from 0 to 2359.
* For example, the date 11:30 is packed to the integer 1130.
* <p>
* This field has invalid values within the range of value values.
* For example, 1073 is invalid as it implies the minute is 73.
* <p>
* When parsing in {@linkplain ResolverStyle#LENIENT lenient mode}, invalid
* times will be accepted. For example, 1073 will result in 11:13.
*/
public static final TemporalField PACKED_HOUR_MIN = PackedHourMin.INSTANCE;
/**
* Packed hour-minute-second time field.
* <p>
* This returns the time as a single integer value.
* The output is an integer from 0 to 235959.
* For example, the date 11:30:52 is packed to the integer 113052.
* <p>
* This field has invalid values within the range of value values.
* For example, 107310 is invalid as it implies the minute is 73.
* <p>
* When parsing in {@linkplain ResolverStyle#LENIENT lenient mode}, invalid
* times will be accepted. For example, 107310 will result in 11:13:10.
*/
public static final TemporalField PACKED_TIME = PackedTime.INSTANCE;
/**
* Restricted constructor.
*/
private PackedFields() {
}
//-------------------------------------------------------------------------
/**
* Implementation of packed date.
*/
private static enum PackedDate implements TemporalField {
INSTANCE;
private static final ValueRange RANGE = ValueRange.of(10000101, 99991231);
private static final long serialVersionUID = -38752465672576L;
//-----------------------------------------------------------------------
@Override
public TemporalUnit getBaseUnit() {
return DAYS;
}
@Override
public TemporalUnit getRangeUnit() {
return FOREVER;
}
@Override
public boolean isDateBased() {
return true;
}
@Override
public boolean isTimeBased() {
return false;
}
@Override
public ValueRange range() {
return RANGE;
}
//-----------------------------------------------------------------------
@Override
public boolean isSupportedBy(TemporalAccessor temporal) {
return temporal.isSupported(EPOCH_DAY);
}
@Override
public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
if (!temporal.isSupported(this)) {
throw new DateTimeException("Unsupported field: " + this);
}
return range();
}
@Override
public long getFrom(TemporalAccessor temporal) {
LocalDate date = LocalDate.ofEpochDay(temporal.getLong(EPOCH_DAY));
int year = date.getYear();
if (year < 1000 || year > 9999) {
throw new DateTimeException("Unable to obtain PackedDate from LocalDate: " + date);
}
int moy = date.getMonthValue();
int dom = date.getDayOfMonth();
return year * 10000 + moy * 100 + dom;
}
@SuppressWarnings("unchecked")
@Override
public <R extends Temporal> R adjustInto(R temporal, long newValue) {
LocalDate date = toDate(newValue);
return (R) temporal.with(date);
}
private LocalDate toDate(long newValue) {
if (range().isValidValue(newValue) == false) {
throw new DateTimeException("Invalid value: PackedDate " + newValue);
}
int val = (int) newValue;
int year = val / 10000;
int moy = (val % 10000) / 100;
int dom = val % 100;
return LocalDate.of(year, moy, dom);
}
//-----------------------------------------------------------------------
@Override
public ChronoLocalDate resolve(
Map<TemporalField, Long> fieldValues, TemporalAccessor partialTemporal, ResolverStyle resolverStyle) {
long value = fieldValues.remove(this);
LocalDate date;
if (resolverStyle == ResolverStyle.LENIENT) {
int year = Math.toIntExact(value / 10000);
int moy = (int) ((value % 10000) / 100);
long dom = value % 100;
date = LocalDate.of(year, 1, 1).plusMonths(moy - 1).plusDays(dom - 1);
} else {
date = toDate(value);
}
Chronology chrono = Chronology.from(partialTemporal);
return chrono.date(date);
}
//-----------------------------------------------------------------------
@Override
public String toString() {
return "PackedDate";
}
}
//-------------------------------------------------------------------------
/**
* Implementation of packed hour-min.
*/
private static enum PackedHourMin implements TemporalField {
INSTANCE;
private static final ValueRange RANGE = ValueRange.of(0, 2359);
private static final long serialVersionUID = -871357658587L;
//-----------------------------------------------------------------------
@Override
public TemporalUnit getBaseUnit() {
return MINUTES;
}
@Override
public TemporalUnit getRangeUnit() {
return DAYS;
}
@Override
public boolean isDateBased() {
return false;
}
@Override
public boolean isTimeBased() {
return true;
}
@Override
public ValueRange range() {
return RANGE;
}
//-----------------------------------------------------------------------
@Override
public boolean isSupportedBy(TemporalAccessor temporal) {
return temporal.isSupported(MINUTE_OF_DAY);
}
@Override
public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
if (!temporal.isSupported(this)) {
throw new DateTimeException("Unsupported field: " + this);
}
return range();
}
@Override
public long getFrom(TemporalAccessor temporal) {
int mod = temporal.get(MINUTE_OF_DAY);
int hour = mod / 60;
int min = mod % 60;
return hour * 100 + min;
}
@SuppressWarnings("unchecked")
@Override
public <R extends Temporal> R adjustInto(R temporal, long newValue) {
long hour = newValue / 100;
long min = newValue % 100;
HOUR_OF_DAY.checkValidValue(hour);
MINUTE_OF_HOUR.checkValidValue(min);
return (R) temporal.with(HOUR_OF_DAY, hour).with(MINUTE_OF_HOUR, min);
}
//-----------------------------------------------------------------------
@Override
public ChronoLocalDate resolve(
Map<TemporalField, Long> fieldValues, TemporalAccessor partialTemporal, ResolverStyle resolverStyle) {
long value = fieldValues.remove(this);
long hour = value / 100;
long min = value % 100;
if (resolverStyle != ResolverStyle.LENIENT) {
HOUR_OF_DAY.checkValidValue(hour);
MINUTE_OF_HOUR.checkValidValue(min);
}
long mod = hour * 60 + min;
updateCheckConflict(fieldValues, this, MINUTE_OF_DAY, mod);
return null;
}
//-----------------------------------------------------------------------
@Override
public String toString() {
return "PackedHourMin";
}
}
//-------------------------------------------------------------------------
/**
* Implementation of packed hour-min-sec.
*/
private static enum PackedTime implements TemporalField {
INSTANCE;
private static final ValueRange RANGE = ValueRange.of(0, 235959);
private static final long serialVersionUID = -98266827687L;
//-----------------------------------------------------------------------
@Override
public TemporalUnit getBaseUnit() {
return SECONDS;
}
@Override
public TemporalUnit getRangeUnit() {
return DAYS;
}
@Override
public boolean isDateBased() {
return false;
}
@Override
public boolean isTimeBased() {
return true;
}
@Override
public ValueRange range() {
return RANGE;
}
//-----------------------------------------------------------------------
@Override
public boolean isSupportedBy(TemporalAccessor temporal) {
return temporal.isSupported(SECOND_OF_DAY);
}
@Override
public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
if (!temporal.isSupported(this)) {
throw new DateTimeException("Unsupported field: " + this);
}
return range();
}
@Override
public long getFrom(TemporalAccessor temporal) {
int sod = temporal.get(SECOND_OF_DAY);
int hour = sod / 3600;
int min = (sod / 60) % 60;
int sec = sod % 60;
return hour * 10000 + min * 100 + sec;
}
@SuppressWarnings("unchecked")
@Override
public <R extends Temporal> R adjustInto(R temporal, long newValue) {
RANGE.checkValidValue(newValue, INSTANCE);
long hour = newValue / 10000;
long min = (newValue % 10000) / 100;
long sec = newValue % 100;
HOUR_OF_DAY.checkValidValue(hour);
MINUTE_OF_HOUR.checkValidValue(min);
SECOND_OF_MINUTE.checkValidValue(sec);
long sod = 3600 * hour + 60 * min + sec;
return (R) temporal.with(SECOND_OF_DAY, sod);
}
//-----------------------------------------------------------------------
@Override
public ChronoLocalDate resolve(
Map<TemporalField, Long> fieldValues, TemporalAccessor partialTemporal, ResolverStyle resolverStyle) {
long value = fieldValues.remove(this);
long hour = value / 10000;
long min = (value % 10000) / 100;
long sec = value % 100;
if (resolverStyle != ResolverStyle.LENIENT) {
HOUR_OF_DAY.checkValidValue(hour);
MINUTE_OF_HOUR.checkValidValue(min);
SECOND_OF_MINUTE.checkValidValue(sec);
}
long sod = 3600 * hour + 60 * min + sec;
updateCheckConflict(fieldValues, this, SECOND_OF_DAY, sod);
return null;
}
//-----------------------------------------------------------------------
@Override
public String toString() {
return "PackedTime";
}
}
//-------------------------------------------------------------------------
private static void updateCheckConflict(
Map<TemporalField, Long> fieldValues,
TemporalField targetField,
TemporalField changeField,
long changeValue) {
Long old = fieldValues.put(changeField, changeValue);
if (old != null && changeValue != old.longValue()) {
throw new DateTimeException(
"Conflict found: " + changeField + " " + old +
" differs from " + changeField + " " + changeValue +
" while resolving " + targetField);
}
}
}