1 package ca.uhn.fhir.model.primitive;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 import static ca.uhn.fhir.model.api.TemporalPrecisionEnum.*;
24
25 import java.text.ParseException;
26 import java.util.Calendar;
27 import java.util.Date;
28 import java.util.GregorianCalendar;
29 import java.util.TimeZone;
30 import java.util.regex.Pattern;
31
32 import javax.xml.bind.DatatypeConverter;
33
34 import org.apache.commons.lang3.Validate;
35 import org.apache.commons.lang3.time.DateUtils;
36 import org.apache.commons.lang3.time.FastDateFormat;
37
38 import ca.uhn.fhir.model.api.BasePrimitive;
39 import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
40 import ca.uhn.fhir.parser.DataFormatException;
41
42 public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
43
44 private static final Pattern ourYearDashMonthDashDayPattern = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}");
45 private static final Pattern ourYearDashMonthPattern = Pattern.compile("[0-9]{4}-[0-9]{2}");
46 private static final FastDateFormat ourYearFormat = FastDateFormat.getInstance("yyyy");
47 private static final FastDateFormat ourYearMonthDayFormat = FastDateFormat.getInstance("yyyy-MM-dd");
48 private static final FastDateFormat ourYearMonthDayNoDashesFormat = FastDateFormat.getInstance("yyyyMMdd");
49 private static final Pattern ourYearMonthDayPattern = Pattern.compile("[0-9]{4}[0-9]{2}[0-9]{2}");
50 private static final FastDateFormat ourYearMonthDayTimeFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss");
51 private static final FastDateFormat ourYearMonthDayTimeMilliFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS");
52 private static final FastDateFormat ourYearMonthDayTimeMilliZoneFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZZ");
53 private static final FastDateFormat ourYearMonthDayTimeZoneFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ssZZ");
54 private static final FastDateFormat ourYearMonthFormat = FastDateFormat.getInstance("yyyy-MM");
55 private static final FastDateFormat ourYearMonthNoDashesFormat = FastDateFormat.getInstance("yyyyMM");
56 private static final Pattern ourYearMonthPattern = Pattern.compile("[0-9]{4}[0-9]{2}");
57 private static final Pattern ourYearPattern = Pattern.compile("[0-9]{4}");
58
59 private TemporalPrecisionEnum myPrecision = TemporalPrecisionEnum.SECOND;
60 private TimeZone myTimeZone;
61 private boolean myTimeZoneZulu = false;
62 private Date myValue;
63
64
65
66
67
68
69
70 public TemporalPrecisionEnum getPrecision() {
71 return myPrecision;
72 }
73
74 public TimeZone getTimeZone() {
75 return myTimeZone;
76 }
77
78 @Override
79 public Date getValue() {
80 return myValue;
81 }
82
83 @Override
84 public String getValueAsString() {
85 if (myValue == null) {
86 return null;
87 } else {
88 switch (myPrecision) {
89 case DAY:
90 return ourYearMonthDayFormat.format(myValue);
91 case MONTH:
92 return ourYearMonthFormat.format(myValue);
93 case YEAR:
94 return ourYearFormat.format(myValue);
95 case SECOND:
96 if (myTimeZoneZulu) {
97 GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
98 cal.setTime(myValue);
99 return ourYearMonthDayTimeFormat.format(cal) + "Z";
100 } else if (myTimeZone != null) {
101 GregorianCalendar cal = new GregorianCalendar(myTimeZone);
102 cal.setTime(myValue);
103 return ourYearMonthDayTimeZoneFormat.format(cal);
104 } else {
105 return ourYearMonthDayTimeFormat.format(myValue);
106 }
107 case MILLI:
108 if (myTimeZoneZulu) {
109 GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
110 cal.setTime(myValue);
111 return ourYearMonthDayTimeMilliFormat.format(cal) + "Z";
112 } else if (myTimeZone != null) {
113 GregorianCalendar cal = new GregorianCalendar(myTimeZone);
114 cal.setTime(myValue);
115 return ourYearMonthDayTimeMilliZoneFormat.format(cal);
116 } else {
117 return ourYearMonthDayTimeMilliFormat.format(myValue);
118 }
119 }
120 throw new IllegalStateException("Invalid precision (this is a HAPI bug, shouldn't happen): " + myPrecision);
121 }
122 }
123
124 public boolean isTimeZoneZulu() {
125 return myTimeZoneZulu;
126 }
127
128
129
130
131
132
133
134 public boolean isToday() {
135 Validate.notNull(myValue, getClass().getSimpleName() + " contains null value");
136 return DateUtils.isSameDay(new Date(), myValue);
137 }
138
139
140
141
142
143
144
145
146
147
148
149
150 public void setPrecision(TemporalPrecisionEnum thePrecision) throws DataFormatException {
151 if (thePrecision == null) {
152 throw new NullPointerException("Precision may not be null");
153 }
154 myPrecision = thePrecision;
155 }
156
157 public void setTimeZone(TimeZone theTimeZone) {
158 myTimeZone = theTimeZone;
159 }
160
161 public void setTimeZoneZulu(boolean theTimeZoneZulu) {
162 myTimeZoneZulu = theTimeZoneZulu;
163 }
164
165 @Override
166 public void setValue(Date theValue) throws DataFormatException {
167 myValue = theValue;
168 }
169
170 @Override
171 public void setValueAsString(String theValue) throws DataFormatException {
172 try {
173 if (theValue == null) {
174 myValue = null;
175 clearTimeZone();
176 } else if (theValue.length() == 4 && ourYearPattern.matcher(theValue).matches()) {
177 if (isPrecisionAllowed(YEAR)) {
178 setValue((ourYearFormat).parse(theValue));
179 setPrecision(YEAR);
180 clearTimeZone();
181 } else {
182 throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support YEAR precision): " + theValue);
183 }
184 } else if (theValue.length() == 6 && ourYearMonthPattern.matcher(theValue).matches()) {
185
186 if (isPrecisionAllowed(MONTH)) {
187 setValue((ourYearMonthNoDashesFormat).parse(theValue));
188 setPrecision(MONTH);
189 clearTimeZone();
190 } else {
191 throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
192 }
193 } else if (theValue.length() == 7 && ourYearDashMonthPattern.matcher(theValue).matches()) {
194
195 if (isPrecisionAllowed(MONTH)) {
196 setValue((ourYearMonthFormat).parse(theValue));
197 setPrecision(MONTH);
198 clearTimeZone();
199 } else {
200 throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support MONTH precision): " + theValue);
201 }
202 } else if (theValue.length() == 8 && ourYearMonthDayPattern.matcher(theValue).matches()) {
203
204 if (isPrecisionAllowed(DAY)) {
205 setValue((ourYearMonthDayNoDashesFormat).parse(theValue));
206 setPrecision(DAY);
207 clearTimeZone();
208 } else {
209 throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
210 }
211 } else if (theValue.length() == 10 && ourYearDashMonthDashDayPattern.matcher(theValue).matches()) {
212
213 if (isPrecisionAllowed(DAY)) {
214 setValue((ourYearMonthDayFormat).parse(theValue));
215 setPrecision(DAY);
216 clearTimeZone();
217 } else {
218 throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
219 }
220 } else if (theValue.length() >= 18) {
221 int dotIndex = theValue.indexOf('.', 18);
222 if (dotIndex == -1 && !isPrecisionAllowed(SECOND)) {
223 throw new DataFormatException("Invalid date/time string (data type does not support SECONDS precision): " + theValue);
224 } else if (dotIndex > -1 && !isPrecisionAllowed(MILLI)) {
225 throw new DataFormatException("Invalid date/time string (data type " + getClass().getSimpleName() + " does not support MILLIS precision):" + theValue);
226 }
227
228 Calendar cal;
229 try {
230 cal = DatatypeConverter.parseDateTime(theValue);
231 } catch (IllegalArgumentException e) {
232 throw new DataFormatException("Invalid data/time string (" + e.getMessage() + "): " + theValue);
233 }
234 myValue = cal.getTime();
235 if (dotIndex == -1) {
236 setPrecision(TemporalPrecisionEnum.SECOND);
237 } else {
238 setPrecision(TemporalPrecisionEnum.MILLI);
239 }
240
241 clearTimeZone();
242 if (theValue.endsWith("Z")) {
243 myTimeZoneZulu = true;
244 } else if (theValue.indexOf('+', 19) != -1 || theValue.indexOf('-', 19) != -1) {
245 myTimeZone = cal.getTimeZone();
246 }
247
248 } else {
249 throw new DataFormatException("Invalid date/time string (invalid length): " + theValue);
250 }
251 } catch (ParseException e) {
252 throw new DataFormatException("Invalid date string (" + e.getMessage() + "): " + theValue);
253 }
254 }
255
256 private void clearTimeZone() {
257 myTimeZone = null;
258 myTimeZoneZulu = false;
259 }
260
261
262
263
264 abstract boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision);
265
266 }