PackedFields.java

  1. /*
  2.  * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
  3.  *
  4.  * All rights reserved.
  5.  *
  6.  * Redistribution and use in source and binary forms, with or without
  7.  * modification, are permitted provided that the following conditions are met:
  8.  *
  9.  *  * Redistributions of source code must retain the above copyright notice,
  10.  *    this list of conditions and the following disclaimer.
  11.  *
  12.  *  * Redistributions in binary form must reproduce the above copyright notice,
  13.  *    this list of conditions and the following disclaimer in the documentation
  14.  *    and/or other materials provided with the distribution.
  15.  *
  16.  *  * Neither the name of JSR-310 nor the names of its contributors
  17.  *    may be used to endorse or promote products derived from this software
  18.  *    without specific prior written permission.
  19.  *
  20.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21.  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22.  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23.  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  24.  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  25.  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  26.  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  27.  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  28.  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  29.  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  30.  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31.  */
  32. package org.threeten.extra;

  33. import static java.time.temporal.ChronoField.EPOCH_DAY;
  34. import static java.time.temporal.ChronoField.HOUR_OF_DAY;
  35. import static java.time.temporal.ChronoField.MINUTE_OF_DAY;
  36. import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
  37. import static java.time.temporal.ChronoField.SECOND_OF_DAY;
  38. import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
  39. import static java.time.temporal.ChronoUnit.DAYS;
  40. import static java.time.temporal.ChronoUnit.FOREVER;
  41. import static java.time.temporal.ChronoUnit.MINUTES;
  42. import static java.time.temporal.ChronoUnit.SECONDS;

  43. import java.time.DateTimeException;
  44. import java.time.LocalDate;
  45. import java.time.chrono.ChronoLocalDate;
  46. import java.time.chrono.Chronology;
  47. import java.time.format.ResolverStyle;
  48. import java.time.temporal.Temporal;
  49. import java.time.temporal.TemporalAccessor;
  50. import java.time.temporal.TemporalField;
  51. import java.time.temporal.TemporalUnit;
  52. import java.time.temporal.ValueRange;
  53. import java.util.Map;

  54. /**
  55.  * Temporal fields based on a packed representation.
  56.  * <p>
  57.  * This provides three fields that use a packed integer representation for dates and times.
  58.  */
  59. public final class PackedFields {

  60.     /**
  61.      * Packed date field.
  62.      * <p>
  63.      * This returns the date as a single integer value.
  64.      * Only dates from year 1000 to year 9999 are supported.
  65.      * The output is always an 8 digit integer.
  66.      * For example, the date 2015-12-03 is packed to the integer 20151203.
  67.      * <p>
  68.      * This field has invalid values within the range of value values.
  69.      * For example, 20121301 is invalid as it implies month 13.
  70.      * <p>
  71.      * When parsing in {@linkplain ResolverStyle#LENIENT lenient mode}, invalid
  72.      * dates will be accepted. For example, 20121301 will result in 2013-01-01.
  73.      */
  74.     public static final TemporalField PACKED_DATE = PackedDate.INSTANCE;
  75.     /**
  76.      * Packed hour-minute time field.
  77.      * <p>
  78.      * This returns the time as a single integer value.
  79.      * The output is an integer from 0 to 2359.
  80.      * For example, the date 11:30 is packed to the integer 1130.
  81.      * <p>
  82.      * This field has invalid values within the range of value values.
  83.      * For example, 1073 is invalid as it implies the minute is 73.
  84.      * <p>
  85.      * When parsing in {@linkplain ResolverStyle#LENIENT lenient mode}, invalid
  86.      * times will be accepted. For example, 1073 will result in 11:13.
  87.      */
  88.     public static final TemporalField PACKED_HOUR_MIN = PackedHourMin.INSTANCE;
  89.     /**
  90.      * Packed hour-minute-second time field.
  91.      * <p>
  92.      * This returns the time as a single integer value.
  93.      * The output is an integer from 0 to 235959.
  94.      * For example, the date 11:30:52 is packed to the integer 113052.
  95.      * <p>
  96.      * This field has invalid values within the range of value values.
  97.      * For example, 107310 is invalid as it implies the minute is 73.
  98.      * <p>
  99.      * When parsing in {@linkplain ResolverStyle#LENIENT lenient mode}, invalid
  100.      * times will be accepted. For example, 107310 will result in 11:13:10.
  101.      */
  102.     public static final TemporalField PACKED_TIME = PackedTime.INSTANCE;

  103.     /**
  104.      * Restricted constructor.
  105.      */
  106.     private PackedFields() {
  107.     }

  108.     //-------------------------------------------------------------------------
  109.     /**
  110.      * Implementation of packed date.
  111.      */
  112.     private static enum PackedDate implements TemporalField {
  113.         INSTANCE;

  114.         private static final ValueRange RANGE = ValueRange.of(10000101, 99991231);
  115.         private static final long serialVersionUID = -38752465672576L;

  116.         //-----------------------------------------------------------------------
  117.         @Override
  118.         public TemporalUnit getBaseUnit() {
  119.             return DAYS;
  120.         }

  121.         @Override
  122.         public TemporalUnit getRangeUnit() {
  123.             return FOREVER;
  124.         }

  125.         @Override
  126.         public boolean isDateBased() {
  127.             return true;
  128.         }

  129.         @Override
  130.         public boolean isTimeBased() {
  131.             return false;
  132.         }

  133.         @Override
  134.         public ValueRange range() {
  135.             return RANGE;
  136.         }

  137.         //-----------------------------------------------------------------------
  138.         @Override
  139.         public boolean isSupportedBy(TemporalAccessor temporal) {
  140.             return temporal.isSupported(EPOCH_DAY);
  141.         }

  142.         @Override
  143.         public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
  144.             if (!temporal.isSupported(this)) {
  145.                 throw new DateTimeException("Unsupported field: " + this);
  146.             }
  147.             return range();
  148.         }

  149.         @Override
  150.         public long getFrom(TemporalAccessor temporal) {
  151.             LocalDate date = LocalDate.ofEpochDay(temporal.getLong(EPOCH_DAY));
  152.             int year = date.getYear();
  153.             if (year < 1000 || year > 9999) {
  154.                 throw new DateTimeException("Unable to obtain PackedDate from LocalDate: " + date);
  155.             }
  156.             int moy = date.getMonthValue();
  157.             int dom = date.getDayOfMonth();
  158.             return year * 10000 + moy * 100 + dom;
  159.         }

  160.         @SuppressWarnings("unchecked")
  161.         @Override
  162.         public <R extends Temporal> R adjustInto(R temporal, long newValue) {
  163.             LocalDate date = toDate(newValue);
  164.             return (R) temporal.with(date);
  165.         }

  166.         private LocalDate toDate(long newValue) {
  167.             if (range().isValidValue(newValue) == false) {
  168.                 throw new DateTimeException("Invalid value: PackedDate " + newValue);
  169.             }
  170.             int val = (int) newValue;
  171.             int year = val / 10000;
  172.             int moy = (val % 10000) / 100;
  173.             int dom = val % 100;
  174.             return LocalDate.of(year, moy, dom);
  175.         }

  176.         //-----------------------------------------------------------------------
  177.         @Override
  178.         public ChronoLocalDate resolve(
  179.                 Map<TemporalField, Long> fieldValues, TemporalAccessor partialTemporal, ResolverStyle resolverStyle) {
  180.             long value = fieldValues.remove(this);
  181.             LocalDate date;
  182.             if (resolverStyle == ResolverStyle.LENIENT) {
  183.                 int year = Math.toIntExact(value / 10000);
  184.                 int moy = (int) ((value % 10000) / 100);
  185.                 long dom = value % 100;
  186.                 date = LocalDate.of(year, 1, 1).plusMonths(moy - 1).plusDays(dom - 1);
  187.             } else {
  188.                 date = toDate(value);
  189.             }
  190.             Chronology chrono = Chronology.from(partialTemporal);
  191.             return chrono.date(date);
  192.         }

  193.         //-----------------------------------------------------------------------
  194.         @Override
  195.         public String toString() {
  196.             return "PackedDate";
  197.         }
  198.     }

  199.     //-------------------------------------------------------------------------
  200.     /**
  201.      * Implementation of packed hour-min.
  202.      */
  203.     private static enum PackedHourMin implements TemporalField {
  204.         INSTANCE;

  205.         private static final ValueRange RANGE = ValueRange.of(0, 2359);
  206.         private static final long serialVersionUID = -871357658587L;

  207.         //-----------------------------------------------------------------------
  208.         @Override
  209.         public TemporalUnit getBaseUnit() {
  210.             return MINUTES;
  211.         }

  212.         @Override
  213.         public TemporalUnit getRangeUnit() {
  214.             return DAYS;
  215.         }

  216.         @Override
  217.         public boolean isDateBased() {
  218.             return false;
  219.         }

  220.         @Override
  221.         public boolean isTimeBased() {
  222.             return true;
  223.         }

  224.         @Override
  225.         public ValueRange range() {
  226.             return RANGE;
  227.         }

  228.         //-----------------------------------------------------------------------
  229.         @Override
  230.         public boolean isSupportedBy(TemporalAccessor temporal) {
  231.             return temporal.isSupported(MINUTE_OF_DAY);
  232.         }

  233.         @Override
  234.         public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
  235.             if (!temporal.isSupported(this)) {
  236.                 throw new DateTimeException("Unsupported field: " + this);
  237.             }
  238.             return range();
  239.         }

  240.         @Override
  241.         public long getFrom(TemporalAccessor temporal) {
  242.             int mod = temporal.get(MINUTE_OF_DAY);
  243.             int hour = mod / 60;
  244.             int min = mod % 60;
  245.             return hour * 100 + min;
  246.         }

  247.         @SuppressWarnings("unchecked")
  248.         @Override
  249.         public <R extends Temporal> R adjustInto(R temporal, long newValue) {
  250.             long hour = newValue / 100;
  251.             long min = newValue % 100;
  252.             HOUR_OF_DAY.checkValidValue(hour);
  253.             MINUTE_OF_HOUR.checkValidValue(min);
  254.             return (R) temporal.with(HOUR_OF_DAY, hour).with(MINUTE_OF_HOUR, min);
  255.         }

  256.         //-----------------------------------------------------------------------
  257.         @Override
  258.         public ChronoLocalDate resolve(
  259.                 Map<TemporalField, Long> fieldValues, TemporalAccessor partialTemporal, ResolverStyle resolverStyle) {
  260.             long value = fieldValues.remove(this);
  261.             long hour = value / 100;
  262.             long min = value % 100;
  263.             if (resolverStyle != ResolverStyle.LENIENT) {
  264.                 HOUR_OF_DAY.checkValidValue(hour);
  265.                 MINUTE_OF_HOUR.checkValidValue(min);
  266.             }
  267.             long mod = hour * 60 + min;
  268.             updateCheckConflict(fieldValues, this, MINUTE_OF_DAY, mod);
  269.             return null;
  270.         }

  271.         //-----------------------------------------------------------------------
  272.         @Override
  273.         public String toString() {
  274.             return "PackedHourMin";
  275.         }
  276.     }

  277.     //-------------------------------------------------------------------------
  278.     /**
  279.      * Implementation of packed hour-min-sec.
  280.      */
  281.     private static enum PackedTime implements TemporalField {
  282.         INSTANCE;

  283.         private static final ValueRange RANGE = ValueRange.of(0, 235959);
  284.         private static final long serialVersionUID = -98266827687L;

  285.         //-----------------------------------------------------------------------
  286.         @Override
  287.         public TemporalUnit getBaseUnit() {
  288.             return SECONDS;
  289.         }

  290.         @Override
  291.         public TemporalUnit getRangeUnit() {
  292.             return DAYS;
  293.         }

  294.         @Override
  295.         public boolean isDateBased() {
  296.             return false;
  297.         }

  298.         @Override
  299.         public boolean isTimeBased() {
  300.             return true;
  301.         }

  302.         @Override
  303.         public ValueRange range() {
  304.             return RANGE;
  305.         }

  306.         //-----------------------------------------------------------------------
  307.         @Override
  308.         public boolean isSupportedBy(TemporalAccessor temporal) {
  309.             return temporal.isSupported(SECOND_OF_DAY);
  310.         }

  311.         @Override
  312.         public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
  313.             if (!temporal.isSupported(this)) {
  314.                 throw new DateTimeException("Unsupported field: " + this);
  315.             }
  316.             return range();
  317.         }

  318.         @Override
  319.         public long getFrom(TemporalAccessor temporal) {
  320.             int sod = temporal.get(SECOND_OF_DAY);
  321.             int hour = sod / 3600;
  322.             int min = (sod / 60) % 60;
  323.             int sec = sod % 60;
  324.             return hour * 10000 + min * 100 + sec;
  325.         }

  326.         @SuppressWarnings("unchecked")
  327.         @Override
  328.         public <R extends Temporal> R adjustInto(R temporal, long newValue) {
  329.             RANGE.checkValidValue(newValue, INSTANCE);
  330.             long hour = newValue / 10000;
  331.             long min = (newValue % 10000) / 100;
  332.             long sec = newValue % 100;
  333.             HOUR_OF_DAY.checkValidValue(hour);
  334.             MINUTE_OF_HOUR.checkValidValue(min);
  335.             SECOND_OF_MINUTE.checkValidValue(sec);
  336.             long sod = 3600 * hour + 60 * min + sec;
  337.             return (R) temporal.with(SECOND_OF_DAY, sod);
  338.         }

  339.         //-----------------------------------------------------------------------
  340.         @Override
  341.         public ChronoLocalDate resolve(
  342.                 Map<TemporalField, Long> fieldValues, TemporalAccessor partialTemporal, ResolverStyle resolverStyle) {
  343.             long value = fieldValues.remove(this);
  344.             long hour = value / 10000;
  345.             long min = (value % 10000) / 100;
  346.             long sec = value % 100;
  347.             if (resolverStyle != ResolverStyle.LENIENT) {
  348.                 HOUR_OF_DAY.checkValidValue(hour);
  349.                 MINUTE_OF_HOUR.checkValidValue(min);
  350.                 SECOND_OF_MINUTE.checkValidValue(sec);
  351.             }
  352.             long sod = 3600 * hour + 60 * min + sec;
  353.             updateCheckConflict(fieldValues, this, SECOND_OF_DAY, sod);
  354.             return null;
  355.         }

  356.         //-----------------------------------------------------------------------
  357.         @Override
  358.         public String toString() {
  359.             return "PackedTime";
  360.         }
  361.     }

  362.     //-------------------------------------------------------------------------
  363.     private static void updateCheckConflict(
  364.             Map<TemporalField, Long> fieldValues,
  365.             TemporalField targetField,
  366.             TemporalField changeField,
  367.             long changeValue) {
  368.        
  369.         Long old = fieldValues.put(changeField, changeValue);
  370.         if (old != null && changeValue != old.longValue()) {
  371.             throw new DateTimeException(
  372.                     "Conflict found: " + changeField + " " + old +
  373.                     " differs from " + changeField + " " + changeValue +
  374.                     " while resolving  " + targetField);
  375.         }
  376.     }

  377. }