001package com.identityworksllc.iiq.common.plugin.vo;
002
003import com.fasterxml.jackson.annotation.JsonAutoDetect;
004import com.fasterxml.jackson.annotation.JsonPropertyDescription;
005
006import java.text.SimpleDateFormat;
007import java.time.*;
008import java.time.format.DateTimeFormatter;
009import java.time.format.TextStyle;
010import java.util.Date;
011import java.util.Locale;
012import java.util.Objects;
013import java.util.StringJoiner;
014import java.util.TimeZone;
015
016/**
017 * A VO class to wrap a date in a known format, allowing clients to
018 * consume it however they wish.
019 */
020@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
021public class ExpandedDate {
022    @JsonPropertyDescription("The date, rendered as an ISO8601 UTC date or date-time string")
023    private final String date;
024
025    @JsonPropertyDescription("The server time zone ID, as returned by Java's ZoneId class")
026    private final String serverTimeZoneId;
027    @JsonPropertyDescription("The server's 'short' time zone name")
028    private final String serverTimeZoneName;
029    @JsonPropertyDescription("The timestamp in Unix epoch milliseconds")
030    private final long timestamp;
031
032    /**
033     * Constructs a new ExpandedDate from the given LocalDate. In this case, the formatted
034     * date will be in the ISO8601 local date standard of yyyy-MM-dd. The timestamp will
035     * be set to local midnight in the system time zone.
036     *
037     * @param in The date to convert
038     */
039    public ExpandedDate(LocalDate in) {
040        Objects.requireNonNull(in, "Cannot expand a null Date");
041
042        DateTimeFormatter dateFormat = DateTimeFormatter.ISO_LOCAL_DATE;
043
044        this.timestamp = in.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
045        this.date = in.format(dateFormat);
046        this.serverTimeZoneId = ZoneId.systemDefault().getId();
047        this.serverTimeZoneName = ZoneId.systemDefault().getDisplayName(TextStyle.SHORT, Locale.US);
048    }
049
050    /**
051     * Constructs a new ExpandedDate by converting the given ZonedDateTime to an epoch instant.
052     * @param date  The date to convert
053     */
054    public ExpandedDate(ZonedDateTime date) {
055        this(Date.from(date.toInstant()));
056    }
057
058    /**
059     * Constructs a new ExpandedDate by converting the given LocalDateTime to an epoch instant
060     * in the system time zone.
061     *
062     * @param date The local date time
063     */
064    public ExpandedDate(LocalDateTime date) {
065        this(Date.from(date.atZone(ZoneId.systemDefault()).toInstant()));
066    }
067
068    /**
069     * Constructs a new ExpandedDate by treating the Instant as a Date with millisecond
070     * precision. This is technically a loss of precision, as Instant values are more
071     * precise than Date values.
072     *
073     * @param instant The instant to convert to an ExpandedDate
074     */
075    public ExpandedDate(Instant instant) {
076        this(Date.from(instant));
077    }
078
079    /**
080     * Constructs a new ExpandedDate from the given input date
081     * @param in The input date, not null
082     */
083    public ExpandedDate(Date in) {
084        Objects.requireNonNull(in, "Cannot expand a null Date");
085
086        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
087        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
088
089        this.timestamp = in.getTime();
090        this.date = dateFormat.format(in);
091        this.serverTimeZoneId = ZoneId.systemDefault().getId();
092        this.serverTimeZoneName = ZoneId.systemDefault().getDisplayName(TextStyle.SHORT, Locale.US);
093    }
094
095    /**
096     * @return the input date as an ISO8601 timestamp
097     */
098    public String getDate() {
099        return date;
100    }
101
102    /**
103     * @return the server time zone ID according to {@link ZoneId#getId()}
104     */
105    public String getServerTimeZoneId() {
106        return serverTimeZoneId;
107    }
108
109    /**
110     * @return the server time zone ID according to {@link ZoneId#getDisplayName(TextStyle, Locale)}}, with a text style of {@link TextStyle#SHORT} and a locale of {@link Locale#US}.
111     */
112    public String getServerTimeZoneName() {
113        return serverTimeZoneName;
114    }
115
116    /**
117     * @return the timestamp as a millisecond Unix epoch offset
118     */
119    public long getTimestamp() {
120        return timestamp;
121    }
122
123    @Override
124    public String toString() {
125        StringJoiner joiner = new StringJoiner(", ", ExpandedDate.class.getSimpleName() + "[", "]");
126        if ((date) != null) {
127            joiner.add("date='" + date + "'");
128        }
129        if ((serverTimeZoneId) != null) {
130            joiner.add("serverTimeZoneId='" + serverTimeZoneId + "'");
131        }
132        if ((serverTimeZoneName) != null) {
133            joiner.add("serverTimeZoneName='" + serverTimeZoneName + "'");
134        }
135        joiner.add("timestamp=" + timestamp);
136        return joiner.toString();
137    }
138}