001package com.identityworksllc.iiq.common.plugin.vo;
002
003import com.fasterxml.jackson.annotation.JsonAutoDetect;
004import org.apache.commons.logging.Log;
005import org.apache.commons.logging.LogFactory;
006
007import java.time.Instant;
008import java.time.LocalDate;
009import java.time.ZoneId;
010import java.time.ZonedDateTime;
011import java.time.format.DateTimeFormatter;
012import java.time.temporal.TemporalAccessor;
013import java.util.Date;
014
015/**
016 * The JAX-RS spec requires that an input bean for {@link javax.ws.rs.QueryParam}
017 * or {@link javax.ws.rs.PathParam }have either a String constructor or a static
018 * valueOf(String) method. This is that bean, for dates.
019 */
020@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
021public class InputDate {
022
023    /**
024     * The regex to recognize an ISO8601 date (yyyy-mm-dd)
025     */
026    private static final String ISO8601_DATE_REGEX = "^\\d{4}-\\d\\d-\\d\\d$";
027
028    /**
029     * The regex to recognize an ISO8601 date-time, with "T" in the middle
030     */
031    private static final String ISO8601_FULL_REGEX = "^\\d{4}-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d(.\\d+)?(([+-]\\d\\d:\\d\\d)|Z)?$";
032
033    /**
034     * The logger
035     */
036    private static final Log logger = LogFactory.getLog(InputDate.class);
037
038    /**
039     * Translates the String to an InputDate. This is the method required by JAX-RS.
040     *
041     * @param input The input string, in either epoch milliseconds or ISO8601 format
042     * @return The InputDate
043     */
044    public static InputDate valueOf(String input) {
045        if (logger.isDebugEnabled()) {
046            logger.debug("Parsing input date: " + input);
047        }
048
049        InputDate result;
050
051        if (input.matches("^\\d+$")) {
052            // Epoch timestamp
053            long timestamp = Long.parseLong(input);
054            result = new InputDate(timestamp);
055        } else if (input.matches(ISO8601_FULL_REGEX)) {
056            TemporalAccessor ta = DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(input);
057            result = new InputDate(ZonedDateTime.from(ta));
058        } else if (input.matches(ISO8601_DATE_REGEX)) {
059            TemporalAccessor ta = DateTimeFormatter.ISO_DATE.parse(input);
060            LocalDate ld = LocalDate.from(ta);
061            result = new InputDate(ld.atStartOfDay(ZoneId.systemDefault()));
062        } else {
063            throw new IllegalArgumentException("Unrecognized date format (expected epoch milliseconds, ISO8601 date (yyyy-mm-dd), or ISO8601 date-time): " + input);
064        }
065
066        if (logger.isDebugEnabled()) {
067            logger.debug("Parsed input date: " + result.date);
068        }
069
070        return result;
071    }
072
073    /**
074     * The stored date-time object
075     */
076    private final ZonedDateTime date;
077
078    /**
079     * Constructs a new InputDate from a long value
080     * @param input an epoch millisecond timestamp
081     */
082    public InputDate(long input) {
083        this.date = Instant.ofEpochMilli(input).atZone(ZoneId.systemDefault());
084    }
085
086    /**
087     * Constructs a new InputDate from an instant
088     * @param from An instant
089     */
090    public InputDate(Instant from) {
091        this.date = from.atZone(ZoneId.systemDefault());
092    }
093
094    /**
095     * Constructs a new InputDate from a {@link Date}
096     * @param input The input date
097     */
098    public InputDate(Date input) {
099        this.date = ZonedDateTime.ofInstant(input.toInstant(), ZoneId.systemDefault());
100    }
101
102    /**
103     * Constructs a new InputDate from a ZonedDateTime. The value is
104     * stored directly in this class.
105     *
106     * @param zdt The {@link ZonedDateTime} input
107     */
108    public InputDate(ZonedDateTime zdt) {
109        this.date = zdt;
110    }
111
112    /**
113     * Returns the epoch millisecond rendition of the date
114     *
115     * @return The date in epoch milliseconds
116     */
117    public long toEpochMillis() {
118        return date.toInstant().toEpochMilli();
119    }
120
121    /**
122     * Returns the java.util.Date object
123     *
124     * @return The java date object
125     */
126    public Date toJavaData() {
127        return Date.from(date.toInstant());
128    }
129
130    @Override
131    public String toString() {
132        return DateTimeFormatter.ISO_DATE_TIME.format(this.date);
133    }
134
135    /**
136     * Returns the input date as a ZonedDateTime
137     *
138     * @return The ZonedDateTime
139     */
140    public ZonedDateTime toZonedDateTime() {
141        return date;
142    }
143}