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}