1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package gov.nist.secauto.metaschema.core.datatype.adapter;
28
29 import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatTypes;
30
31 import gov.nist.secauto.metaschema.core.datatype.AbstractCustomJavaDataTypeAdapter;
32 import gov.nist.secauto.metaschema.core.datatype.object.Date;
33 import gov.nist.secauto.metaschema.core.metapath.function.InvalidValueForCastFunctionException;
34 import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
35 import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDateItem;
36 import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDateTimeItem;
37 import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem;
38 import gov.nist.secauto.metaschema.core.metapath.item.atomic.IUntypedAtomicItem;
39 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
40
41 import java.time.LocalDate;
42 import java.time.LocalTime;
43 import java.time.ZoneOffset;
44 import java.time.ZonedDateTime;
45 import java.time.format.DateTimeParseException;
46 import java.time.temporal.TemporalAccessor;
47 import java.util.List;
48 import java.util.regex.Matcher;
49 import java.util.regex.Pattern;
50
51 import edu.umd.cs.findbugs.annotations.NonNull;
52
53 public class DateAdapter
54 extends AbstractCustomJavaDataTypeAdapter<Date, IDateItem> {
55 @NonNull
56 private static final List<String> NAMES = ObjectUtils.notNull(
57 List.of("date"));
58 private static final Pattern DATE_TIMEZONE = Pattern.compile("^("
59 + "^(?:(?:2000|2400|2800|(?:19|2[0-9](?:0[48]|[2468][048]|[13579][26])))-02-29)"
60 + "|(?:(?:(?:19|2[0-9])[0-9]{2})-02-(?:0[1-9]|1[0-9]|2[0-8]))"
61 + "|(?:(?:(?:19|2[0-9])[0-9]{2})-(?:0[13578]|10|12)-(?:0[1-9]|[12][0-9]|3[01]))"
62 + "|(?:(?:(?:19|2[0-9])[0-9]{2})-(?:0[469]|11)-(?:0[1-9]|[12][0-9]|30))"
63 + ")"
64 + "(Z|[+-][0-9]{2}:[0-9]{2})?$");
65
66 DateAdapter() {
67 super(Date.class);
68 }
69
70 @Override
71 public List<String> getNames() {
72 return NAMES;
73 }
74
75 @Override
76 public JsonFormatTypes getJsonRawType() {
77 return JsonFormatTypes.STRING;
78 }
79
80 @Override
81 public Date parse(String value) {
82 Matcher matcher = DATE_TIMEZONE.matcher(value);
83 if (!matcher.matches()) {
84 throw new IllegalArgumentException("Invalid date: " + value);
85 }
86
87 String parseValue
88 = String.format("%sT00:00:00%s", matcher.group(1), matcher.group(2) == null ? "" : matcher.group(2));
89 try {
90 TemporalAccessor accessor = DateFormats.DATE_TIME_WITH_TZ.parse(parseValue);
91 return new Date(ObjectUtils.notNull(ZonedDateTime.from(accessor)), true);
92 } catch (DateTimeParseException ex) {
93 try {
94 TemporalAccessor accessor = DateFormats.DATE_TIME_WITHOUT_TZ.parse(parseValue);
95 LocalDate date = LocalDate.from(accessor);
96 return new Date(ObjectUtils.notNull(ZonedDateTime.of(date, LocalTime.MIN, ZoneOffset.UTC)), false);
97 } catch (DateTimeParseException ex2) {
98 IllegalArgumentException newEx = new IllegalArgumentException(ex2.getLocalizedMessage(), ex2);
99 newEx.addSuppressed(ex);
100 throw newEx;
101 }
102 }
103 }
104
105 @Override
106 public String asString(Object obj) {
107 Date value = (Date) obj;
108 String retval;
109 if (value.hasTimeZone()) {
110 @SuppressWarnings("null")
111 @NonNull String formatted = DateFormats.DATE_WITH_TZ.format(value.getValue());
112 retval = formatted;
113 } else {
114 @SuppressWarnings("null")
115 @NonNull String formatted = DateFormats.DATE_WITHOUT_TZ.format(value.getValue());
116 retval = formatted;
117 }
118 return retval;
119 }
120
121 @Override
122 public Class<IDateItem> getItemClass() {
123 return IDateItem.class;
124 }
125
126 @Override
127 public IDateItem newItem(Object value) {
128 Date item = toValue(value);
129 return IDateItem.valueOf(item);
130 }
131
132 @Override
133 protected @NonNull IDateItem castInternal(@NonNull IAnyAtomicItem item) {
134 IDateItem retval;
135 if (item instanceof IDateTimeItem) {
136 ZonedDateTime value = ((IDateTimeItem) item).asZonedDateTime();
137 retval = IDateItem.valueOf(value);
138 } else if (item instanceof IStringItem || item instanceof IUntypedAtomicItem) {
139 retval = super.castInternal(item);
140 } else {
141 throw new InvalidValueForCastFunctionException(
142 String.format("unsupported item type '%s'", item.getClass().getName()));
143 }
144 return retval;
145 }
146
147 }