001/*
002 * Licensed under the Apache License, Version 2.0 (the "License");
003 * you may not use this file except in compliance with the License.
004 * You may obtain a copy of the License at
005 *
006 *     http://www.apache.org/licenses/LICENSE-2.0
007 *
008 * Unless required by applicable law or agreed to in writing, software
009 * distributed under the License is distributed on an "AS IS" BASIS,
010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011 * See the License for the specific language governing permissions and
012 * limitations under the License.
013 */
014package org.gbif.common.parsers.date;
015
016import org.gbif.utils.PreconditionUtils;
017
018import java.time.format.DateTimeFormatter;
019import java.time.format.DateTimeParseException;
020import java.time.temporal.TemporalAccessor;
021import java.time.temporal.TemporalQuery;
022import java.util.Objects;
023
024import javax.annotation.Nullable;
025import javax.validation.constraints.NotNull;
026
027/**
028 * Adds some flexibility around {@link DateTimeFormatter} with the {@link DateTimeSeparatorNormalizer} and
029 * simple optimization the support of DateComponentOrdering.
030 * <p>
031 * This class is thread-safe once an instance is created.
032 */
033public class DateTimeParser {
034
035  private final DateTimeFormatter formatter;
036  private final DateTimeSeparatorNormalizer normalizer;
037  private final DateComponentOrdering ordering;
038
039  private final TemporalQuery<?>[] types;
040  private final int minLength;
041
042  /**
043   * Package protected constructor.
044   * Use {@link DateTimeParserBuilder}
045   */
046  DateTimeParser(@NotNull DateTimeFormatter formatter, @Nullable DateTimeSeparatorNormalizer normalizer,
047                 @NotNull DateComponentOrdering ordering, TemporalQuery<?>[] type, int minLength) {
048
049    Objects.requireNonNull(formatter, "DateTimeFormatter can not be null");
050    Objects.requireNonNull(ordering, "DateComponentOrdering can not be null");
051    Objects.requireNonNull(type, "TemporalQuery can not be null");
052    PreconditionUtils.checkArgument(minLength > 0, "minLength must be greater than 0");
053
054    this.formatter = formatter;
055    this.ordering = ordering;
056    this.normalizer = normalizer;
057    this.minLength = minLength;
058    this.types = type;
059  }
060
061  public DateComponentOrdering getOrdering() {
062    return ordering;
063  }
064
065  /**
066   * Parses the provided String as a TemporalAccessor if possible, otherwise returns null.
067   * <p>
068   * This function fully support partial dates and will return the best possible date resolution based
069   * on the {@link DateComponentOrdering} provided.
070   * <p>
071   * This function will not throw DateTimeParseException but returns null in case the input
072   * can not be parsed.
073   *
074   * @return TemporalAccessor or null in case the input can not be parsed.
075   */
076  public TemporalAccessor parse(String input) {
077
078    // return fast if minimum length is not meet
079    if (input.length() < minLength) {
080      return null;
081    }
082
083    if (normalizer != null) {
084      input = normalizer.normalize(input);
085    }
086
087    try {
088      if (types.length > 1) {
089        return formatter.parseBest(input, types);
090      }
091      return (TemporalAccessor) formatter.parse(input, types[0]);
092    } catch (DateTimeParseException dpe) {
093    }
094    return null;
095  }
096
097}