1 | |
|
2 | |
|
3 | |
|
4 | |
|
5 | |
|
6 | |
|
7 | |
|
8 | |
|
9 | |
|
10 | |
|
11 | |
package org.openpermis.basic; |
12 | |
|
13 | |
import java.util.Arrays; |
14 | |
import java.util.Date; |
15 | |
import java.util.regex.Matcher; |
16 | |
import java.util.regex.Pattern; |
17 | |
|
18 | |
import org.joda.time.DateTime; |
19 | |
import org.joda.time.DateTimeZone; |
20 | |
|
21 | |
import org.openpermis.policy.TimeStamp; |
22 | |
import org.openpermis.policy.io.xml.TimeUtility; |
23 | |
|
24 | |
|
25 | |
|
26 | |
|
27 | |
|
28 | |
|
29 | |
|
30 | |
|
31 | |
|
32 | |
|
33 | |
|
34 | |
|
35 | |
|
36 | |
|
37 | |
|
38 | |
|
39 | |
|
40 | |
|
41 | |
|
42 | |
|
43 | |
|
44 | |
|
45 | |
public final class PartialTime { |
46 | |
|
47 | |
|
48 | |
|
49 | |
private int year; |
50 | |
|
51 | |
private boolean isYearAsterix; |
52 | |
|
53 | |
private int monthOfYear; |
54 | |
|
55 | |
private int dayOfMonth; |
56 | |
|
57 | |
private int hourOfDay; |
58 | |
|
59 | |
private int minuteOfHour; |
60 | |
|
61 | |
private int secondOfMinute; |
62 | |
|
63 | 228 | private int[] zone = new int[2]; |
64 | |
|
65 | |
boolean hasZone; |
66 | |
|
67 | |
private DateTimeZone defaultZone; |
68 | |
|
69 | |
|
70 | |
|
71 | |
|
72 | |
|
73 | |
|
74 | |
|
75 | |
|
76 | 77 | public PartialTime (Date date) { |
77 | 77 | final DateTime dateTime = new DateTime(date, DateTimeZone.UTC); |
78 | |
|
79 | 77 | this.isYearAsterix = false; |
80 | 77 | this.year = dateTime.getYear(); |
81 | 77 | this.monthOfYear = dateTime.getMonthOfYear(); |
82 | 77 | this.dayOfMonth = dateTime.getDayOfMonth(); |
83 | 77 | this.hourOfDay = dateTime.getHourOfDay(); |
84 | 77 | this.minuteOfHour = dateTime.getMinuteOfHour(); |
85 | 77 | this.secondOfMinute = dateTime.getSecondOfMinute(); |
86 | |
|
87 | 77 | this.hasZone = true; |
88 | 77 | this.zone[0] = 0; |
89 | 77 | this.zone[1] = 0; |
90 | 77 | } |
91 | |
|
92 | |
|
93 | |
|
94 | |
|
95 | |
|
96 | |
|
97 | |
|
98 | |
|
99 | |
public PartialTime (String string, DateTimeZone defaultZone, boolean allowWildcards) |
100 | |
throws NumberFormatException |
101 | 151 | { |
102 | 151 | this.defaultZone = defaultZone; |
103 | 151 | this.hasZone = false; |
104 | 151 | parse(string); |
105 | 151 | if (!allowWildcards && !isComplete()) { |
106 | 0 | throw new NumberFormatException("No wildcards allowd."); |
107 | |
} |
108 | 151 | checkTimeAndOffset(); |
109 | 150 | } |
110 | |
|
111 | |
|
112 | |
|
113 | |
|
114 | |
|
115 | |
|
116 | |
|
117 | |
|
118 | |
|
119 | |
|
120 | |
|
121 | |
|
122 | |
|
123 | |
|
124 | |
public PartialTime ( |
125 | |
boolean isYearAsterix, |
126 | |
int year, |
127 | |
int monthOfYear, |
128 | |
int dayOfMonth, |
129 | |
int hourOfDay, |
130 | |
int minuteOfHour, |
131 | |
int secondOfMinute, |
132 | |
boolean hasZone, |
133 | |
int offsetHours, |
134 | |
int offsetMinutes, |
135 | |
DateTimeZone defaultZone, |
136 | |
boolean allowWildcards |
137 | 0 | ) { |
138 | 0 | this.isYearAsterix = isYearAsterix; |
139 | 0 | this.year = year; |
140 | 0 | this.monthOfYear = monthOfYear; |
141 | 0 | this.dayOfMonth = dayOfMonth; |
142 | 0 | this.hourOfDay = hourOfDay; |
143 | 0 | this.minuteOfHour = minuteOfHour; |
144 | 0 | this.secondOfMinute = secondOfMinute; |
145 | 0 | this.hasZone = hasZone; |
146 | 0 | this.zone[0] = offsetHours; |
147 | 0 | this.zone[1] = offsetMinutes; |
148 | 0 | this.defaultZone = defaultZone; |
149 | 0 | if (!allowWildcards && !isComplete()) { |
150 | 0 | throw new NumberFormatException("No wildcards allowd."); |
151 | |
} |
152 | 0 | checkTimeAndOffset(); |
153 | 0 | } |
154 | |
|
155 | |
|
156 | |
|
157 | |
|
158 | |
|
159 | |
|
160 | |
|
161 | |
|
162 | |
public boolean isComplete () { |
163 | 504 | if (this.isYearAsterix || |
164 | |
this.monthOfYear == -1 || |
165 | |
this.dayOfMonth == -1 || |
166 | |
this.hourOfDay == -1 || |
167 | |
this.minuteOfHour == -1 || |
168 | |
this.secondOfMinute == -1 |
169 | |
) { |
170 | 4 | return false; |
171 | |
} |
172 | 500 | return true; |
173 | |
} |
174 | |
|
175 | |
|
176 | |
|
177 | |
|
178 | |
|
179 | |
|
180 | |
private boolean inLocalZone () { |
181 | 159 | return !this.hasZone && this.zone == null; |
182 | |
} |
183 | |
|
184 | |
|
185 | |
|
186 | |
|
187 | |
|
188 | |
|
189 | |
public boolean inSameTimeZone (PartialTime other) { |
190 | 53 | if (inLocalZone() && other.inLocalZone()) { |
191 | 0 | return true; |
192 | 53 | } else if (inLocalZone() || other.inLocalZone()) { |
193 | 0 | return false; |
194 | |
} |
195 | |
|
196 | |
final DateTimeZone t; |
197 | |
final DateTimeZone o; |
198 | 53 | if (this.hasZone) { |
199 | 53 | t = DateTimeZone.forOffsetHoursMinutes(this.zone[0], this.zone[1]); |
200 | |
} else { |
201 | 0 | t = this.defaultZone; |
202 | |
} |
203 | 53 | if (other.hasZone) { |
204 | 53 | o = DateTimeZone.forOffsetHoursMinutes(other.zone[0], other.zone[1]); |
205 | |
} else { |
206 | 0 | o = other.defaultZone; |
207 | |
} |
208 | 53 | return t.equals(o); |
209 | |
} |
210 | |
|
211 | |
|
212 | |
|
213 | |
|
214 | |
|
215 | |
|
216 | |
|
217 | |
public DateTime toDateTime (TimeStamp timeStamp) { |
218 | 183 | if (!isComplete()) { |
219 | 0 | throw new IllegalStateException("Not complete."); |
220 | |
} |
221 | 183 | return new DateTime( |
222 | |
this.year, |
223 | |
this.monthOfYear, |
224 | |
this.dayOfMonth, |
225 | |
this.hourOfDay, |
226 | |
this.minuteOfHour, |
227 | |
this.secondOfMinute, |
228 | |
0, |
229 | |
toDateTimeZone(timeStamp) |
230 | |
); |
231 | |
} |
232 | |
|
233 | |
|
234 | |
|
235 | |
|
236 | |
|
237 | |
|
238 | |
|
239 | |
|
240 | |
public boolean isComparableWith (PartialTime other) { |
241 | 63 | return isComplete() || other.isComplete(); |
242 | |
} |
243 | |
|
244 | |
|
245 | |
|
246 | |
|
247 | |
|
248 | |
|
249 | |
|
250 | |
public boolean isAfter (PartialTime other, TimeStamp timeStamp) { |
251 | 5 | return compareTo(other, timeStamp) > 0; |
252 | |
} |
253 | |
|
254 | |
|
255 | |
|
256 | |
|
257 | |
|
258 | |
|
259 | |
|
260 | |
public boolean isBefore (PartialTime other, TimeStamp timeStamp) { |
261 | 55 | return compareTo(other, timeStamp) < 0; |
262 | |
} |
263 | |
|
264 | |
|
265 | |
|
266 | |
|
267 | |
|
268 | |
|
269 | |
|
270 | |
public boolean isEqual (PartialTime other, TimeStamp timeStamp) { |
271 | 2 | return compareTo(other, timeStamp) == 0; |
272 | |
} |
273 | |
|
274 | |
|
275 | |
|
276 | |
|
277 | |
private int compareTo (PartialTime other, TimeStamp timeStamp) { |
278 | |
|
279 | 62 | if (!isComparableWith(other)) { |
280 | 1 | throw new IllegalStateException("Not comparable."); |
281 | |
} |
282 | |
|
283 | |
|
284 | 61 | if (isComplete() && other.isComplete()) { |
285 | 61 | return toDateTime(timeStamp).compareTo(other.toDateTime(timeStamp)); |
286 | |
} |
287 | |
|
288 | |
|
289 | |
DateTime complete; |
290 | |
PartialTime incomplete; |
291 | |
int sign; |
292 | 0 | if (isComplete()) { |
293 | 0 | complete = toDateTime(timeStamp).toDateTime(other.toDateTimeZone(timeStamp)); |
294 | 0 | incomplete = other; |
295 | 0 | sign = 1; |
296 | |
} else { |
297 | 0 | complete = other.toDateTime(timeStamp).toDateTime(toDateTimeZone(timeStamp)); |
298 | 0 | incomplete = this; |
299 | 0 | sign = -1; |
300 | |
} |
301 | |
|
302 | 0 | if (!incomplete.isYearAsterix) { |
303 | 0 | return sign * (incomplete.year - complete.getYear()); |
304 | |
} |
305 | 0 | if (incomplete.monthOfYear != -1) { |
306 | 0 | return sign * (incomplete.monthOfYear - complete.getMonthOfYear()); |
307 | |
} |
308 | 0 | if (incomplete.dayOfMonth != -1) { |
309 | 0 | return sign * (incomplete.dayOfMonth - complete.getDayOfMonth()); |
310 | |
} |
311 | 0 | if (incomplete.hourOfDay != -1) { |
312 | 0 | return sign * (incomplete.hourOfDay - complete.getHourOfDay()); |
313 | |
} |
314 | 0 | if (incomplete.minuteOfHour != -1) { |
315 | 0 | return sign * (incomplete.minuteOfHour - complete.getMinuteOfHour()); |
316 | |
} |
317 | 0 | if (incomplete.secondOfMinute != -1) { |
318 | 0 | return sign * (incomplete.secondOfMinute - complete.getSecondOfMinute()); |
319 | |
} |
320 | |
|
321 | 0 | return 0; |
322 | |
} |
323 | |
|
324 | |
|
325 | |
|
326 | |
|
327 | |
private void parse (String string) { |
328 | |
|
329 | 151 | final String four = "(\\d\\d\\d\\d|\\*)"; |
330 | 151 | final String two = "(\\d\\d|\\*)"; |
331 | 151 | final String time = four + "-" + two + "-" + two + "T" + two + ":" + two + ":" + two; |
332 | 151 | final String timeZone = "(Z)|([+-])" + "(\\d\\d)" + ":" + "(\\d\\d)"; |
333 | 151 | final String timeAndZone = time + "(" + timeZone + ")?"; |
334 | |
|
335 | 151 | final int gYear = 1; |
336 | 151 | final int gMonthOfYear = 2; |
337 | 151 | final int gDayOfMonth = 3; |
338 | 151 | final int gHourOfDay = 4; |
339 | 151 | final int gMinuteOfHour = 5; |
340 | 151 | final int gSecondOfMinute = 6; |
341 | 151 | final int gZoneComplete = 7; |
342 | 151 | final int gZoneZ = 8; |
343 | 151 | final int gZoneSign = 9; |
344 | 151 | final int gZoneHours = 10; |
345 | 151 | final int gZoneMinuteOfHour = 11; |
346 | |
|
347 | 151 | final Pattern pattern = Pattern.compile(timeAndZone); |
348 | 151 | final Matcher matcher = pattern.matcher(string); |
349 | |
|
350 | 151 | if (matcher.matches()) { |
351 | 151 | if (matcher.group(gZoneComplete) != null) { |
352 | 40 | this.hasZone = true; |
353 | 40 | this.zone = parseZone( |
354 | |
matcher.group(gZoneZ), |
355 | |
matcher.group(gZoneSign), |
356 | |
matcher.group(gZoneHours), |
357 | |
matcher.group(gZoneMinuteOfHour) |
358 | |
); |
359 | |
} |
360 | |
|
361 | 151 | parseDateAndTime( |
362 | |
matcher.group(gYear), |
363 | |
matcher.group(gMonthOfYear), |
364 | |
matcher.group(gDayOfMonth), |
365 | |
matcher.group(gHourOfDay), |
366 | |
matcher.group(gMinuteOfHour), |
367 | |
matcher.group(gSecondOfMinute) |
368 | |
); |
369 | |
|
370 | |
} else { |
371 | 0 | throw new NumberFormatException(); |
372 | |
} |
373 | 151 | } |
374 | |
|
375 | |
|
376 | |
|
377 | |
|
378 | |
private void parseDateAndTime (String y, String mo, String d, String h, String mi, String s) { |
379 | 151 | if ("*".equals(y)) { |
380 | 0 | this.isYearAsterix = true; |
381 | |
} else { |
382 | 151 | this.isYearAsterix = false; |
383 | |
} |
384 | 151 | this.year = "*".equals(y) ? -1 : Integer.parseInt(y); |
385 | 151 | this.monthOfYear = "*".equals(mo) ? -1 : Integer.parseInt(mo); |
386 | 151 | this.dayOfMonth = "*".equals(d) ? -1 : Integer.parseInt(d); |
387 | 151 | this.hourOfDay = "*".equals(h) ? -1 : Integer.parseInt(h); |
388 | 151 | this.minuteOfHour = "*".equals(mi) ? -1 : Integer.parseInt(mi); |
389 | 151 | this.secondOfMinute = "*".equals(s) ? -1 : Integer.parseInt(s); |
390 | 151 | } |
391 | |
|
392 | |
|
393 | |
|
394 | |
|
395 | |
private void checkTimeAndOffset () { |
396 | 151 | final int int12 = 12; |
397 | 151 | final int int23 = 23; |
398 | 151 | final int int31 = 31; |
399 | 151 | final int int59 = 59; |
400 | |
|
401 | 151 | if (!this.isYearAsterix && this.year < 0) { |
402 | 0 | throw new NumberFormatException("Year must be positive."); |
403 | |
} |
404 | |
|
405 | 151 | if (this.monthOfYear != -1 && (this.monthOfYear < 1 || this.monthOfYear > int12)) { |
406 | 1 | throw new NumberFormatException("Month of year must be in range [1,12]."); |
407 | |
} |
408 | |
|
409 | 150 | if (this.dayOfMonth != -1 && (this.dayOfMonth < 1 || this.dayOfMonth > int31)) { |
410 | 0 | throw new NumberFormatException("Day of month must be in range [1,31]."); |
411 | |
} |
412 | |
|
413 | 150 | if (this.hourOfDay != -1 && (this.hourOfDay < 0 || this.hourOfDay > int23)) { |
414 | 0 | throw new NumberFormatException("Hour of day must be in range [0,23]."); |
415 | |
} |
416 | |
|
417 | 150 | if (this.minuteOfHour != -1 && (this.minuteOfHour < 0 || this.minuteOfHour > int59)) { |
418 | 0 | throw new NumberFormatException("Minute of hour must be in range [0,59]."); |
419 | |
} |
420 | |
|
421 | 150 | if (this.secondOfMinute != -1 && (this.secondOfMinute < 0 || this.secondOfMinute > int59)) { |
422 | 0 | throw new NumberFormatException("Second of minute must be in range [0,59]."); |
423 | |
} |
424 | |
|
425 | 150 | if (this.hasZone && (this.zone[1] < 0 || this.zone[1] > int59)) { |
426 | 0 | throw new NumberFormatException("Minute of hour in time zone must be in range [0,59]."); |
427 | |
} |
428 | 150 | } |
429 | |
|
430 | |
|
431 | |
|
432 | |
|
433 | |
private static int[] parseZone (String utc, String sign, String hours, String minutes) { |
434 | 40 | if ("Z".equals(utc)) { |
435 | 38 | return new int[]{0, 0}; |
436 | |
} |
437 | 2 | int h = Integer.parseInt(hours); |
438 | 2 | int m = Integer.parseInt(minutes); |
439 | 2 | if ("-".equals(sign)) { |
440 | 1 | h = -h; |
441 | |
} |
442 | 2 | return new int[]{h, m}; |
443 | |
} |
444 | |
|
445 | |
|
446 | |
|
447 | |
|
448 | |
|
449 | |
|
450 | |
public boolean equals (Object object) { |
451 | 81 | if (object == null) { |
452 | 0 | return false; |
453 | |
} |
454 | 81 | if (this == object) { |
455 | 0 | return true; |
456 | |
} |
457 | 81 | if (object instanceof PartialTime) { |
458 | 81 | final PartialTime other = (PartialTime) object; |
459 | 81 | return |
460 | |
this.isYearAsterix == other.isYearAsterix && |
461 | |
(this.isYearAsterix ? true : this.year == other.year) && |
462 | |
this.monthOfYear == other.monthOfYear && |
463 | |
this.dayOfMonth == other.dayOfMonth && |
464 | |
this.hourOfDay == other.hourOfDay && |
465 | |
this.minuteOfHour == other.minuteOfHour && |
466 | |
this.secondOfMinute == other.secondOfMinute && |
467 | |
Arrays.equals(this.zone, other.zone) && |
468 | |
this.hasZone == other.hasZone && |
469 | |
(this.defaultZone != null ? this.defaultZone.equals(other.defaultZone) : true) && |
470 | |
(other.defaultZone != null ? other.defaultZone.equals(this.defaultZone) : true); |
471 | |
} |
472 | 0 | return false; |
473 | |
} |
474 | |
|
475 | |
|
476 | |
|
477 | |
|
478 | |
public int hashCode () { |
479 | 0 | return |
480 | |
(this.isYearAsterix ? 1 : Integer.valueOf(this.year).hashCode()) * |
481 | |
Integer.valueOf(this.monthOfYear).hashCode() * |
482 | |
Integer.valueOf(this.dayOfMonth).hashCode() * |
483 | |
Integer.valueOf(this.hourOfDay).hashCode() * |
484 | |
Integer.valueOf(this.minuteOfHour).hashCode() * |
485 | |
Integer.valueOf(this.secondOfMinute).hashCode() * |
486 | |
this.zone.hashCode() * |
487 | |
(this.defaultZone == null ? 1 : this.defaultZone.hashCode()); |
488 | |
} |
489 | |
|
490 | |
|
491 | |
|
492 | |
|
493 | |
|
494 | |
public String toString () { |
495 | 14 | String t = |
496 | |
writePart(this.year) + |
497 | |
"-" + |
498 | |
writePart(this.monthOfYear) + |
499 | |
"-" + |
500 | |
writePart(this.dayOfMonth) + |
501 | |
"T" + |
502 | |
writePart(this.hourOfDay) + |
503 | |
":" + |
504 | |
writePart(this.minuteOfHour) + |
505 | |
":" + |
506 | |
writePart(this.secondOfMinute); |
507 | |
|
508 | 14 | String z = ""; |
509 | 14 | if (this.hasZone) { |
510 | 4 | int hours = this.zone[0]; |
511 | 4 | int minutes = this.zone[1]; |
512 | 4 | if (hours == 0 && minutes == 0) { |
513 | 3 | z = "Z"; |
514 | 1 | } else if (hours < 0) { |
515 | 1 | z = "-" + writePart(-hours) + ":" + writePart(minutes); |
516 | |
} else { |
517 | 0 | z = "+" + writePart(hours) + ":" + writePart(minutes); |
518 | |
} |
519 | |
} |
520 | 14 | return t + z; |
521 | |
} |
522 | |
|
523 | |
|
524 | |
|
525 | |
|
526 | |
private String writePart (int i) { |
527 | 86 | final int addLeadingZeroIfSmallerThan = 10; |
528 | 86 | if (i < 0) { |
529 | 6 | return "*"; |
530 | 80 | } else if (i < addLeadingZeroIfSmallerThan) { |
531 | 34 | return "0" + i; |
532 | |
} else { |
533 | 46 | return "" + i; |
534 | |
} |
535 | |
} |
536 | |
|
537 | |
|
538 | |
|
539 | |
|
540 | |
private DateTimeZone toDateTimeZone (TimeStamp timeStamp) { |
541 | 183 | if (this.hasZone) { |
542 | 164 | return DateTimeZone.forOffsetHoursMinutes(this.zone[0], this.zone[1]); |
543 | |
} |
544 | 19 | if (this.defaultZone != null) { |
545 | 19 | return this.defaultZone; |
546 | |
} |
547 | 0 | return TimeUtility.getDateTimeZone(timeStamp); |
548 | |
} |
549 | |
|
550 | |
} |