ChronoUnitEnum.java

/*
 * 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
 *
 * http://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.calcite.util.format.postgresql;

import com.google.common.collect.ImmutableSet;

import java.time.temporal.ChronoUnit;
import java.util.Set;

import static org.apache.calcite.util.format.postgresql.DateCalendarEnum.GREGORIAN;
import static org.apache.calcite.util.format.postgresql.DateCalendarEnum.ISO_8601;
import static org.apache.calcite.util.format.postgresql.DateCalendarEnum.JULIAN;
import static org.apache.calcite.util.format.postgresql.DateCalendarEnum.NONE;

/**
 * A component of a datetime. May belong to a type of calendar. Also contains
 * a list of parent values. For example months are in a year. A datetime can be
 * reconstructed from one or more <code>ChronUnitEnum</code> items along with
 * their associated values.
 *
 * <p>Some <code>ChronoUnitEnum</code> items conflict with others.
 */
public enum ChronoUnitEnum {
  ERAS(ChronoUnit.ERAS, NONE),
  CENTURIES(
      ChronoUnit.CENTURIES,
      ImmutableSet.of(ISO_8601, GREGORIAN),
      ERAS),
  YEARS_ISO_8601(
      ChronoUnit.YEARS,
      ISO_8601,
      CENTURIES),
  YEARS_IN_MILLENIA_ISO_8601(
      ChronoUnit.YEARS,
      ISO_8601,
      CENTURIES),
  YEARS_IN_CENTURY_ISO_8601(
      ChronoUnit.YEARS,
      ISO_8601,
      CENTURIES),
  DAYS_IN_YEAR_ISO_8601(
      ChronoUnit.DAYS,
      ISO_8601,
      YEARS_ISO_8601),
  WEEKS_IN_YEAR_ISO_8601(
      ChronoUnit.WEEKS,
      ISO_8601,
      YEARS_ISO_8601),
  DAYS_JULIAN(
      ChronoUnit.DAYS,
      JULIAN),
  YEARS(
      ChronoUnit.YEARS,
      GREGORIAN,
      CENTURIES),
  YEARS_IN_MILLENIA(
      ChronoUnit.YEARS,
      GREGORIAN,
      CENTURIES),
  YEARS_IN_CENTURY(
      ChronoUnit.YEARS,
      GREGORIAN,
      CENTURIES),
  MONTHS_IN_YEAR(
      ChronoUnit.MONTHS,
      GREGORIAN,
      YEARS, YEARS_IN_CENTURY),
  DAYS_IN_YEAR(
      ChronoUnit.DAYS,
      GREGORIAN,
      YEARS, YEARS_IN_CENTURY),
  DAYS_IN_MONTH(
      ChronoUnit.DAYS,
      GREGORIAN,
      MONTHS_IN_YEAR),
  WEEKS_IN_YEAR(
      ChronoUnit.WEEKS,
      GREGORIAN,
      YEARS, YEARS_IN_CENTURY),
  WEEKS_IN_MONTH(
      ChronoUnit.WEEKS,
      GREGORIAN,
      MONTHS_IN_YEAR),
  DAYS_IN_WEEK(
      ChronoUnit.DAYS,
      NONE,
      YEARS_ISO_8601, WEEKS_IN_MONTH, WEEKS_IN_YEAR),
  HOURS_IN_DAY(
      ChronoUnit.HOURS,
      NONE,
      DAYS_IN_YEAR, DAYS_IN_MONTH, DAYS_IN_WEEK),
  HALF_DAYS(
      ChronoUnit.HALF_DAYS,
      NONE,
      DAYS_IN_YEAR, DAYS_IN_MONTH, DAYS_IN_WEEK),
  HOURS_IN_HALF_DAY(
      ChronoUnit.HOURS,
      NONE,
      HALF_DAYS),
  MINUTES_IN_HOUR(
      ChronoUnit.MINUTES,
      NONE,
      HOURS_IN_DAY, HOURS_IN_HALF_DAY),
  SECONDS_IN_DAY(
      ChronoUnit.SECONDS,
      NONE,
      DAYS_IN_YEAR, DAYS_IN_MONTH, DAYS_IN_WEEK),
  SECONDS_IN_MINUTE(
      ChronoUnit.SECONDS,
      NONE,
      MINUTES_IN_HOUR),
  MILLIS(
      ChronoUnit.MILLIS,
      NONE,
      SECONDS_IN_DAY, SECONDS_IN_MINUTE),
  MICROS(
      ChronoUnit.MICROS,
      NONE,
      MILLIS),
  TENTHS_OF_SECOND(
      ChronoUnit.MILLIS,
      NONE,
      SECONDS_IN_DAY, SECONDS_IN_MINUTE),
  HUNDREDTHS_OF_SECOND(
      ChronoUnit.MILLIS,
      NONE,
      SECONDS_IN_DAY, SECONDS_IN_MINUTE),
  THOUSANDTHS_OF_SECOND(
      ChronoUnit.MILLIS,
      NONE,
      SECONDS_IN_DAY, SECONDS_IN_MINUTE),
  TENTHS_OF_MS(
      ChronoUnit.MICROS,
      NONE,
      SECONDS_IN_DAY, SECONDS_IN_MINUTE),
  HUNDREDTHS_OF_MS(
      ChronoUnit.MICROS,
      NONE,
      SECONDS_IN_DAY, SECONDS_IN_MINUTE),
  THOUSANDTHS_OF_MS(
      ChronoUnit.MICROS,
      NONE,
      SECONDS_IN_DAY, SECONDS_IN_MINUTE),
  TIMEZONE_HOURS(
      ChronoUnit.HOURS,
      NONE),
  TIMEZONE_MINUTES(
      ChronoUnit.MINUTES,
      NONE,
      TIMEZONE_HOURS);

