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 * The string input must either the ISO8601 local date, offset date time, or instant 021 * formats. Other formats will not be interpreted successfully. 022 * 023 * The stored value will be a {@link ZonedDateTime} by default. 024 * 025 * @see <a href="https://docs.oracle.com/javaee/7/api/javax/ws/rs/QueryParam.html">QueryParam</a> 026 */ 027@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) 028public class InputDate { 029 030 /** 031 * The regex to recognize an ISO8601 date (yyyy-mm-dd) 032 */ 033 private static final String ISO8601_DATE_REGEX = "^\\d{4}-\\d\\d-\\d\\d$"; 034 035 /** 036 * The regex to recognize an ISO8601 date-time, with "T" in the middle 037 */ 038 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)?$"; 039 040 /** 041 * The logger 042 */ 043 private static final Log logger = LogFactory.getLog(InputDate.class); 044 045 /** 046 * Translates the String to an InputDate. This is the method required by JAX-RS. 047 * 048 * @param input The input string, in either epoch milliseconds or ISO8601 format 049 * @return The InputDate 050 */ 051 public static InputDate valueOf(String input) { 052 if (logger.isDebugEnabled()) { 053 logger.debug("Parsing input date: " + input); 054 } 055 056 InputDate result; 057 058 if (input.matches("^\\d+$")) { 059 // Epoch timestamp 060 long timestamp = Long.parseLong(input); 061 result = new InputDate(timestamp); 062 } else if (input.matches(ISO8601_FULL_REGEX)) { 063 TemporalAccessor ta = DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(input); 064 result = new InputDate(ZonedDateTime.from(ta)); 065 } else if (input.matches(ISO8601_DATE_REGEX)) { 066 TemporalAccessor ta = DateTimeFormatter.ISO_DATE.parse(input); 067 LocalDate ld = LocalDate.from(ta); 068 result = new InputDate(ld.atStartOfDay(ZoneId.systemDefault())); 069 } else { 070 throw new IllegalArgumentException("Unrecognized date format (expected epoch milliseconds, ISO8601 date (yyyy-mm-dd), or ISO8601 date-time): " + input); 071 } 072 073 if (logger.isDebugEnabled()) { 074 logger.debug("Parsed input date: " + result.date); 075 } 076 077 return result; 078 } 079 080 /** 081 * The stored date-time object 082 */ 083 private final ZonedDateTime date; 084 085 /** 086 * Constructs a new InputDate from a long value 087 * @param input an epoch millisecond timestamp 088 */ 089 public InputDate(long input) { 090 this.date = Instant.ofEpochMilli(input).atZone(ZoneId.systemDefault()); 091 } 092 093 /** 094 * Constructs a new InputDate from an instant 095 * @param from An instant 096 */ 097 public InputDate(Instant from) { 098 this.date = from.atZone(ZoneId.systemDefault()); 099 } 100 101 /** 102 * Constructs a new InputDate from a {@link Date} 103 * @param input The input date 104 */ 105 public InputDate(Date input) { 106 this.date = ZonedDateTime.ofInstant(input.toInstant(), ZoneId.systemDefault()); 107 } 108 109 /** 110 * Constructs a new InputDate from a ZonedDateTime. The value is 111 * stored directly in this class. 112 * 113 * @param zdt The {@link ZonedDateTime} input 114 */ 115 public InputDate(ZonedDateTime zdt) { 116 this.date = zdt; 117 } 118 119 /** 120 * Returns the epoch millisecond rendition of the date 121 * 122 * @return The date in epoch milliseconds 123 */ 124 public long toEpochMillis() { 125 return date.toInstant().toEpochMilli(); 126 } 127 128 /** 129 * Returns the java.util.Date object 130 * 131 * @return The java date object 132 */ 133 public Date toJavaData() { 134 return Date.from(date.toInstant()); 135 } 136 137 @Override 138 public String toString() { 139 return DateTimeFormatter.ISO_DATE_TIME.format(this.date); 140 } 141 142 /** 143 * Returns the input date as a ZonedDateTime 144 * 145 * @return The ZonedDateTime 146 */ 147 public ZonedDateTime toZonedDateTime() { 148 return date; 149 } 150}