1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.datamodel.common;
20
21 import java.text.DateFormat;
22 import java.text.MessageFormat;
23 import java.text.ParseException;
24 import java.time.DateTimeException;
25 import java.time.Instant;
26 import java.time.LocalDate;
27 import java.time.LocalDateTime;
28 import java.time.Year;
29 import java.time.YearMonth;
30 import java.time.ZoneId;
31 import java.time.ZonedDateTime;
32 import java.time.format.DateTimeFormatter;
33 import java.time.temporal.TemporalAccessor;
34 import java.util.Date;
35 import java.util.Locale;
36 import java.util.Objects;
37 import java.util.Optional;
38 import java.util.StringTokenizer;
39 import java.util.TimeZone;
40
41 import org.apache.logging.log4j.LogManager;
42 import org.apache.logging.log4j.Logger;
43 import org.mycore.common.config.MCRConfiguration2;
44
45
46
47
48
49
50
51 public class MCRISO8601Date {
52
53 public static final String PROPERTY_STRICT_PARSING = "MCR.Metadata.SimpleDateFormat.StrictParsing";
54
55 private static final Logger LOGGER = LogManager.getLogger(MCRISO8601Date.class);
56
57 private DateTimeFormatter dateTimeFormatter = MCRISO8601FormatChooser.getFormatter(null, null);
58
59 private TemporalAccessor dt;
60
61 private MCRISO8601Format isoFormat;
62
63
64
65
66 public MCRISO8601Date() {
67
68 }
69
70
71
72
73
74
75
76 public MCRISO8601Date(final String isoString) {
77 this();
78 setDate(isoString);
79 }
80
81 private static Locale getLocale(final String locale) {
82 String lang = "", country = "";
83 final int pos = locale.indexOf("_");
84 if (pos > 0) {
85 lang = locale.substring(0, pos);
86 country = locale.substring(pos + 1);
87 } else {
88 lang = locale;
89 }
90 return new Locale(lang, country);
91 }
92
93 public static MCRISO8601Date now() {
94 MCRISO8601Date instance = new MCRISO8601Date();
95 instance.setInstant(Instant.now());
96 return instance;
97 }
98
99
100
101
102
103
104
105
106
107
108 public String format(final String format, final Locale locale) {
109 return format(format, locale, null);
110 }
111
112
113
114
115
116
117
118
119
120
121
122
123 public String format(final String format, final Locale locale, String timeZone) {
124 DateTimeFormatter df = DateTimeFormatter.ofPattern(format,
125 Optional.ofNullable(locale)
126 .orElseGet(Locale::getDefault));
127 ZoneId zone = null;
128 if (timeZone != null) {
129 try {
130 zone = ZoneId.of(timeZone);
131 } catch (DateTimeException e) {
132 LOGGER.warn(e.getMessage());
133 }
134 }
135 if (zone == null) {
136 zone = ZoneId.systemDefault();
137 }
138 df = df.withZone(zone);
139 if (LOGGER.isDebugEnabled()) {
140 Object[] parameter = { dt, zone, dt != null ? df.format(dt) : null };
141 String msg = new MessageFormat("DateTime ''{0}'', using time zone ''{1}'', formatted: {2}", Locale.ROOT)
142 .format(parameter);
143 LOGGER.debug(msg);
144 }
145 String formatted = null;
146 try {
147 formatted = dt == null ? null : !format.contains("G") ? df.format(dt) : df.format(dt).replace("-", "");
148 } catch (Exception e) {
149 LOGGER.error("Could not format date", e);
150 }
151 return formatted;
152 }
153
154
155
156
157
158
159 public final Date getDate() {
160 return dt == null ? null : Date.from(Instant.from(dt));
161 }
162
163
164
165
166 public TemporalAccessor getDt() {
167 return dt;
168 }
169
170
171
172
173 public MCRISO8601Format getIsoFormat() {
174 return isoFormat;
175 }
176
177
178
179
180
181
182 public final String getISOString() {
183 return dt == null ? null : dateTimeFormatter.format(dt);
184 }
185
186
187
188
189
190
191
192 public void setDate(final Date dt) {
193 if (dt == null) {
194 this.dt = null;
195 } else {
196 this.dt = dt.toInstant();
197 }
198 }
199
200
201
202
203
204
205
206 public final void setDate(final String isoString) {
207 TemporalAccessor dt = null;
208 try {
209 dt = getDateTime(MCRISO8601FormatChooser.cropSecondFractions(isoString));
210 } catch (final RuntimeException e) {
211 final boolean strictParsingEnabled = true;
212 if (!strictParsingEnabled) {
213
214
215
216 LOGGER.warn("Strict date parsing is disabled. This may result in incorrect dates.");
217 dt = guessDateTime(isoString);
218 } else {
219 LOGGER.debug("Error while parsing date, set date to NULL.", e);
220 dt = null;
221 }
222 }
223 setInstant(dt);
224 }
225
226
227
228
229
230
231 public void setFormat(final MCRISO8601Format isoFormat) {
232 this.isoFormat = isoFormat;
233 dateTimeFormatter = MCRISO8601FormatChooser.getFormatter(null, this.isoFormat);
234 }
235
236
237
238
239
240
241
242
243
244 public void setFormat(String format) {
245 setFormat(MCRISO8601Format.getFormat(format));
246 }
247
248 private TemporalAccessor getDateTime(final String timeString) {
249 dateTimeFormatter = MCRISO8601FormatChooser.getFormatter(timeString, isoFormat);
250 return dateTimeFormatter.parseBest(timeString, ZonedDateTime::from, LocalDateTime::from, LocalDate::from,
251 YearMonth::from,
252 Year::from);
253 }
254
255 private TemporalAccessor guessDateTime(final String date) {
256 final String locales = MCRConfiguration2.getString("MCR.Metadata.SimpleDateFormat.Locales").orElse("de_DE");
257 final StringTokenizer tok = new StringTokenizer(locales, ",");
258 while (tok.hasMoreTokens()) {
259 final Locale locale = getLocale(tok.nextToken());
260 final DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
261 df.setTimeZone(TimeZone.getTimeZone("UTC"));
262 df.setLenient(true);
263 TemporalAccessor result = null;
264 try {
265 final Date pDate = df.parse(date);
266 result = pDate.toInstant();
267 return result;
268 } catch (final ParseException e) {
269 LOGGER.warn("Date guess failed for locale: {}", locale);
270
271 }
272 }
273 LOGGER.error("Error trying to guess date for string: {}", date);
274 return null;
275 }
276
277 private void setInstant(final TemporalAccessor dt) {
278 this.dt = dt;
279 }
280
281 @Override
282 public boolean equals(Object obj) {
283 if (obj == null) {
284 return false;
285 }
286 if (getClass() != obj.getClass()) {
287 return false;
288 }
289 final MCRISO8601Date other = (MCRISO8601Date) obj;
290 return Objects.equals(this.dt, other.dt);
291 }
292
293 }