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
016
017import java.time.Duration;
018import java.time.temporal.ChronoField;
019import java.time.temporal.TemporalAccessor;
020import java.util.Objects;
021
022import org.apache.commons.lang3.builder.HashCodeBuilder;
023import org.apache.commons.lang3.builder.ToStringBuilder;
024import org.apache.commons.lang3.builder.ToStringStyle;
025
026/**
027 * Still experimental, the class should probably require to have a LocalDate otherwise the output
028 * of getResolution can not be comparable with {@link AtomizedLocalDate}
029 *
030 * AtomizedLocalDateTime is a simple immutable class to hold local date and time data from a
031 * {@link TemporalAccessor}.
032 *
033 *
034 * Thread-Safe, immutable class.
035 */
036public class AtomizedLocalDateTime {
037
038  private final AtomizedLocalDate localDate;
039  private final Integer hour;
040  private final Integer minute;
041  private final Integer second;
042  private final Integer millisecond;
043
044  private final int resolution;
045
046
047  private AtomizedLocalDateTime(AtomizedLocalDate localDate, Integer hour, Integer minute,
048                                Integer second, Integer millisecond) {
049    int res = 0;
050    this.localDate = localDate;
051    this.hour = hour;
052    this.minute = minute;
053    this.second = second;
054    this.millisecond = millisecond;
055
056    // compute resolution
057    if(localDate != null){
058      res += localDate.getResolution();
059    }
060
061    if(hour != null){
062      res ++;
063    }
064    if(minute != null){
065      res++;
066    }
067    if(second != null){
068      res++;
069    }
070    if(millisecond != null){
071      res++;
072    }
073    this.resolution = res;
074  }
075
076  public Integer getYear() {
077    if(localDate == null){
078      return null;
079    }
080    return localDate.getYear();
081  }
082
083  public Integer getMonth() {
084    if(localDate == null){
085      return null;
086    }
087    return localDate.getMonth();
088  }
089
090  public Integer getDay() {
091    if(localDate == null){
092      return null;
093    }
094    return localDate.getDay();
095  }
096
097  public Integer getHour() {
098    return hour;
099  }
100
101  public Integer getMinute() {
102    return minute;
103  }
104
105  public Integer getSecond() {
106    return second;
107  }
108
109  public Integer getMillisecond() {
110    return millisecond;
111  }
112
113  /**
114   * Get the resolution of the {@link AtomizedLocalDateTime}.
115   * Resolution represents the number of parts this date/time contains.
116   * Be aware that it is not possible to express partial time which means that the lowest
117   * resolution possible if 4 (Time with no date)
118   * @return
119   */
120  public int getResolution(){
121    return resolution;
122  }
123
124  /**
125   * Build a new instance of {@link AtomizedLocalDateTime} based on a {@link TemporalAccessor}.
126   * This is done by extracting the {@link ChronoField}.
127   * Please note this expected behavior: if a least 1 time component is available on the TemporalAccessor, the other
128   * time components will return 0 instead of null if they are not provided.
129   *
130   * AtomizedLocalDateTime.fromTemporalAccessor(LocalTime.of(HOUR, MINUTE) will return 0 as second and millisecond and
131   * the resolution will be 4.
132   */
133  public static AtomizedLocalDateTime fromTemporalAccessor(TemporalAccessor temporalAccessor) {
134    if (temporalAccessor == null) {
135      return null;
136    }
137
138    //TODO if localDate == null, we should probably return null. If we want to handle time only
139    //we should create AtomizedLocalTime
140
141    AtomizedLocalDate localDate = AtomizedLocalDate.fromTemporalAccessor(temporalAccessor);
142
143
144    Integer h = null, m = null, s = null, ms = null;
145    if (temporalAccessor.isSupported(ChronoField.HOUR_OF_DAY)) {
146      h = temporalAccessor.get(ChronoField.HOUR_OF_DAY);
147    }
148
149    if (temporalAccessor.isSupported(ChronoField.MINUTE_OF_HOUR)) {
150      m = temporalAccessor.get(ChronoField.MINUTE_OF_HOUR);
151    }
152
153    if (temporalAccessor.isSupported(ChronoField.SECOND_OF_MINUTE)) {
154      s = temporalAccessor.get(ChronoField.SECOND_OF_MINUTE);
155    }
156
157    if (temporalAccessor.isSupported(ChronoField.MILLI_OF_SECOND)) {
158      ms = temporalAccessor.get(ChronoField.MILLI_OF_SECOND);
159    } else if (temporalAccessor.isSupported(ChronoField.NANO_OF_SECOND)) {
160      ms = Long.valueOf(Duration.ofNanos(temporalAccessor.get(ChronoField.NANO_OF_SECOND)).toMillis()).intValue();
161    }
162    return new AtomizedLocalDateTime(localDate, h, m, s, ms);
163  }
164
165  @Override
166  public int hashCode() {
167    return new HashCodeBuilder().append(localDate).append(hour).append(minute).
168            append(second).append(millisecond).append(resolution).toHashCode();
169  }
170
171  @Override
172  public boolean equals(Object o) {
173    if (this == o) {
174      return true;
175    }
176    if (!(o instanceof AtomizedLocalDateTime)) {
177      return false;
178    }
179
180    AtomizedLocalDateTime that = (AtomizedLocalDateTime) o;
181    return resolution == that.resolution
182        && Objects.equals(localDate, that.localDate)
183        && Objects.equals(hour, that.hour)
184        && Objects.equals(minute, that.minute)
185        && Objects.equals(second, that.second)
186        && Objects.equals(millisecond, that.millisecond);
187  }
188
189  @Override
190  public String toString() {
191    return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
192            .append("localDate", localDate)
193            .append("hour", hour)
194            .append("minute", minute)
195            .append("second", second)
196            .append("millisecond", millisecond)
197            .append("resolution", resolution)
198            .toString();
199  }
200
201}