1
2
3
4
5
6 package dev.metaschema.core.metapath.item.atomic;
7
8 import java.time.LocalDate;
9 import java.time.LocalDateTime;
10 import java.time.LocalTime;
11 import java.time.OffsetTime;
12 import java.time.ZoneId;
13 import java.time.ZoneOffset;
14 import java.time.ZonedDateTime;
15
16 import dev.metaschema.core.datatype.adapter.MetaschemaDataTypeProvider;
17 import dev.metaschema.core.datatype.object.AmbiguousDateTime;
18 import dev.metaschema.core.metapath.DynamicContext;
19 import dev.metaschema.core.metapath.function.DateTimeFunctionException;
20 import dev.metaschema.core.metapath.function.InvalidValueForCastFunctionException;
21 import dev.metaschema.core.metapath.item.atomic.impl.DateTimeWithoutTimeZoneItemImpl;
22 import dev.metaschema.core.metapath.type.IAtomicOrUnionType;
23 import dev.metaschema.core.metapath.type.InvalidTypeMetapathException;
24 import dev.metaschema.core.util.ObjectUtils;
25 import edu.umd.cs.findbugs.annotations.NonNull;
26 import edu.umd.cs.findbugs.annotations.Nullable;
27
28
29
30
31
32
33
34
35
36
37 public interface IDateTimeItem extends ICalendarTemporalItem {
38
39
40
41
42
43 @NonNull
44 static IAtomicOrUnionType<IDateTimeItem> type() {
45 return MetaschemaDataTypeProvider.DATE_TIME.getItemType();
46 }
47
48 @Override
49 default IAtomicOrUnionType<? extends IDateTimeItem> getType() {
50 return type();
51 }
52
53
54
55
56
57
58
59
60 @NonNull
61 static IDateTimeItem valueOf(@NonNull String value) {
62 try {
63 return valueOf(MetaschemaDataTypeProvider.DATE_TIME.parse(value));
64 } catch (IllegalArgumentException ex) {
65 throw new InvalidTypeMetapathException(
66 null,
67 String.format("Invalid date/time value '%s'. %s",
68 value,
69 ex.getLocalizedMessage()),
70 ex);
71 }
72 }
73
74
75
76
77
78
79
80
81
82
83 @SuppressWarnings("PMD.CyclomaticComplexity")
84 @NonNull
85 static IDateTimeItem valueOf(@NonNull IDateItem date, @NonNull ITimeItem time) {
86 ZonedDateTime zdtDate = ObjectUtils.notNull(date.asZonedDateTime());
87 ZoneId tzDate = date.hasTimezone() ? zdtDate.getZone() : null;
88 OffsetTime zdtTime = ObjectUtils.notNull(time.asOffsetTime());
89 ZoneId tzTime = time.hasTimezone() ? zdtTime.getOffset() : null;
90
91 if (tzDate != null && tzTime != null && !tzDate.equals(tzTime)) {
92 throw new InvalidTypeMetapathException(
93 null,
94 String.format("The date and time values do not have the same timezone value. date='%s', time='%s'",
95 tzDate.toString(),
96 tzTime.toString()));
97 }
98
99
100 ZoneId zone = tzDate == null
101 ? tzTime == null ? null : tzTime
102 : tzDate;
103
104 return valueOf(
105 ObjectUtils.notNull(ZonedDateTime.of(
106 zdtDate.toLocalDate(),
107 zdtTime.toLocalTime(),
108 zone == null ? ZoneOffset.UTC : zone)),
109 zone != null);
110 }
111
112
113
114
115
116
117
118
119 @NonNull
120 static IDateTimeItem valueOf(@NonNull ICalendarTemporalItem item) {
121 return item instanceof IDateTimeItem
122 ? (IDateTimeItem) item
123 : valueOf(item.asZonedDateTime(), item.hasTimezone());
124 }
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141 @NonNull
142 static IDateTimeItem valueOf(@NonNull ZonedDateTime value, boolean hasTimeZone) {
143 return hasTimeZone
144 ? IDateTimeWithTimeZoneItem.valueOf(value)
145 : valueOf(new AmbiguousDateTime(value, false));
146 }
147
148
149
150
151
152
153
154
155
156
157
158
159
160 @NonNull
161 static IDateTimeItem valueOf(@NonNull LocalDateTime value) {
162 return valueOf(new AmbiguousDateTime(ObjectUtils.notNull(value.atZone(ZoneOffset.UTC)), false));
163 }
164
165
166
167
168
169
170
171
172
173
174
175
176
177 @NonNull
178 static IDateTimeItem valueOf(@NonNull AmbiguousDateTime value) {
179 return value.hasTimeZone()
180 ? IDateTimeWithTimeZoneItem.valueOf(value.getValue())
181 : new DateTimeWithoutTimeZoneItemImpl(value);
182 }
183
184 @Override
185 default boolean hasDate() {
186 return true;
187 }
188
189 @Override
190 default boolean hasTime() {
191 return true;
192 }
193
194 @Override
195 default int getYear() {
196 return asZonedDateTime().getYear();
197 }
198
199 @Override
200 default int getMonth() {
201 return asZonedDateTime().getMonthValue();
202 }
203
204 @Override
205 default int getDay() {
206 return asZonedDateTime().getDayOfMonth();
207 }
208
209 @Override
210 default int getHour() {
211 return asZonedDateTime().getHour();
212 }
213
214 @Override
215 default int getMinute() {
216 return asZonedDateTime().getMinute();
217 }
218
219 @Override
220 default int getSecond() {
221 return asZonedDateTime().getSecond();
222 }
223
224 @Override
225 default int getNano() {
226 return asZonedDateTime().getNano();
227 }
228
229
230
231
232
233
234 @NonNull
235 default LocalDateTime asLocalDateTime() {
236 return ObjectUtils.notNull(asZonedDateTime().toLocalDateTime());
237 }
238
239
240
241
242
243
244 @NonNull
245 default LocalDate asLocalDate() {
246 return ObjectUtils.notNull(asZonedDateTime().toLocalDate());
247 }
248
249
250
251
252
253
254 @NonNull
255 default LocalTime asLocalTime() {
256 return ObjectUtils.notNull(asZonedDateTime().toLocalTime());
257 }
258
259
260
261
262
263
264 @NonNull
265 default OffsetTime asOffsetTime() {
266 return ObjectUtils.notNull(asZonedDateTime().toOffsetDateTime().toOffsetTime());
267 }
268
269
270
271
272
273
274 @NonNull
275 default IDateItem asDate() {
276 return IDateItem.valueOf(asZonedDateTime(), hasTimezone());
277 }
278
279
280
281
282
283
284 @NonNull
285 default ITimeItem asTime() {
286 return ITimeItem.valueOf(asOffsetTime(), hasTimezone());
287 }
288
289
290
291
292
293
294
295
296
297
298
299 @NonNull
300 default IDateTimeItem normalize(@NonNull DynamicContext dynamicContext) {
301 IDateTimeItem retval = hasTimezone()
302 ? this
303 : replaceTimezone(dynamicContext.getImplicitTimeZoneAsDayTimeDuration());
304 return valueOf(
305 ObjectUtils.notNull(retval.asZonedDateTime().withZoneSameInstant(ZoneOffset.UTC)),
306 true);
307 }
308
309
310
311
312
313
314 default IDateTimeItem asDateTimeZ() {
315 return ZoneOffset.UTC.equals(getZoneOffset())
316 ? this
317 : valueOf(ObjectUtils.notNull(asZonedDateTime().withZoneSameLocal(ZoneOffset.UTC)), hasTimezone());
318 }
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349 @Override
350 default IDateTimeItem replaceTimezone(@Nullable IDayTimeDurationItem offset) {
351 return offset == null
352 ? hasTimezone()
353 ? valueOf(ObjectUtils.notNull(asZonedDateTime().withZoneSameLocal(ZoneOffset.UTC)), false)
354 : this
355 : hasTimezone()
356 ? valueOf(
357 ObjectUtils.notNull(asZonedDateTime().withZoneSameInstant(offset.asZoneOffset())),
358 true)
359 : valueOf(
360 ObjectUtils.notNull(asZonedDateTime().withZoneSameLocal(offset.asZoneOffset())),
361 true);
362 }
363
364
365
366
367
368
369
370
371
372
373
374 @NonNull
375 static IDateTimeItem cast(@NonNull IAnyAtomicItem item) {
376 IDateTimeItem retval;
377 if (item instanceof IDateTimeItem) {
378 retval = (IDateTimeItem) item;
379 } else if (item instanceof IDateItem) {
380 IDateItem date = (IDateItem) item;
381 retval = valueOf(date.asZonedDateTime(), date.hasTimezone());
382 } else if (item instanceof IStringItem || item instanceof IUntypedAtomicItem) {
383 try {
384 retval = valueOf(item.asString());
385 } catch (IllegalStateException | InvalidTypeMetapathException ex) {
386
387 throw new InvalidValueForCastFunctionException(ex);
388 }
389 } else {
390 throw new InvalidValueForCastFunctionException(
391 String.format("unsupported item type '%s'", item.getClass().getName()));
392 }
393 return retval;
394 }
395
396 @Override
397 default IDateTimeItem castAsType(IAnyAtomicItem item) {
398 return cast(item);
399 }
400 }