1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | * Iso8601: |
6 | * Initial Developer: Robert Rathsack (firstName dot lastName at gmx dot de) |
7 | */ |
8 | package org.h2.util; |
9 | |
10 | import java.sql.Date; |
11 | import java.sql.Time; |
12 | import java.sql.Timestamp; |
13 | import java.text.SimpleDateFormat; |
14 | import java.util.Calendar; |
15 | import java.util.GregorianCalendar; |
16 | import java.util.Locale; |
17 | import java.util.TimeZone; |
18 | |
19 | import org.h2.api.ErrorCode; |
20 | import org.h2.message.DbException; |
21 | import org.h2.value.Value; |
22 | import org.h2.value.ValueDate; |
23 | import org.h2.value.ValueNull; |
24 | import org.h2.value.ValueTime; |
25 | import org.h2.value.ValueTimestamp; |
26 | |
27 | /** |
28 | * This utility class contains time conversion functions. |
29 | * <p> |
30 | * Date value: a bit field with bits for the year, month, and day. |
31 | * Absolute day: the day number (0 means 1970-01-01). |
32 | */ |
33 | public class DateTimeUtils { |
34 | |
35 | /** |
36 | * The number of milliseconds per day. |
37 | */ |
38 | public static final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000L; |
39 | |
40 | private static final long NANOS_PER_DAY = MILLIS_PER_DAY * 1000000; |
41 | |
42 | private static final int SHIFT_YEAR = 9; |
43 | private static final int SHIFT_MONTH = 5; |
44 | |
45 | private static final int[] NORMAL_DAYS_PER_MONTH = { 0, 31, 28, 31, 30, 31, |
46 | 30, 31, 31, 30, 31, 30, 31 }; |
47 | |
48 | /** |
49 | * Offsets of month within a year, starting with March, April,... |
50 | */ |
51 | private static final int[] DAYS_OFFSET = { 0, 31, 61, 92, 122, 153, 184, |
52 | 214, 245, 275, 306, 337, 366 }; |
53 | |
54 | private static final ThreadLocal<Calendar> CACHED_CALENDAR = |
55 | new ThreadLocal<Calendar>() { |
56 | @Override |
57 | protected Calendar initialValue() { |
58 | return Calendar.getInstance(); |
59 | } |
60 | }; |
61 | |
62 | private DateTimeUtils() { |
63 | // utility class |
64 | } |
65 | |
66 | /** |
67 | * Reset the calendar, for example after changing the default timezone. |
68 | */ |
69 | public static void resetCalendar() { |
70 | CACHED_CALENDAR.remove(); |
71 | } |
72 | |
73 | /** |
74 | * Convert the date to the specified time zone. |
75 | * |
76 | * @param value the date (might be ValueNull) |
77 | * @param calendar the calendar |
78 | * @return the date using the correct time zone |
79 | */ |
80 | public static Date convertDate(Value value, Calendar calendar) { |
81 | if (value == ValueNull.INSTANCE) { |
82 | return null; |
83 | } |
84 | ValueDate d = (ValueDate) value.convertTo(Value.DATE); |
85 | Calendar cal = (Calendar) calendar.clone(); |
86 | cal.clear(); |
87 | cal.setLenient(true); |
88 | long dateValue = d.getDateValue(); |
89 | setCalendarFields(cal, yearFromDateValue(dateValue), |
90 | monthFromDateValue(dateValue), |
91 | dayFromDateValue(dateValue), |
92 | 0, 0, 0, 0); |
93 | long ms = cal.getTimeInMillis(); |
94 | return new Date(ms); |
95 | } |
96 | |
97 | /** |
98 | * Convert the time to the specified time zone. |
99 | * |
100 | * @param value the time (might be ValueNull) |
101 | * @param calendar the calendar |
102 | * @return the time using the correct time zone |
103 | */ |
104 | public static Time convertTime(Value value, Calendar calendar) { |
105 | if (value == ValueNull.INSTANCE) { |
106 | return null; |
107 | } |
108 | ValueTime t = (ValueTime) value.convertTo(Value.TIME); |
109 | Calendar cal = (Calendar) calendar.clone(); |
110 | cal.clear(); |
111 | cal.setLenient(true); |
112 | long nanos = t.getNanos(); |
113 | long millis = nanos / 1000000; |
114 | nanos -= millis * 1000000; |
115 | long s = millis / 1000; |
116 | millis -= s * 1000; |
117 | long m = s / 60; |
118 | s -= m * 60; |
119 | long h = m / 60; |
120 | m -= h * 60; |
121 | setCalendarFields(cal, 1970, 1, 1, |
122 | (int) h, (int) m, (int) s, (int) millis); |
123 | long ms = cal.getTimeInMillis(); |
124 | return new Time(ms); |
125 | } |
126 | |
127 | /** |
128 | * Convert the timestamp to the specified time zone. |
129 | * |
130 | * @param value the timestamp (might be ValueNull) |
131 | * @param calendar the calendar |
132 | * @return the timestamp using the correct time zone |
133 | */ |
134 | public static Timestamp convertTimestamp(Value value, Calendar calendar) { |
135 | if (value == ValueNull.INSTANCE) { |
136 | return null; |
137 | } |
138 | ValueTimestamp ts = (ValueTimestamp) value.convertTo(Value.TIMESTAMP); |
139 | Calendar cal = (Calendar) calendar.clone(); |
140 | cal.clear(); |
141 | cal.setLenient(true); |
142 | long dateValue = ts.getDateValue(); |
143 | long nanos = ts.getTimeNanos(); |
144 | long millis = nanos / 1000000; |
145 | nanos -= millis * 1000000; |
146 | long s = millis / 1000; |
147 | millis -= s * 1000; |
148 | long m = s / 60; |
149 | s -= m * 60; |
150 | long h = m / 60; |
151 | m -= h * 60; |
152 | setCalendarFields(cal, yearFromDateValue(dateValue), |
153 | monthFromDateValue(dateValue), |
154 | dayFromDateValue(dateValue), |
155 | (int) h, (int) m, (int) s, (int) millis); |
156 | long ms = cal.getTimeInMillis(); |
157 | Timestamp x = new Timestamp(ms); |
158 | x.setNanos((int) (nanos + millis * 1000000)); |
159 | return x; |
160 | } |
161 | |
162 | /** |
163 | * Convert the date using the specified calendar. |
164 | * |
165 | * @param x the date |
166 | * @param calendar the calendar |
167 | * @return the date |
168 | */ |
169 | public static ValueDate convertDate(Date x, Calendar calendar) { |
170 | if (calendar == null) { |
171 | throw DbException.getInvalidValueException("calendar", null); |
172 | } |
173 | Calendar cal = (Calendar) calendar.clone(); |
174 | cal.setTimeInMillis(x.getTime()); |
175 | long dateValue = dateValueFromCalendar(cal); |
176 | return ValueDate.fromDateValue(dateValue); |
177 | } |
178 | |
179 | /** |
180 | * Convert the time using the specified calendar. |
181 | * |
182 | * @param x the time |
183 | * @param calendar the calendar |
184 | * @return the time |
185 | */ |
186 | public static ValueTime convertTime(Time x, Calendar calendar) { |
187 | if (calendar == null) { |
188 | throw DbException.getInvalidValueException("calendar", null); |
189 | } |
190 | Calendar cal = (Calendar) calendar.clone(); |
191 | cal.setTimeInMillis(x.getTime()); |
192 | long nanos = nanosFromCalendar(cal); |
193 | return ValueTime.fromNanos(nanos); |
194 | } |
195 | |
196 | /** |
197 | * Convert a date to the specified time zone. |
198 | * |
199 | * @param x the date to convert |
200 | * @param target the calendar with the target timezone |
201 | * @return the milliseconds in UTC |
202 | */ |
203 | public static long convertToLocal(java.util.Date x, Calendar target) { |
204 | if (target == null) { |
205 | throw DbException.getInvalidValueException("calendar", null); |
206 | } |
207 | target = (Calendar) target.clone(); |
208 | Calendar local = Calendar.getInstance(); |
209 | synchronized (local) { |
210 | local.setTime(x); |
211 | convertTime(local, target); |
212 | } |
213 | return target.getTime().getTime(); |
214 | } |
215 | |
216 | private static void convertTime(Calendar from, Calendar to) { |
217 | to.set(Calendar.ERA, from.get(Calendar.ERA)); |
218 | to.set(Calendar.YEAR, from.get(Calendar.YEAR)); |
219 | to.set(Calendar.MONTH, from.get(Calendar.MONTH)); |
220 | to.set(Calendar.DAY_OF_MONTH, from.get(Calendar.DAY_OF_MONTH)); |
221 | to.set(Calendar.HOUR_OF_DAY, from.get(Calendar.HOUR_OF_DAY)); |
222 | to.set(Calendar.MINUTE, from.get(Calendar.MINUTE)); |
223 | to.set(Calendar.SECOND, from.get(Calendar.SECOND)); |
224 | to.set(Calendar.MILLISECOND, from.get(Calendar.MILLISECOND)); |
225 | } |
226 | |
227 | /** |
228 | * Convert the timestamp using the specified calendar. |
229 | * |
230 | * @param x the time |
231 | * @param calendar the calendar |
232 | * @return the timestamp |
233 | */ |
234 | public static ValueTimestamp convertTimestamp(Timestamp x, Calendar calendar) { |
235 | if (calendar == null) { |
236 | throw DbException.getInvalidValueException("calendar", null); |
237 | } |
238 | Calendar cal = (Calendar) calendar.clone(); |
239 | cal.setTimeInMillis(x.getTime()); |
240 | long dateValue = dateValueFromCalendar(cal); |
241 | long nanos = nanosFromCalendar(cal); |
242 | nanos += x.getNanos() % 1000000; |
243 | return ValueTimestamp.fromDateValueAndNanos(dateValue, nanos); |
244 | } |
245 | |
246 | /** |
247 | * Parse a date string. The format is: [+|-]year-month-day |
248 | * |
249 | * @param s the string to parse |
250 | * @param start the parse index start |
251 | * @param end the parse index end |
252 | * @return the date value |
253 | * @throws IllegalArgumentException if there is a problem |
254 | */ |
255 | public static long parseDateValue(String s, int start, int end) { |
256 | if (s.charAt(start) == '+') { |
257 | // +year |
258 | start++; |
259 | } |
260 | // start at position 1 to support "-year" |
261 | int s1 = s.indexOf('-', start + 1); |
262 | int s2 = s.indexOf('-', s1 + 1); |
263 | if (s1 <= 0 || s2 <= s1) { |
264 | throw new IllegalArgumentException(s); |
265 | } |
266 | int year = Integer.parseInt(s.substring(start, s1)); |
267 | int month = Integer.parseInt(s.substring(s1 + 1, s2)); |
268 | int day = Integer.parseInt(s.substring(s2 + 1, end)); |
269 | if (!isValidDate(year, month, day)) { |
270 | throw new IllegalArgumentException(year + "-" + month + "-" + day); |
271 | } |
272 | return dateValue(year, month, day); |
273 | } |
274 | |
275 | /** |
276 | * Parse a time string. The format is: [-]hour:minute:second[.nanos] |
277 | * |
278 | * @param s the string to parse |
279 | * @param start the parse index start |
280 | * @param end the parse index end |
281 | * @param timeOfDay whether the result need to be within 0 (inclusive) and 1 |
282 | * day (exclusive) |
283 | * @return the time in nanoseconds |
284 | * @throws IllegalArgumentException if there is a problem |
285 | */ |
286 | public static long parseTimeNanos(String s, int start, int end, |
287 | boolean timeOfDay) { |
288 | int hour = 0, minute = 0, second = 0; |
289 | long nanos = 0; |
290 | int s1 = s.indexOf(':', start); |
291 | int s2 = s.indexOf(':', s1 + 1); |
292 | int s3 = s.indexOf('.', s2 + 1); |
293 | if (s1 <= 0 || s2 <= s1) { |
294 | throw new IllegalArgumentException(s); |
295 | } |
296 | boolean negative; |
297 | hour = Integer.parseInt(s.substring(start, s1)); |
298 | if (hour < 0) { |
299 | if (timeOfDay) { |
300 | throw new IllegalArgumentException(s); |
301 | } |
302 | negative = true; |
303 | hour = -hour; |
304 | } else { |
305 | negative = false; |
306 | } |
307 | minute = Integer.parseInt(s.substring(s1 + 1, s2)); |
308 | if (s3 < 0) { |
309 | second = Integer.parseInt(s.substring(s2 + 1, end)); |
310 | } else { |
311 | second = Integer.parseInt(s.substring(s2 + 1, s3)); |
312 | String n = (s.substring(s3 + 1, end) + "000000000").substring(0, 9); |
313 | nanos = Integer.parseInt(n); |
314 | } |
315 | if (hour >= 2000000 || minute < 0 || |
316 | minute >= 60 || second < 0 || second >= 60) { |
317 | throw new IllegalArgumentException(s); |
318 | } |
319 | if (timeOfDay && hour >= 24) { |
320 | throw new IllegalArgumentException(s); |
321 | } |
322 | nanos += ((((hour * 60L) + minute) * 60) + second) * 1000000000; |
323 | return negative ? -nanos : nanos; |
324 | } |
325 | |
326 | /** |
327 | * Calculate the milliseconds since 1970-01-01 (UTC) for the given date and |
328 | * time (in the specified timezone). |
329 | * |
330 | * @param tz the timezone of the parameters, |
331 | * or null for the default timezone |
332 | * @param year the absolute year (positive or negative) |
333 | * @param month the month (1-12) |
334 | * @param day the day (1-31) |
335 | * @param hour the hour (0-23) |
336 | * @param minute the minutes (0-59) |
337 | * @param second the number of seconds (0-59) |
338 | * @param millis the number of milliseconds |
339 | * @return the number of milliseconds (UTC) |
340 | */ |
341 | public static long getMillis(TimeZone tz, int year, int month, int day, |
342 | int hour, int minute, int second, int millis) { |
343 | try { |
344 | return getTimeTry(false, tz, year, month, day, hour, minute, second, millis); |
345 | } catch (IllegalArgumentException e) { |
346 | // special case: if the time simply doesn't exist because of |
347 | // daylight saving time changes, use the lenient version |
348 | String message = e.toString(); |
349 | if (message.indexOf("HOUR_OF_DAY") > 0) { |
350 | if (hour < 0 || hour > 23) { |
351 | throw e; |
352 | } |
353 | return getTimeTry(true, tz, year, month, day, hour, minute, |
354 | second, millis); |
355 | } else if (message.indexOf("DAY_OF_MONTH") > 0) { |
356 | int maxDay; |
357 | if (month == 2) { |
358 | maxDay = new GregorianCalendar().isLeapYear(year) ? 29 : 28; |
359 | } else { |
360 | maxDay = 30 + ((month + (month > 7 ? 1 : 0)) & 1); |
361 | } |
362 | if (day < 1 || day > maxDay) { |
363 | throw e; |
364 | } |
365 | // DAY_OF_MONTH is thrown for years > 2037 |
366 | // using the timezone Brasilia and others, |
367 | // for example for 2042-10-12 00:00:00. |
368 | hour += 6; |
369 | return getTimeTry(true, tz, year, month, day, hour, minute, |
370 | second, millis); |
371 | } else { |
372 | return getTimeTry(true, tz, year, month, day, hour, minute, |
373 | second, millis); |
374 | } |
375 | } |
376 | } |
377 | |
378 | private static long getTimeTry(boolean lenient, TimeZone tz, |
379 | int year, int month, int day, int hour, int minute, int second, |
380 | int millis) { |
381 | Calendar c; |
382 | if (tz == null) { |
383 | c = CACHED_CALENDAR.get(); |
384 | } else { |
385 | c = Calendar.getInstance(tz); |
386 | } |
387 | c.clear(); |
388 | c.setLenient(lenient); |
389 | setCalendarFields(c, year, month, day, hour, minute, second, millis); |
390 | return c.getTime().getTime(); |
391 | } |
392 | |
393 | private static void setCalendarFields(Calendar cal, int year, int month, |
394 | int day, int hour, int minute, int second, int millis) { |
395 | if (year <= 0) { |
396 | cal.set(Calendar.ERA, GregorianCalendar.BC); |
397 | cal.set(Calendar.YEAR, 1 - year); |
398 | } else { |
399 | cal.set(Calendar.ERA, GregorianCalendar.AD); |
400 | cal.set(Calendar.YEAR, year); |
401 | } |
402 | // january is 0 |
403 | cal.set(Calendar.MONTH, month - 1); |
404 | cal.set(Calendar.DAY_OF_MONTH, day); |
405 | cal.set(Calendar.HOUR_OF_DAY, hour); |
406 | cal.set(Calendar.MINUTE, minute); |
407 | cal.set(Calendar.SECOND, second); |
408 | cal.set(Calendar.MILLISECOND, millis); |
409 | } |
410 | |
411 | /** |
412 | * Get the specified field of a date, however with years normalized to |
413 | * positive or negative, and month starting with 1. |
414 | * |
415 | * @param d the date |
416 | * @param field the field type |
417 | * @return the value |
418 | */ |
419 | public static int getDatePart(java.util.Date d, int field) { |
420 | Calendar c = CACHED_CALENDAR.get(); |
421 | c.setTime(d); |
422 | if (field == Calendar.YEAR) { |
423 | return getYear(c); |
424 | } |
425 | int value = c.get(field); |
426 | if (field == Calendar.MONTH) { |
427 | return value + 1; |
428 | } |
429 | return value; |
430 | } |
431 | |
432 | /** |
433 | * Get the year (positive or negative) from a calendar. |
434 | * |
435 | * @param calendar the calendar |
436 | * @return the year |
437 | */ |
438 | private static int getYear(Calendar calendar) { |
439 | int year = calendar.get(Calendar.YEAR); |
440 | if (calendar.get(Calendar.ERA) == GregorianCalendar.BC) { |
441 | year = 1 - year; |
442 | } |
443 | return year; |
444 | } |
445 | |
446 | /** |
447 | * Get the number of milliseconds since 1970-01-01 in the local timezone, |
448 | * but without daylight saving time into account. |
449 | * |
450 | * @param d the date |
451 | * @return the milliseconds |
452 | */ |
453 | public static long getTimeLocalWithoutDst(java.util.Date d) { |
454 | return d.getTime() + CACHED_CALENDAR.get().get(Calendar.ZONE_OFFSET); |
455 | } |
456 | |
457 | /** |
458 | * Convert the number of milliseconds since 1970-01-01 in the local timezone |
459 | * to UTC, but without daylight saving time into account. |
460 | * |
461 | * @param millis the number of milliseconds in the local timezone |
462 | * @return the number of milliseconds in UTC |
463 | */ |
464 | public static long getTimeUTCWithoutDst(long millis) { |
465 | return millis - CACHED_CALENDAR.get().get(Calendar.ZONE_OFFSET); |
466 | } |
467 | |
468 | /** |
469 | * Return the day of week according to the ISO 8601 specification. Week |
470 | * starts at Monday. See also http://en.wikipedia.org/wiki/ISO_8601 |
471 | * |
472 | * @author Robert Rathsack |
473 | * |
474 | * @param date the date object which day of week should be calculated |
475 | * @return the day of the week, Monday as 1 to Sunday as 7 |
476 | */ |
477 | public static int getIsoDayOfWeek(java.util.Date date) { |
478 | Calendar cal = Calendar.getInstance(); |
479 | cal.setTimeInMillis(date.getTime()); |
480 | int val = cal.get(Calendar.DAY_OF_WEEK) - 1; |
481 | return val == 0 ? 7 : val; |
482 | } |
483 | |
484 | /** |
485 | * Returns the week of the year according to the ISO 8601 specification. The |
486 | * spec defines the first week of the year as the week which contains at |
487 | * least 4 days of the new year. The week starts at Monday. Therefore |
488 | * December 29th - 31th could belong to the next year and January 1st - 3th |
489 | * could belong to the previous year. If January 1st is on Thursday (or |
490 | * earlier) it belongs to the first week, otherwise to the last week of the |
491 | * previous year. Hence January 4th always belongs to the first week while |
492 | * the December 28th always belongs to the last week. |
493 | * |
494 | * @author Robert Rathsack |
495 | * @param date the date object which week of year should be calculated |
496 | * @return the week of the year |
497 | */ |
498 | public static int getIsoWeek(java.util.Date date) { |
499 | Calendar c = Calendar.getInstance(); |
500 | c.setTimeInMillis(date.getTime()); |
501 | c.setFirstDayOfWeek(Calendar.MONDAY); |
502 | c.setMinimalDaysInFirstWeek(4); |
503 | return c.get(Calendar.WEEK_OF_YEAR); |
504 | } |
505 | |
506 | /** |
507 | * Returns the year according to the ISO week definition. |
508 | * |
509 | * @author Robert Rathsack |
510 | * |
511 | * @param date the date object which year should be calculated |
512 | * @return the year |
513 | */ |
514 | public static int getIsoYear(java.util.Date date) { |
515 | Calendar cal = Calendar.getInstance(); |
516 | cal.setTimeInMillis(date.getTime()); |
517 | cal.setFirstDayOfWeek(Calendar.MONDAY); |
518 | cal.setMinimalDaysInFirstWeek(4); |
519 | int year = getYear(cal); |
520 | int month = cal.get(Calendar.MONTH); |
521 | int week = cal.get(Calendar.WEEK_OF_YEAR); |
522 | if (month == 0 && week > 51) { |
523 | year--; |
524 | } else if (month == 11 && week == 1) { |
525 | year++; |
526 | } |
527 | return year; |
528 | } |
529 | |
530 | /** |
531 | * Formats a date using a format string. |
532 | * |
533 | * @param date the date to format |
534 | * @param format the format string |
535 | * @param locale the locale |
536 | * @param timeZone the timezone |
537 | * @return the formatted date |
538 | */ |
539 | public static String formatDateTime(java.util.Date date, String format, |
540 | String locale, String timeZone) { |
541 | SimpleDateFormat dateFormat = getDateFormat(format, locale, timeZone); |
542 | synchronized (dateFormat) { |
543 | return dateFormat.format(date); |
544 | } |
545 | } |
546 | |
547 | /** |
548 | * Parses a date using a format string. |
549 | * |
550 | * @param date the date to parse |
551 | * @param format the parsing format |
552 | * @param locale the locale |
553 | * @param timeZone the timeZone |
554 | * @return the parsed date |
555 | */ |
556 | public static java.util.Date parseDateTime(String date, String format, |
557 | String locale, String timeZone) { |
558 | SimpleDateFormat dateFormat = getDateFormat(format, locale, timeZone); |
559 | try { |
560 | synchronized (dateFormat) { |
561 | return dateFormat.parse(date); |
562 | } |
563 | } catch (Exception e) { |
564 | // ParseException |
565 | throw DbException.get(ErrorCode.PARSE_ERROR_1, e, date); |
566 | } |
567 | } |
568 | |
569 | private static SimpleDateFormat getDateFormat(String format, String locale, |
570 | String timeZone) { |
571 | try { |
572 | // currently, a new instance is create for each call |
573 | // however, could cache the last few instances |
574 | SimpleDateFormat df; |
575 | if (locale == null) { |
576 | df = new SimpleDateFormat(format); |
577 | } else { |
578 | Locale l = new Locale(locale); |
579 | df = new SimpleDateFormat(format, l); |
580 | } |
581 | if (timeZone != null) { |
582 | df.setTimeZone(TimeZone.getTimeZone(timeZone)); |
583 | } |
584 | return df; |
585 | } catch (Exception e) { |
586 | throw DbException.get(ErrorCode.PARSE_ERROR_1, e, |
587 | format + "/" + locale + "/" + timeZone); |
588 | } |
589 | } |
590 | |
591 | /** |
592 | * Verify if the specified date is valid. |
593 | * |
594 | * @param year the year |
595 | * @param month the month (January is 1) |
596 | * @param day the day (1 is the first of the month) |
597 | * @return true if it is valid |
598 | */ |
599 | public static boolean isValidDate(int year, int month, int day) { |
600 | if (month < 1 || month > 12 || day < 1) { |
601 | return false; |
602 | } |
603 | if (year > 1582) { |
604 | // Gregorian calendar |
605 | if (month != 2) { |
606 | return day <= NORMAL_DAYS_PER_MONTH[month]; |
607 | } |
608 | // February |
609 | if ((year & 3) != 0) { |
610 | return day <= 28; |
611 | } |
612 | return day <= ((year % 100 != 0) || (year % 400 == 0) ? 29 : 28); |
613 | } else if (year == 1582 && month == 10) { |
614 | // special case: days 1582-10-05 .. 1582-10-14 don't exist |
615 | return day <= 31 && (day < 5 || day > 14); |
616 | } |
617 | if (month != 2 && day <= NORMAL_DAYS_PER_MONTH[month]) { |
618 | return true; |
619 | } |
620 | return day <= ((year & 3) != 0 ? 28 : 29); |
621 | } |
622 | |
623 | /** |
624 | * Convert a date value to a date, using the default timezone. |
625 | * |
626 | * @param dateValue the date value |
627 | * @return the date |
628 | */ |
629 | public static Date convertDateValueToDate(long dateValue) { |
630 | long millis = getMillis(null, |
631 | yearFromDateValue(dateValue), |
632 | monthFromDateValue(dateValue), |
633 | dayFromDateValue(dateValue), 0, 0, 0, 0); |
634 | return new Date(millis); |
635 | } |
636 | |
637 | /** |
638 | * Convert a date value / time value to a timestamp, using the default |
639 | * timezone. |
640 | * |
641 | * @param dateValue the date value |
642 | * @param timeNanos the nanoseconds since midnight |
643 | * @return the timestamp |
644 | */ |
645 | public static Timestamp convertDateValueToTimestamp(long dateValue, |
646 | long timeNanos) { |
647 | long millis = timeNanos / 1000000; |
648 | timeNanos -= millis * 1000000; |
649 | long s = millis / 1000; |
650 | millis -= s * 1000; |
651 | long m = s / 60; |
652 | s -= m * 60; |
653 | long h = m / 60; |
654 | m -= h * 60; |
655 | long ms = getMillis(null, |
656 | yearFromDateValue(dateValue), |
657 | monthFromDateValue(dateValue), |
658 | dayFromDateValue(dateValue), |
659 | (int) h, (int) m, (int) s, 0); |
660 | Timestamp ts = new Timestamp(ms); |
661 | ts.setNanos((int) (timeNanos + millis * 1000000)); |
662 | return ts; |
663 | } |
664 | |
665 | /** |
666 | * Convert a time value to a time, using the default |
667 | * timezone. |
668 | * |
669 | * @param nanos the nanoseconds since midnight |
670 | * @return the time |
671 | */ |
672 | public static Time convertNanoToTime(long nanos) { |
673 | long millis = nanos / 1000000; |
674 | long s = millis / 1000; |
675 | millis -= s * 1000; |
676 | long m = s / 60; |
677 | s -= m * 60; |
678 | long h = m / 60; |
679 | m -= h * 60; |
680 | long ms = getMillis(null, |
681 | 1970, 1, 1, (int) (h % 24), (int) m, (int) s, (int) millis); |
682 | return new Time(ms); |
683 | } |
684 | |
685 | /** |
686 | * Get the year from a date value. |
687 | * |
688 | * @param x the date value |
689 | * @return the year |
690 | */ |
691 | public static int yearFromDateValue(long x) { |
692 | return (int) (x >>> SHIFT_YEAR); |
693 | } |
694 | |
695 | /** |
696 | * Get the month from a date value. |
697 | * |
698 | * @param x the date value |
699 | * @return the month (1..12) |
700 | */ |
701 | public static int monthFromDateValue(long x) { |
702 | return (int) (x >>> SHIFT_MONTH) & 15; |
703 | } |
704 | |
705 | /** |
706 | * Get the day of month from a date value. |
707 | * |
708 | * @param x the date value |
709 | * @return the day (1..31) |
710 | */ |
711 | public static int dayFromDateValue(long x) { |
712 | return (int) (x & 31); |
713 | } |
714 | |
715 | /** |
716 | * Get the date value from a given date. |
717 | * |
718 | * @param year the year |
719 | * @param month the month (1..12) |
720 | * @param day the day (1..31) |
721 | * @return the date value |
722 | */ |
723 | public static long dateValue(long year, int month, int day) { |
724 | return (year << SHIFT_YEAR) | (month << SHIFT_MONTH) | day; |
725 | } |
726 | |
727 | /** |
728 | * Calculate the date value (in the default timezone) from a given time in |
729 | * milliseconds in UTC. |
730 | * |
731 | * @param ms the milliseconds |
732 | * @return the date value |
733 | */ |
734 | public static long dateValueFromDate(long ms) { |
735 | Calendar cal = CACHED_CALENDAR.get(); |
736 | cal.clear(); |
737 | cal.setTimeInMillis(ms); |
738 | return dateValueFromCalendar(cal); |
739 | } |
740 | |
741 | /** |
742 | * Calculate the date value from a given calendar. |
743 | * |
744 | * @param cal the calendar |
745 | * @return the date value |
746 | */ |
747 | private static long dateValueFromCalendar(Calendar cal) { |
748 | int year, month, day; |
749 | year = getYear(cal); |
750 | month = cal.get(Calendar.MONTH) + 1; |
751 | day = cal.get(Calendar.DAY_OF_MONTH); |
752 | return ((long) year << SHIFT_YEAR) | (month << SHIFT_MONTH) | day; |
753 | } |
754 | |
755 | /** |
756 | * Calculate the nanoseconds since midnight (in the default timezone) from a |
757 | * given time in milliseconds in UTC. |
758 | * |
759 | * @param ms the milliseconds |
760 | * @return the nanoseconds |
761 | */ |
762 | public static long nanosFromDate(long ms) { |
763 | Calendar cal = CACHED_CALENDAR.get(); |
764 | cal.clear(); |
765 | cal.setTimeInMillis(ms); |
766 | return nanosFromCalendar(cal); |
767 | } |
768 | |
769 | /** |
770 | * Calculate the nanoseconds since midnight from a given calendar. |
771 | * |
772 | * @param cal the calendar |
773 | * @return the nanoseconds |
774 | */ |
775 | private static long nanosFromCalendar(Calendar cal) { |
776 | int h = cal.get(Calendar.HOUR_OF_DAY); |
777 | int m = cal.get(Calendar.MINUTE); |
778 | int s = cal.get(Calendar.SECOND); |
779 | int millis = cal.get(Calendar.MILLISECOND); |
780 | return ((((((h * 60L) + m) * 60) + s) * 1000) + millis) * 1000000; |
781 | } |
782 | |
783 | /** |
784 | * Calculate the normalized timestamp. |
785 | * |
786 | * @param absoluteDay the absolute day |
787 | * @param nanos the nanoseconds (may be negative or larger than one day) |
788 | * @return the timestamp |
789 | */ |
790 | public static ValueTimestamp normalizeTimestamp(long absoluteDay, long nanos) { |
791 | if (nanos > NANOS_PER_DAY || nanos < 0) { |
792 | long d; |
793 | if (nanos > NANOS_PER_DAY) { |
794 | d = nanos / NANOS_PER_DAY; |
795 | } else { |
796 | d = (nanos - NANOS_PER_DAY + 1) / NANOS_PER_DAY; |
797 | } |
798 | nanos -= d * NANOS_PER_DAY; |
799 | absoluteDay += d; |
800 | } |
801 | return ValueTimestamp.fromDateValueAndNanos( |
802 | dateValueFromAbsoluteDay(absoluteDay), nanos); |
803 | } |
804 | |
805 | /** |
806 | * Calculate the absolute day from a date value. |
807 | * |
808 | * @param dateValue the date value |
809 | * @return the absolute day |
810 | */ |
811 | public static long absoluteDayFromDateValue(long dateValue) { |
812 | long y = yearFromDateValue(dateValue); |
813 | int m = monthFromDateValue(dateValue); |
814 | int d = dayFromDateValue(dateValue); |
815 | if (m <= 2) { |
816 | y--; |
817 | m += 12; |
818 | } |
819 | long a = ((y * 2922L) >> 3) + DAYS_OFFSET[m - 3] + d - 719484; |
820 | if (y <= 1582 && ((y < 1582) || (m * 100 + d < 1005))) { |
821 | // Julian calendar (cutover at 1582-10-04 / 1582-10-15) |
822 | a += 13; |
823 | } else if (y < 1901 || y > 2099) { |
824 | // Gregorian calendar (slow mode) |
825 | a += (y / 400) - (y / 100) + 15; |
826 | } |
827 | return a; |
828 | } |
829 | |
830 | /** |
831 | * Calculate the date value from an absolute day. |
832 | * |
833 | * @param absoluteDay the absolute day |
834 | * @return the date value |
835 | */ |
836 | public static long dateValueFromAbsoluteDay(long absoluteDay) { |
837 | long d = absoluteDay + 719468; |
838 | long y100 = 0, offset; |
839 | if (d > 578040) { |
840 | // Gregorian calendar |
841 | long y400 = d / 146097; |
842 | d -= y400 * 146097; |
843 | y100 = d / 36524; |
844 | d -= y100 * 36524; |
845 | offset = y400 * 400 + y100 * 100; |
846 | } else { |
847 | // Julian calendar |
848 | d += 292200000002L; |
849 | offset = -800000000; |
850 | } |
851 | long y4 = d / 1461; |
852 | d -= y4 * 1461; |
853 | long y = d / 365; |
854 | d -= y * 365; |
855 | if (d == 0 && (y == 4 || y100 == 4)) { |
856 | y--; |
857 | d += 365; |
858 | } |
859 | y += offset + y4 * 4; |
860 | // month of a day |
861 | int m = ((int) d * 2 + 1) * 5 / 306; |
862 | d -= DAYS_OFFSET[m] - 1; |
863 | if (m >= 10) { |
864 | y++; |
865 | m -= 12; |
866 | } |
867 | return dateValue(y, m + 3, (int) d); |
868 | } |
869 | |
870 | } |