/** * Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.util.time; import java.io.Serializable; import org.apache.commons.lang.ObjectUtils; import org.threeten.bp.LocalDate; import org.threeten.bp.LocalDateTime; import org.threeten.bp.LocalTime; import org.threeten.bp.OffsetDateTime; import org.threeten.bp.OffsetTime; import org.threeten.bp.ZoneId; import org.threeten.bp.ZoneOffset; import org.threeten.bp.ZonedDateTime; import org.threeten.bp.temporal.Temporal; import org.threeten.bp.temporal.TemporalAccessor; import org.threeten.bp.temporal.TemporalQueries; import com.google.common.base.Objects; import com.opengamma.util.ArgumentChecker; /** * A flexible date-time representation. * <p> * A {@code FlexiDateTime} always stores a date. * In addition is can optionally store a time and a time-zone. * The time-zone can be an offset-based time-zone. * This combination allows the flexi date-time to represent a {@code LocalDate}, * {@code LocalDateTime}, {@code OffsetDateTime} or {@code ZonedDateTime}. * <p> * This class is immutable and thread-safe. */ public final class FlexiDateTime implements Serializable { /** Serialization version. */ private static final long serialVersionUID = 1L; /** * The date, not null. */ private final LocalDate _date; /** * The time, may be null. */ private final LocalTime _time; /** * The zone, may be null. */ private final ZoneId _zone; /** * Obtains a flexi date-time, specifying the local date. * <p> * This factory is strict and requires the date. * * @param date the date, not null * @return the date-time, not null */ public static FlexiDateTime of(LocalDate date) { ArgumentChecker.notNull(date, "date"); return new FlexiDateTime(date, null, null); } /** * Obtains a flexi date-time, specifying the local date and time. * <p> * This factory is strict and requires both the date and time. * * @param date the date, not null * @param time the time, not null * @return the date-time, not null */ public static FlexiDateTime of(LocalDate date, LocalTime time) { ArgumentChecker.notNull(date, "date"); ArgumentChecker.notNull(time, "time"); return new FlexiDateTime(date, time, null); } /** * Obtains a flexi date-time, specifying the local date-time. * <p> * This factory is strict and requires the date-time. * * @param dateTime the date-time, not null * @return the date-time, not null */ public static FlexiDateTime of(LocalDateTime dateTime) { ArgumentChecker.notNull(dateTime, "dateTime"); return new FlexiDateTime(dateTime.toLocalDate(), dateTime.toLocalTime(), null); } /** * Obtains a flexi date-time, specifying the offset date-time. * <p> * This factory is strict and requires the date-time. * * @param dateTime the date-time, not null * @return the date-time, not null */ public static FlexiDateTime of(OffsetDateTime dateTime) { ArgumentChecker.notNull(dateTime, "dateTime"); return new FlexiDateTime(dateTime.toLocalDate(), dateTime.toLocalTime(), dateTime.getOffset()); } /** * Obtains a flexi date-time, specifying the zoned date-time. * <p> * This factory is strict and requires the date-time. * * @param dateTime the date-time, not null * @return the date-time, not null */ public static FlexiDateTime of(ZonedDateTime dateTime) { ArgumentChecker.notNull(dateTime, "dateTime"); return new FlexiDateTime(dateTime.toLocalDate(), dateTime.toLocalTime(), dateTime.getZone()); } /** * Obtains a flexi date-time, specifying an arbitrary temporal. * <p> * This factory examines the temporal, extracting the date, time, offset and zone. * * @param temporal the temporal, not null * @return the date-time, not null */ public static FlexiDateTime from(TemporalAccessor temporal) { ArgumentChecker.notNull(temporal, "calendrical"); LocalDate date = LocalDate.from(temporal); LocalTime time; try { time = LocalTime.from(temporal); } catch (Exception ex) { time = null; } ZoneId zone = temporal.query(TemporalQueries.zone()); return new FlexiDateTime(date, time, zone); } /** * Obtains a flexi date-time, specifying the date and optionally the time. * <p> * This factory requires the date. * * @param date the date, not null * @param time the time, may be null * @return the date-time, not null */ public static FlexiDateTime ofLenient(LocalDate date, OffsetTime time) { ArgumentChecker.notNull(date, "date"); if (time != null) { return new FlexiDateTime(date, time.toLocalTime(), time.getOffset()); } return new FlexiDateTime(date, null, null); } /** * Obtains a flexi date-time, specifying the date and optionally specifying the time and zone. * <p> * This factory requires the date. The time is only mandatory if the zone is non-null. * * @param date the date, not null * @param time the time, may be null unless the zone is non-null * @param zone the zone, may be null * @return the date-time, not null */ public static FlexiDateTime ofLenient(LocalDate date, LocalTime time, ZoneId zone) { ArgumentChecker.notNull(date, "date"); return new FlexiDateTime(date, time, zone); } /** * Creates a flexi date-time for the specified date, time and offset, * handling null inputs by returning null. * * @param date the date, may be null * @param time the time, may be null * @return the date-time, may be null */ public static FlexiDateTime create(LocalDate date, OffsetTime time) { if (date != null) { return FlexiDateTime.create(date, time); } else { return null; } } /** * Creates a flexi date-time for the specified date, time and offset, * handling null inputs by returning null. * * @param date the date, may be null * @param time the time, may be null * @param zone the zone, may be null * @return the date-time, may be null */ public static FlexiDateTime create(LocalDate date, LocalTime time, ZoneId zone) { if (date != null) { return FlexiDateTime.create(date, time, zone); } else { return null; } } //------------------------------------------------------------------------- /** * Creates a new instance. */ private FlexiDateTime(final LocalDate date, LocalTime time, ZoneId zone) { ArgumentChecker.notNull(date, "date"); if (zone != null) { ArgumentChecker.notNull(time, "time"); } _date = date; _time = time; _zone = zone; } //------------------------------------------------------------------------- /** * Gets the local date. * * @return the date, not null */ public LocalDate getDate() { return _date; } /** * Gets the optional local time. * * @return the time, may be null */ public LocalTime getTime() { return _time; } /** * Gets the optional time-zone. * * @return the time-zone, may be null */ public ZoneId getZone() { return _zone; } //------------------------------------------------------------------------- /** * Converts to a local date-time, only if the time is available. * <p> * If the time is not available, an exception is thrown. * * @return the local date-time, not null */ public LocalDateTime toLocalDateTime() { return LocalDateTime.of(_date, _time); } /** * Converts to a local date-time, specifying a default time to use if none is stored. * * @param defaultTime the default time, not null * @return the local date-time, not null */ public LocalDateTime toLocalDateTime(LocalTime defaultTime) { ArgumentChecker.notNull(defaultTime, "defaultTime"); return LocalDateTime.of(_date, Objects.firstNonNull(_time, defaultTime)); } /** * Converts to an offset time, only if all data is available. * <p> * If the time or zone is not available, an exception is thrown. * * @return the offset date-time, not null */ public OffsetTime toOffsetTime() { return toZonedDateTime().toOffsetDateTime().toOffsetTime(); } /** * Converts to an offset date-time, only if all data is available. * <p> * If the time or zone is not available, an exception is thrown. * * @return the offset date-time, not null */ public OffsetDateTime toOffsetDateTime() { return toZonedDateTime().toOffsetDateTime(); } /** * Converts to a zoned date-time, only if all data is available. * <p> * If the time or zone is not available, an exception is thrown. * * @return the zoned date-time, not null */ public ZonedDateTime toZonedDateTime() { return _date.atTime(_time).atZone(_zone); } /** * Converts to a zoned date-time. * <p> * Conversion requires defaults for the time and zone. * * @param defaultTime the default time, not null * @param defaultZone the default zone, not null * @return the zoned date-time, not null */ public ZonedDateTime toZonedDateTime(LocalTime defaultTime, ZoneId defaultZone) { ArgumentChecker.notNull(defaultTime, "defaultTime"); ArgumentChecker.notNull(defaultZone, "defaultZone"); return toLocalDateTime(defaultTime).atZone(Objects.firstNonNull(_zone, defaultZone)); } /** * Converts to the best representation of the date-time. * <p> * This will return the best option of {@code LocalDate}, {@code LocalDateTime}, * {@code OffsetDateTime} or {@code ZonedDateTime} depending on the stored data. * * @return the best representation, not null */ public Temporal toBest() { if (_zone != null) { ZonedDateTime zdt = _date.atTime(_time).atZone(_zone); return (_zone instanceof ZoneOffset ? zdt.toOffsetDateTime() : zdt); } else if (_time != null) { return LocalDateTime.of(_date, _time); } return _date; } /** * Checks if the date-time is complete. * * @return true if complete */ public boolean isComplete() { return _zone != null; } //------------------------------------------------------------------------- /** * Checks if this date-time equals the specified date-time. * <p> * To be equal, the date, time and zone must be equal. * * @param obj the object to compare to, null returns false * @return true if equal */ @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj instanceof FlexiDateTime) { final FlexiDateTime other = (FlexiDateTime) obj; return _date.equals(other._date) && ObjectUtils.equals(_time, other._time) && ObjectUtils.equals(_zone, other._zone); } return false; } /** * A suitable hash code. * * @return the hash code */ @Override public int hashCode() { return _date.hashCode() ^ ObjectUtils.hashCode(_time) ^ ObjectUtils.hashCode(_zone); } //------------------------------------------------------------------------- /** * Returns a string describing the state of this date-time. * <p> * This is the {@code toString()} of the result of {@link #toBest()}. * * @return the string, not null */ @Override public String toString() { return toBest().toString(); } }