  private final ChronoUnit chronoUnit;
  private final ImmutableSet<ChronoUnitEnum> parentUnits;
  private final ImmutableSet<DateCalendarEnum> calendars;

  ChronoUnitEnum(ChronoUnit chronoUnit, DateCalendarEnum calendar,
      ChronoUnitEnum... parentUnits) {
    this.chronoUnit = chronoUnit;
    this.parentUnits = ImmutableSet.copyOf(parentUnits);
    this.calendars = ImmutableSet.of(calendar);
  }

  ChronoUnitEnum(ChronoUnit chronoUnit, Set<DateCalendarEnum> calendars,
      ChronoUnitEnum... parentUnits) {
    this.chronoUnit = chronoUnit;
    this.parentUnits = ImmutableSet.copyOf(parentUnits);
    this.calendars = ImmutableSet.<DateCalendarEnum>builder().addAll(calendars).build();
  }

  /**
   * Get the ChronoUnit value that corresponds to this value.
   *
   * @return a ChronoUnit value
   */
  public ChronoUnit getChronoUnit() {
    return chronoUnit;
  }

  /**
   * Get the set of calendars that this value can be in.
   *
   * @return set of calendars that this value can be in
   */
  public Set<DateCalendarEnum> getCalendars() {
    return calendars;
  }

  /**
   * Checks if the current item can be added to <code>units</code> without causing
   * any conflicts.
   *
   * @param units a <code>Set</code> of items to test against
   * @return <code>true</code> if this item does not conflict with <code>units</code>
   */
  public boolean isCompatible(Set<ChronoUnitEnum> units) {
    if (!calendars.isEmpty()) {
      for (ChronoUnitEnum unit : units) {
        boolean haveCompatibleCalendar = false;

        for (DateCalendarEnum unitCalendar : unit.getCalendars()) {
          for (DateCalendarEnum calendar : calendars) {
            if (unitCalendar == NONE || calendar == NONE
                || unitCalendar.isCalendarCompatible(calendar)) {
              haveCompatibleCalendar = true;
              break;
            }
          }

          if (haveCompatibleCalendar) {
            break;
          }
        }

        if (!haveCompatibleCalendar) {
          return false;
        }
      }
    }

    for (ChronoUnitEnum unit : units) {
      if (parentUnits.equals(unit.parentUnits)) {
        return false;
      }
    }

    return true;
  }
}