All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.h2.util.DateTimeUtils Maven / Gradle / Ivy

There is a newer version: 2.3.232
Show newest version
/*
 * Copyright 2004-2019 H2 Group. Multiple-Licensed under the MPL 2.0, and the
 * EPL 1.0 (http://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 * Iso8601: Initial Developer: Robert Rathsack (firstName dot lastName at gmx
 * dot de)
 */
package org.h2.util;

import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import org.h2.engine.Mode;
import org.h2.value.Value;
import org.h2.value.ValueDate;
import org.h2.value.ValueNull;
import org.h2.value.ValueTime;
import org.h2.value.ValueTimestamp;
import org.h2.value.ValueTimestampTimeZone;

/**
 * This utility class contains time conversion functions.
 * 

* Date value: a bit field with bits for the year, month, and day. Absolute day: * the day number (0 means 1970-01-01). */ public class DateTimeUtils { /** * The number of milliseconds per day. */ public static final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000L; /** * The number of seconds per day. */ public static final long SECONDS_PER_DAY = 24 * 60 * 60; /** * UTC time zone. */ public static final TimeZone UTC = TimeZone.getTimeZone("UTC"); /** * The number of nanoseconds per second. */ public static final long NANOS_PER_SECOND = 1_000_000_000; /** * The number of nanoseconds per minute. */ public static final long NANOS_PER_MINUTE = 60 * NANOS_PER_SECOND; /** * The number of nanoseconds per hour. */ public static final long NANOS_PER_HOUR = 60 * NANOS_PER_MINUTE; /** * The number of nanoseconds per day. */ public static final long NANOS_PER_DAY = MILLIS_PER_DAY * 1_000_000; private static final int SHIFT_YEAR = 9; private static final int SHIFT_MONTH = 5; /** * Date value for 1970-01-01. */ public static final int EPOCH_DATE_VALUE = (1970 << SHIFT_YEAR) + (1 << SHIFT_MONTH) + 1; private static final int[] NORMAL_DAYS_PER_MONTH = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; /** * Offsets of month within a year, starting with March, April,... */ private static final int[] DAYS_OFFSET = { 0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337, 366 }; /** * Multipliers for {@link #convertScale(long, int)}. */ private static final int[] CONVERT_SCALE_TABLE = { 1_000_000_000, 100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10 }; /** * The thread local. Can not override initialValue because this would result * in an inner class, which would not be garbage collected in a web * container, and prevent the class loader of H2 from being garbage * collected. Using a ThreadLocal on a system class like Calendar does not * have that problem, and while it is still a small memory leak, it is not a * class loader memory leak. */ private static final ThreadLocal CACHED_CALENDAR = new ThreadLocal<>(); /** * A cached instance of Calendar used when a timezone is specified. */ private static final ThreadLocal CACHED_CALENDAR_NON_DEFAULT_TIMEZONE = new ThreadLocal<>(); /** * Cached local time zone. */ private static volatile TimeZone timeZone; /** * Raw offset doesn't change during DST transitions, but changes during * other transitions that some time zones have. H2 1.4.193 and later * versions use zone offset that is valid for startup time for performance * reasons. This code is now used only by old PageStore engine and its * datetime storage code has issues with all time zone transitions, so this * buggy logic is preserved as is too. */ private static int zoneOffsetMillis = createGregorianCalendar().get(Calendar.ZONE_OFFSET); private DateTimeUtils() { // utility class } /** * Returns local time zone offset for a specified timestamp. * * @param ms milliseconds since Epoch in UTC * @return local time zone offset */ public static int getTimeZoneOffset(long ms) { TimeZone tz = timeZone; if (tz == null) { timeZone = tz = TimeZone.getDefault(); } return tz.getOffset(ms); } /** * Reset the cached calendar for default timezone, for example after * changing the default timezone. */ public static void resetCalendar() { CACHED_CALENDAR.remove(); timeZone = null; zoneOffsetMillis = createGregorianCalendar().get(Calendar.ZONE_OFFSET); } /** * Get a calendar for the default timezone. * * @return a calendar instance. A cached instance is returned where possible */ public static GregorianCalendar getCalendar() { GregorianCalendar c = CACHED_CALENDAR.get(); if (c == null) { c = createGregorianCalendar(); CACHED_CALENDAR.set(c); } c.clear(); return c; } /** * Get a calendar for the given timezone. * * @param tz timezone for the calendar, is never null * @return a calendar instance. A cached instance is returned where possible */ private static GregorianCalendar getCalendar(TimeZone tz) { GregorianCalendar c = CACHED_CALENDAR_NON_DEFAULT_TIMEZONE.get(); if (c == null || !c.getTimeZone().equals(tz)) { c = createGregorianCalendar(tz); CACHED_CALENDAR_NON_DEFAULT_TIMEZONE.set(c); } c.clear(); return c; } /** * Creates a Gregorian calendar for the default timezone using the default * locale. Dates in H2 are represented in a Gregorian calendar. So this * method should be used instead of Calendar.getInstance() to ensure that * the Gregorian calendar is used for all date processing instead of a * default locale calendar that can be non-Gregorian in some locales. * * @return a new calendar instance. */ public static GregorianCalendar createGregorianCalendar() { return new GregorianCalendar(); } /** * Creates a Gregorian calendar for the given timezone using the default * locale. Dates in H2 are represented in a Gregorian calendar. So this * method should be used instead of Calendar.getInstance() to ensure that * the Gregorian calendar is used for all date processing instead of a * default locale calendar that can be non-Gregorian in some locales. * * @param tz timezone for the calendar, is never null * @return a new calendar instance. */ public static GregorianCalendar createGregorianCalendar(TimeZone tz) { return new GregorianCalendar(tz); } /** * Convert the date to the specified time zone. * * @param value the date (might be ValueNull) * @param calendar the calendar * @return the date using the correct time zone */ public static Date convertDate(Value value, Calendar calendar) { if (value == ValueNull.INSTANCE) { return null; } ValueDate d = (ValueDate) value.convertTo(Value.DATE); Calendar cal = (Calendar) calendar.clone(); cal.clear(); cal.setLenient(true); long dateValue = d.getDateValue(); long ms = convertToMillis(cal, yearFromDateValue(dateValue), monthFromDateValue(dateValue), dayFromDateValue(dateValue), 0, 0, 0, 0); return new Date(ms); } /** * Convert the time to the specified time zone. * * @param value the time (might be ValueNull) * @param calendar the calendar * @return the time using the correct time zone */ public static Time convertTime(Value value, Calendar calendar) { if (value == ValueNull.INSTANCE) { return null; } ValueTime t = (ValueTime) value.convertTo(Value.TIME); Calendar cal = (Calendar) calendar.clone(); cal.clear(); cal.setLenient(true); long nanos = t.getNanos(); long millis = nanos / 1_000_000; nanos -= millis * 1_000_000; long s = millis / 1_000; millis -= s * 1_000; long m = s / 60; s -= m * 60; long h = m / 60; m -= h * 60; return new Time(convertToMillis(cal, 1970, 1, 1, (int) h, (int) m, (int) s, (int) millis)); } /** * Convert the timestamp to the specified time zone. * * @param value the timestamp (might be ValueNull) * @param calendar the calendar * @return the timestamp using the correct time zone */ public static Timestamp convertTimestamp(Value value, Calendar calendar) { if (value == ValueNull.INSTANCE) { return null; } ValueTimestamp ts = (ValueTimestamp) value.convertTo(Value.TIMESTAMP); Calendar cal = (Calendar) calendar.clone(); cal.clear(); cal.setLenient(true); long dateValue = ts.getDateValue(); long nanos = ts.getTimeNanos(); long millis = nanos / 1_000_000; nanos -= millis * 1_000_000; long s = millis / 1_000; millis -= s * 1_000; long m = s / 60; s -= m * 60; long h = m / 60; m -= h * 60; long ms = convertToMillis(cal, yearFromDateValue(dateValue), monthFromDateValue(dateValue), dayFromDateValue(dateValue), (int) h, (int) m, (int) s, (int) millis); Timestamp x = new Timestamp(ms); x.setNanos((int) (nanos + millis * 1_000_000)); return x; } /** * Convert a java.util.Date using the specified calendar. * * @param x the date * @param calendar the calendar * @return the date */ public static ValueDate convertDate(Date x, Calendar calendar) { Calendar cal = (Calendar) calendar.clone(); cal.setTimeInMillis(x.getTime()); long dateValue = dateValueFromCalendar(cal); return ValueDate.fromDateValue(dateValue); } /** * Convert the time using the specified calendar. * * @param x the time * @param calendar the calendar * @return the time */ public static ValueTime convertTime(Time x, Calendar calendar) { Calendar cal = (Calendar) calendar.clone(); cal.setTimeInMillis(x.getTime()); long nanos = nanosFromCalendar(cal); return ValueTime.fromNanos(nanos); } /** * Convert the timestamp using the specified calendar. * * @param x the time * @param calendar the calendar * @return the timestamp */ public static ValueTimestamp convertTimestamp(Timestamp x, Calendar calendar) { Calendar cal = (Calendar) calendar.clone(); cal.setTimeInMillis(x.getTime()); long dateValue = dateValueFromCalendar(cal); long nanos = nanosFromCalendar(cal); nanos += x.getNanos() % 1_000_000; return ValueTimestamp.fromDateValueAndNanos(dateValue, nanos); } /** * Parse a date string. The format is: [+|-]year-month-day * or [+|-]yyyyMMdd. * * @param s the string to parse * @param start the parse index start * @param end the parse index end * @return the date value * @throws IllegalArgumentException if there is a problem */ public static long parseDateValue(String s, int start, int end) { if (s.charAt(start) == '+') { // +year start++; } // start at position 1 to support "-year" int yEnd = s.indexOf('-', start + 1); int mStart, mEnd, dStart; if (yEnd > 0) { // Standard [+|-]year-month-day format mStart = yEnd + 1; mEnd = s.indexOf('-', mStart); if (mEnd <= mStart) { throw new IllegalArgumentException(s); } dStart = mEnd + 1; } else { // Additional [+|-]yyyyMMdd format for compatibility mEnd = dStart = end - 2; yEnd = mStart = mEnd - 2; // Accept only 3 or more digits in year for now if (yEnd < start + 3) { throw new IllegalArgumentException(s); } } int year = Integer.parseInt(s.substring(start, yEnd)); int month = StringUtils.parseUInt31(s, mStart, mEnd); int day = StringUtils.parseUInt31(s, dStart, end); if (!isValidDate(year, month, day)) { throw new IllegalArgumentException(year + "-" + month + "-" + day); } return dateValue(year, month, day); } /** * Parse a time string. The format is: hour:minute[:second[.nanos]], * hhmm[ss[.nanos]], or hour.minute.second[.nanos]. * * @param s the string to parse * @param start the parse index start * @param end the parse index end * @return the time in nanoseconds * @throws IllegalArgumentException if there is a problem */ public static long parseTimeNanos(String s, int start, int end) { int hour, minute, second, nanos; int hEnd = s.indexOf(':', start); int mStart, mEnd, sStart, sEnd; if (hEnd > 0) { mStart = hEnd + 1; mEnd = s.indexOf(':', mStart); if (mEnd >= mStart) { // Standard hour:minute:second[.nanos] format sStart = mEnd + 1; sEnd = s.indexOf('.', sStart); } else { // Additional hour:minute format for compatibility mEnd = end; sStart = sEnd = -1; } } else { int t = s.indexOf('.', start); if (t < 0) { // Additional hhmm[ss] format for compatibility hEnd = mStart = start + 2; mEnd = mStart + 2; int len = end - start; if (len == 6) { sStart = mEnd; sEnd = -1; } else if (len == 4) { sStart = sEnd = -1; } else { throw new IllegalArgumentException(s); } } else if (t >= start + 6) { // Additional hhmmss.nanos format for compatibility if (t - start != 6) { throw new IllegalArgumentException(s); } hEnd = mStart = start + 2; mEnd = sStart = mStart + 2; sEnd = t; } else { // Additional hour.minute.second[.nanos] IBM DB2 time format hEnd = t; mStart = hEnd + 1; mEnd = s.indexOf('.', mStart); if (mEnd <= mStart) { throw new IllegalArgumentException(s); } sStart = mEnd + 1; sEnd = s.indexOf('.', sStart); } } hour = StringUtils.parseUInt31(s, start, hEnd); if (hour >= 24) { throw new IllegalArgumentException(s); } minute = StringUtils.parseUInt31(s, mStart, mEnd); if (sStart > 0) { if (sEnd < 0) { second = StringUtils.parseUInt31(s, sStart, end); nanos = 0; } else { second = StringUtils.parseUInt31(s, sStart, sEnd); nanos = parseNanos(s, sEnd + 1, end); } } else { second = nanos = 0; } if (minute >= 60 || second >= 60) { throw new IllegalArgumentException(s); } return ((((hour * 60L) + minute) * 60) + second) * NANOS_PER_SECOND + nanos; } /** * Parse nanoseconds. * * @param s String to parse. * @param start Begin position at the string to read. * @param end End position at the string to read. * @return Parsed nanoseconds. */ static int parseNanos(String s, int start, int end) { if (start >= end) { throw new IllegalArgumentException(s); } int nanos = 0, mul = 100_000_000; do { char c = s.charAt(start); if (c < '0' || c > '9') { throw new IllegalArgumentException(s); } nanos += mul * (c - '0'); // mul can become 0, but continue loop anyway to ensure that all // remaining digits are valid mul /= 10; } while (++start < end); return nanos; } /** * See: * https://stackoverflow.com/questions/3976616/how-to-find-nth-occurrence-of-character-in-a-string#answer-3976656 */ private static int findNthIndexOf(String str, char chr, int n) { int pos = str.indexOf(chr); while (--n > 0 && pos != -1) { pos = str.indexOf(chr, pos + 1); } return pos; } /** * Parses timestamp value from the specified string. * * @param s * string to parse * @param mode * database mode, or {@code null} * @param withTimeZone * if {@code true} return {@link ValueTimestampTimeZone} instead of * {@link ValueTimestamp} * @return parsed timestamp */ public static Value parseTimestamp(String s, Mode mode, boolean withTimeZone) { int dateEnd = s.indexOf(' '); if (dateEnd < 0) { // ISO 8601 compatibility dateEnd = s.indexOf('T'); if (dateEnd < 0 && mode != null && mode.allowDB2TimestampFormat) { // DB2 also allows dash between date and time dateEnd = findNthIndexOf(s, '-', 3); } } int timeStart; if (dateEnd < 0) { dateEnd = s.length(); timeStart = -1; } else { timeStart = dateEnd + 1; } long dateValue = parseDateValue(s, 0, dateEnd); long nanos; short tzMinutes = 0; if (timeStart < 0) { nanos = 0; } else { int timeEnd = s.length(); TimeZone tz = null; if (s.endsWith("Z")) { tz = UTC; timeEnd--; } else { int timeZoneStart = s.indexOf('+', dateEnd + 1); if (timeZoneStart < 0) { timeZoneStart = s.indexOf('-', dateEnd + 1); } if (timeZoneStart >= 0) { // Allow [timeZoneName] part after time zone offset int offsetEnd = s.indexOf('[', timeZoneStart + 1); if (offsetEnd < 0) { offsetEnd = s.length(); } String tzName = "GMT" + s.substring(timeZoneStart, offsetEnd); tz = TimeZone.getTimeZone(tzName); if (!tz.getID().startsWith(tzName)) { throw new IllegalArgumentException( tzName + " (" + tz.getID() + "?)"); } if (s.charAt(timeZoneStart - 1) == ' ') { timeZoneStart--; } timeEnd = timeZoneStart; } else { timeZoneStart = s.indexOf(' ', dateEnd + 1); if (timeZoneStart > 0) { String tzName = s.substring(timeZoneStart + 1); tz = TimeZone.getTimeZone(tzName); if (!tz.getID().startsWith(tzName)) { throw new IllegalArgumentException(tzName); } timeEnd = timeZoneStart; } } } nanos = parseTimeNanos(s, dateEnd + 1, timeEnd); if (tz != null) { if (withTimeZone) { if (tz != UTC) { long millis = convertDateTimeValueToMillis(tz, dateValue, nanos / 1_000_000); tzMinutes = (short) (tz.getOffset(millis) / 60_000); } } else { long millis = convertDateTimeValueToMillis(tz, dateValue, nanos / 1_000_000); millis += getTimeZoneOffset(millis); dateValue = dateValueFromLocalMillis(millis); nanos = nanos % 1_000_000 + nanosFromLocalMillis(millis); } } } if (withTimeZone) { return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, nanos, tzMinutes); } return ValueTimestamp.fromDateValueAndNanos(dateValue, nanos); } /** * Calculates the time zone offset in minutes for the specified time zone, date * value, and nanoseconds since midnight. * * @param tz * time zone, or {@code null} for default * @param dateValue * date value * @param timeNanos * nanoseconds since midnight * @return time zone offset in milliseconds */ public static int getTimeZoneOffsetMillis(TimeZone tz, long dateValue, long timeNanos) { long msec = timeNanos / 1_000_000; long utc = convertDateTimeValueToMillis(tz, dateValue, msec); long local = absoluteDayFromDateValue(dateValue) * MILLIS_PER_DAY + msec; return (int) (local - utc); } /** * Calculates the milliseconds since epoch for the specified date value, * nanoseconds since midnight, and time zone offset. * @param dateValue * date value * @param timeNanos * nanoseconds since midnight * @param offsetMins * time zone offset in minutes * @return milliseconds since epoch in UTC */ public static long getMillis(long dateValue, long timeNanos, short offsetMins) { return absoluteDayFromDateValue(dateValue) * MILLIS_PER_DAY + timeNanos / 1_000_000 - offsetMins * 60_000; } /** * Calculate the milliseconds since 1970-01-01 (UTC) for the given date and * time (in the specified timezone). * * @param tz the timezone of the parameters, or null for the default * timezone * @param year the absolute year (positive or negative) * @param month the month (1-12) * @param day the day (1-31) * @param hour the hour (0-23) * @param minute the minutes (0-59) * @param second the number of seconds (0-59) * @param millis the number of milliseconds * @return the number of milliseconds (UTC) */ public static long getMillis(TimeZone tz, int year, int month, int day, int hour, int minute, int second, int millis) { GregorianCalendar c; if (tz == null) { c = getCalendar(); } else { c = getCalendar(tz); } c.setLenient(false); try { return convertToMillis(c, year, month, day, hour, minute, second, millis); } catch (IllegalArgumentException e) { // special case: if the time simply doesn't exist because of // daylight saving time changes, use the lenient version String message = e.toString(); if (message.indexOf("HOUR_OF_DAY") > 0) { if (hour < 0 || hour > 23) { throw e; } } else if (message.indexOf("DAY_OF_MONTH") > 0) { int maxDay; if (month == 2) { maxDay = c.isLeapYear(year) ? 29 : 28; } else { maxDay = NORMAL_DAYS_PER_MONTH[month]; } if (day < 1 || day > maxDay) { throw e; } // DAY_OF_MONTH is thrown for years > 2037 // using the timezone Brasilia and others, // for example for 2042-10-12 00:00:00. hour += 6; } c.setLenient(true); return convertToMillis(c, year, month, day, hour, minute, second, millis); } } private static long convertToMillis(Calendar cal, int year, int month, int day, int hour, int minute, int second, int millis) { if (year <= 0) { cal.set(Calendar.ERA, GregorianCalendar.BC); cal.set(Calendar.YEAR, 1 - year); } else { cal.set(Calendar.ERA, GregorianCalendar.AD); cal.set(Calendar.YEAR, year); } // january is 0 cal.set(Calendar.MONTH, month - 1); cal.set(Calendar.DAY_OF_MONTH, day); cal.set(Calendar.HOUR_OF_DAY, hour); cal.set(Calendar.MINUTE, minute); cal.set(Calendar.SECOND, second); cal.set(Calendar.MILLISECOND, millis); return cal.getTimeInMillis(); } /** * Extracts date value and nanos of day from the specified value. * * @param value * value to extract fields from * @return array with date value and nanos of day */ public static long[] dateAndTimeFromValue(Value value) { long dateValue = EPOCH_DATE_VALUE; long timeNanos = 0; if (value instanceof ValueTimestamp) { ValueTimestamp v = (ValueTimestamp) value; dateValue = v.getDateValue(); timeNanos = v.getTimeNanos(); } else if (value instanceof ValueDate) { dateValue = ((ValueDate) value).getDateValue(); } else if (value instanceof ValueTime) { timeNanos = ((ValueTime) value).getNanos(); } else if (value instanceof ValueTimestampTimeZone) { ValueTimestampTimeZone v = (ValueTimestampTimeZone) value; dateValue = v.getDateValue(); timeNanos = v.getTimeNanos(); } else { ValueTimestamp v = (ValueTimestamp) value.convertTo(Value.TIMESTAMP); dateValue = v.getDateValue(); timeNanos = v.getTimeNanos(); } return new long[] {dateValue, timeNanos}; } /** * Creates a new date-time value with the same type as original value. If * original value is a ValueTimestampTimeZone, returned value will have the same * time zone offset as original value. * * @param original * original value * @param dateValue * date value for the returned value * @param timeNanos * nanos of day for the returned value * @param forceTimestamp * if {@code true} return ValueTimestamp if original argument is * ValueDate or ValueTime * @return new value with specified date value and nanos of day */ public static Value dateTimeToValue(Value original, long dateValue, long timeNanos, boolean forceTimestamp) { if (!(original instanceof ValueTimestamp)) { if (!forceTimestamp) { if (original instanceof ValueDate) { return ValueDate.fromDateValue(dateValue); } if (original instanceof ValueTime) { return ValueTime.fromNanos(timeNanos); } } if (original instanceof ValueTimestampTimeZone) { return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, timeNanos, ((ValueTimestampTimeZone) original).getTimeZoneOffsetMins()); } } return ValueTimestamp.fromDateValueAndNanos(dateValue, timeNanos); } /** * Get the number of milliseconds since 1970-01-01 in the local timezone, * but without daylight saving time into account. * * @param d the date * @return the milliseconds */ public static long getTimeLocalWithoutDst(java.util.Date d) { return d.getTime() + zoneOffsetMillis; } /** * Convert the number of milliseconds since 1970-01-01 in the local timezone * to UTC, but without daylight saving time into account. * * @param millis the number of milliseconds in the local timezone * @return the number of milliseconds in UTC */ public static long getTimeUTCWithoutDst(long millis) { return millis - zoneOffsetMillis; } /** * Returns day of week. * * @param dateValue * the date value * @param firstDayOfWeek * first day of week, Monday as 1, Sunday as 7 or 0 * @return day of week * @see #getIsoDayOfWeek(long) */ public static int getDayOfWeek(long dateValue, int firstDayOfWeek) { return getDayOfWeekFromAbsolute(absoluteDayFromDateValue(dateValue), firstDayOfWeek); } /** * Get the day of the week from the absolute day value. * * @param absoluteValue the absolute day * @param firstDayOfWeek the first day of the week * @return the day of week */ public static int getDayOfWeekFromAbsolute(long absoluteValue, int firstDayOfWeek) { return absoluteValue >= 0 ? (int) ((absoluteValue - firstDayOfWeek + 11) % 7) + 1 : (int) ((absoluteValue - firstDayOfWeek - 2) % 7) + 7; } /** * Returns number of day in year. * * @param dateValue * the date value * @return number of day in year */ public static int getDayOfYear(long dateValue) { return (int) (absoluteDayFromDateValue(dateValue) - absoluteDayFromYear(yearFromDateValue(dateValue))) + 1; } /** * Returns ISO day of week. * * @param dateValue * the date value * @return ISO day of week, Monday as 1 to Sunday as 7 * @see #getSundayDayOfWeek(long) */ public static int getIsoDayOfWeek(long dateValue) { return getDayOfWeek(dateValue, 1); } /** * Returns ISO number of week in year. * * @param dateValue * the date value * @return number of week in year * @see #getIsoWeekYear(long) * @see #getWeekOfYear(long, int, int) */ public static int getIsoWeekOfYear(long dateValue) { return getWeekOfYear(dateValue, 1, 4); } /** * Returns ISO week year. * * @param dateValue * the date value * @return ISO week year * @see #getIsoWeekOfYear(long) * @see #getWeekYear(long, int, int) */ public static int getIsoWeekYear(long dateValue) { return getWeekYear(dateValue, 1, 4); } /** * Returns day of week with Sunday as 1. * * @param dateValue * the date value * @return day of week, Sunday as 1 to Monday as 7 * @see #getIsoDayOfWeek(long) */ public static int getSundayDayOfWeek(long dateValue) { return getDayOfWeek(dateValue, 0); } /** * Returns number of week in year. * * @param dateValue * the date value * @param firstDayOfWeek * first day of week, Monday as 1, Sunday as 7 or 0 * @param minimalDaysInFirstWeek * minimal days in first week of year * @return number of week in year * @see #getIsoWeekOfYear(long) */ public static int getWeekOfYear(long dateValue, int firstDayOfWeek, int minimalDaysInFirstWeek) { long abs = absoluteDayFromDateValue(dateValue); int year = yearFromDateValue(dateValue); long base = getWeekOfYearBase(year, firstDayOfWeek, minimalDaysInFirstWeek); if (abs - base < 0) { base = getWeekOfYearBase(year - 1, firstDayOfWeek, minimalDaysInFirstWeek); } else if (monthFromDateValue(dateValue) == 12 && 24 + minimalDaysInFirstWeek < dayFromDateValue(dateValue)) { if (abs >= getWeekOfYearBase(year + 1, firstDayOfWeek, minimalDaysInFirstWeek)) { return 1; } } return (int) ((abs - base) / 7) + 1; } private static long getWeekOfYearBase(int year, int firstDayOfWeek, int minimalDaysInFirstWeek) { long first = absoluteDayFromYear(year); int daysInFirstWeek = 8 - getDayOfWeekFromAbsolute(first, firstDayOfWeek); long base = first + daysInFirstWeek; if (daysInFirstWeek >= minimalDaysInFirstWeek) { base -= 7; } return base; } /** * Returns week year. * * @param dateValue * the date value * @param firstDayOfWeek * first day of week, Monday as 1, Sunday as 7 or 0 * @param minimalDaysInFirstWeek * minimal days in first week of year * @return week year * @see #getIsoWeekYear(long) */ public static int getWeekYear(long dateValue, int firstDayOfWeek, int minimalDaysInFirstWeek) { long abs = absoluteDayFromDateValue(dateValue); int year = yearFromDateValue(dateValue); long base = getWeekOfYearBase(year, firstDayOfWeek, minimalDaysInFirstWeek); if (abs - base < 0) { return year - 1; } else if (monthFromDateValue(dateValue) == 12 && 24 + minimalDaysInFirstWeek < dayFromDateValue(dateValue)) { if (abs >= getWeekOfYearBase(year + 1, firstDayOfWeek, minimalDaysInFirstWeek)) { return year + 1; } } return year; } /** * Returns number of days in month. * * @param year the year * @param month the month * @return number of days in the specified month */ public static int getDaysInMonth(int year, int month) { if (month != 2) { return NORMAL_DAYS_PER_MONTH[month]; } // All leap years divisible by 4 return (year & 3) == 0 // All such years before 1582 are Julian and leap && (year < 1582 // Otherwise check Gregorian conditions || year % 100 != 0 || year % 400 == 0) ? 29 : 28; } /** * Verify if the specified date is valid. * * @param year the year * @param month the month (January is 1) * @param day the day (1 is the first of the month) * @return true if it is valid */ public static boolean isValidDate(int year, int month, int day) { if (month < 1 || month > 12 || day < 1) { return false; } if (year == 1582 && month == 10) { // special case: days 1582-10-05 .. 1582-10-14 don't exist return day < 5 || (day > 14 && day <= 31); } return day <= getDaysInMonth(year, month); } /** * Convert an encoded date value to a java.util.Date, using the default * timezone. * * @param dateValue the date value * @return the date */ public static Date convertDateValueToDate(long dateValue) { long millis = getMillis(null, yearFromDateValue(dateValue), monthFromDateValue(dateValue), dayFromDateValue(dateValue), 0, 0, 0, 0); return new Date(millis); } /** * Convert an encoded date-time value to millis, using the supplied timezone. * * @param tz the timezone * @param dateValue the date value * @param ms milliseconds of day * @return the date */ public static long convertDateTimeValueToMillis(TimeZone tz, long dateValue, long ms) { long second = ms / 1000; ms -= second * 1000; int minute = (int) (second / 60); second -= minute * 60; int hour = minute / 60; minute -= hour * 60; return getMillis(tz, yearFromDateValue(dateValue), monthFromDateValue(dateValue), dayFromDateValue(dateValue), hour, minute, (int) second, (int) ms); } /** * Convert an encoded date value / time value to a timestamp, using the * default timezone. * * @param dateValue the date value * @param timeNanos the nanoseconds since midnight * @return the timestamp */ public static Timestamp convertDateValueToTimestamp(long dateValue, long timeNanos) { Timestamp ts = new Timestamp(convertDateTimeValueToMillis(null, dateValue, timeNanos / 1_000_000)); // This method expects the complete nanoseconds value including milliseconds ts.setNanos((int) (timeNanos % NANOS_PER_SECOND)); return ts; } /** * Convert an encoded date value / time value to a timestamp using the specified * time zone offset. * * @param dateValue the date value * @param timeNanos the nanoseconds since midnight * @param offsetMins time zone offset in minutes * @return the timestamp */ public static Timestamp convertTimestampTimeZoneToTimestamp(long dateValue, long timeNanos, short offsetMins) { Timestamp ts = new Timestamp(getMillis(dateValue, timeNanos, offsetMins)); ts.setNanos((int) (timeNanos % NANOS_PER_SECOND)); return ts; } /** * Convert a time value to a time, using the default timezone. * * @param nanosSinceMidnight the nanoseconds since midnight * @return the time */ public static Time convertNanoToTime(long nanosSinceMidnight) { long millis = nanosSinceMidnight / 1_000_000; long s = millis / 1_000; millis -= s * 1_000; long m = s / 60; s -= m * 60; long h = m / 60; m -= h * 60; long ms = getMillis(null, 1970, 1, 1, (int) (h % 24), (int) m, (int) s, (int) millis); return new Time(ms); } /** * Get the year from a date value. * * @param x the date value * @return the year */ public static int yearFromDateValue(long x) { return (int) (x >>> SHIFT_YEAR); } /** * Get the month from a date value. * * @param x the date value * @return the month (1..12) */ public static int monthFromDateValue(long x) { return (int) (x >>> SHIFT_MONTH) & 15; } /** * Get the day of month from a date value. * * @param x the date value * @return the day (1..31) */ public static int dayFromDateValue(long x) { return (int) (x & 31); } /** * Get the date value from a given date. * * @param year the year * @param month the month (1..12) * @param day the day (1..31) * @return the date value */ public static long dateValue(long year, int month, int day) { return (year << SHIFT_YEAR) | (month << SHIFT_MONTH) | day; } /** * Get the date value from a given denormalized date with possible out of range * values of month and/or day. Used after addition or subtraction month or years * to (from) it to get a valid date. * * @param year * the year * @param month * the month, if out of range month and year will be normalized * @param day * the day of the month, if out of range it will be saturated * @return the date value */ public static long dateValueFromDenormalizedDate(long year, long month, int day) { long mm1 = month - 1; long yd = mm1 / 12; if (mm1 < 0 && yd * 12 != mm1) { yd--; } int y = (int) (year + yd); int m = (int) (month - yd * 12); if (day < 1) { day = 1; } else { int max = getDaysInMonth(y, m); if (day > max) { day = max; } } return dateValue(y, m, day); } /** * Convert a local datetime in millis to an encoded date. * * @param ms the milliseconds * @return the date value */ public static long dateValueFromLocalMillis(long ms) { long absoluteDay = ms / MILLIS_PER_DAY; // Round toward negative infinity if (ms < 0 && (absoluteDay * MILLIS_PER_DAY != ms)) { absoluteDay--; } return dateValueFromAbsoluteDay(absoluteDay); } /** * Calculate the encoded date value from a given calendar. * * @param cal the calendar * @return the date value */ private static long dateValueFromCalendar(Calendar cal) { int year = cal.get(Calendar.YEAR); if (cal.get(Calendar.ERA) == GregorianCalendar.BC) { year = 1 - year; } int month = cal.get(Calendar.MONTH) + 1; int day = cal.get(Calendar.DAY_OF_MONTH); return ((long) year << SHIFT_YEAR) | (month << SHIFT_MONTH) | day; } /** * Convert a time in milliseconds in local time to the nanoseconds since midnight. * * @param ms the milliseconds * @return the nanoseconds */ public static long nanosFromLocalMillis(long ms) { long absoluteDay = ms / MILLIS_PER_DAY; // Round toward negative infinity if (ms < 0 && (absoluteDay * MILLIS_PER_DAY != ms)) { absoluteDay--; } return (ms - absoluteDay * MILLIS_PER_DAY) * 1_000_000; } /** * Convert a java.util.Calendar to nanoseconds since midnight. * * @param cal the calendar * @return the nanoseconds */ private static long nanosFromCalendar(Calendar cal) { int h = cal.get(Calendar.HOUR_OF_DAY); int m = cal.get(Calendar.MINUTE); int s = cal.get(Calendar.SECOND); int millis = cal.get(Calendar.MILLISECOND); return ((((((h * 60L) + m) * 60) + s) * 1000) + millis) * 1000000; } /** * Calculate the normalized timestamp. * * @param absoluteDay the absolute day * @param nanos the nanoseconds (may be negative or larger than one day) * @return the timestamp */ public static ValueTimestamp normalizeTimestamp(long absoluteDay, long nanos) { if (nanos > NANOS_PER_DAY || nanos < 0) { long d; if (nanos > NANOS_PER_DAY) { d = nanos / NANOS_PER_DAY; } else { d = (nanos - NANOS_PER_DAY + 1) / NANOS_PER_DAY; } nanos -= d * NANOS_PER_DAY; absoluteDay += d; } return ValueTimestamp.fromDateValueAndNanos( dateValueFromAbsoluteDay(absoluteDay), nanos); } /** * Converts local date value and nanoseconds to timestamp with time zone. * * @param dateValue * date value * @param timeNanos * nanoseconds since midnight * @return timestamp with time zone */ public static ValueTimestampTimeZone timestampTimeZoneFromLocalDateValueAndNanos(long dateValue, long timeNanos) { int timeZoneOffset = getTimeZoneOffsetMillis(null, dateValue, timeNanos); int offsetMins = timeZoneOffset / 60_000; int correction = timeZoneOffset % 60_000; if (correction != 0) { timeNanos -= correction; if (timeNanos < 0) { timeNanos += NANOS_PER_DAY; dateValue = decrementDateValue(dateValue); } else if (timeNanos >= NANOS_PER_DAY) { timeNanos -= NANOS_PER_DAY; dateValue = incrementDateValue(dateValue); } } return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, timeNanos, (short) offsetMins); } /** * Creates the instance of the {@link ValueTimestampTimeZone} from milliseconds. * * @param ms milliseconds since 1970-01-01 (UTC) * @return timestamp with time zone with specified value and current time zone */ public static ValueTimestampTimeZone timestampTimeZoneFromMillis(long ms) { int offset = getTimeZoneOffset(ms); ms += offset; long absoluteDay = ms / MILLIS_PER_DAY; // Round toward negative infinity if (ms < 0 && (absoluteDay * MILLIS_PER_DAY != ms)) { absoluteDay--; } return ValueTimestampTimeZone.fromDateValueAndNanos( dateValueFromAbsoluteDay(absoluteDay), (ms - absoluteDay * MILLIS_PER_DAY) * 1_000_000, (short) (offset / 60_000)); } /** * Calculate the absolute day for a January, 1 of the specified year. * * @param year * the year * @return the absolute day */ public static long absoluteDayFromYear(long year) { year--; long a = ((year * 1461L) >> 2) - 719_177; if (year < 1582) { // Julian calendar a += 13; } else if (year < 1900 || year > 2099) { // Gregorian calendar (slow mode) a += (year / 400) - (year / 100) + 15; } return a; } /** * Calculate the absolute day from an encoded date value. * * @param dateValue the date value * @return the absolute day */ public static long absoluteDayFromDateValue(long dateValue) { long y = yearFromDateValue(dateValue); int m = monthFromDateValue(dateValue); int d = dayFromDateValue(dateValue); if (m <= 2) { y--; m += 12; } long a = ((y * 1461L) >> 2) + DAYS_OFFSET[m - 3] + d - 719_484; if (y <= 1582 && ((y < 1582) || (m * 100 + d < 10_15))) { // Julian calendar (cutover at 1582-10-04 / 1582-10-15) a += 13; } else if (y < 1900 || y > 2099) { // Gregorian calendar (slow mode) a += (y / 400) - (y / 100) + 15; } return a; } /** * Calculate the absolute day from an encoded date value in proleptic Gregorian * calendar. * * @param dateValue the date value * @return the absolute day in proleptic Gregorian calendar */ public static long prolepticGregorianAbsoluteDayFromDateValue(long dateValue) { long y = yearFromDateValue(dateValue); int m = monthFromDateValue(dateValue); int d = dayFromDateValue(dateValue); if (m <= 2) { y--; m += 12; } long a = ((y * 1461L) >> 2) + DAYS_OFFSET[m - 3] + d - 719_484; if (y < 1900 || y > 2099) { // Slow mode a += (y / 400) - (y / 100) + 15; } return a; } /** * Calculate the encoded date value from an absolute day. * * @param absoluteDay the absolute day * @return the date value */ public static long dateValueFromAbsoluteDay(long absoluteDay) { long d = absoluteDay + 719_468; long y100, offset; if (d > 578_040) { // Gregorian calendar long y400 = d / 146_097; d -= y400 * 146_097; y100 = d / 36_524; d -= y100 * 36_524; offset = y400 * 400 + y100 * 100; } else { // Julian calendar y100 = 0; d += 292_200_000_002L; offset = -800_000_000; } long y4 = d / 1461; d -= y4 * 1461; long y = d / 365; d -= y * 365; if (d == 0 && (y == 4 || y100 == 4)) { y--; d += 365; } y += offset + y4 * 4; // month of a day int m = ((int) d * 2 + 1) * 5 / 306; d -= DAYS_OFFSET[m] - 1; if (m >= 10) { y++; m -= 12; } return dateValue(y, m + 3, (int) d); } /** * Return the next date value. * * @param dateValue * the date value * @return the next date value */ public static long incrementDateValue(long dateValue) { int year = yearFromDateValue(dateValue); if (year == 1582) { // Use slow way instead of rarely needed large custom code. return dateValueFromAbsoluteDay(absoluteDayFromDateValue(dateValue) + 1); } int day = dayFromDateValue(dateValue); if (day < 28) { return dateValue + 1; } int month = monthFromDateValue(dateValue); if (day < getDaysInMonth(year, month)) { return dateValue + 1; } if (month < 12) { month++; } else { month = 1; year++; } return dateValue(year, month, 1); } /** * Return the previous date value. * * @param dateValue * the date value * @return the previous date value */ public static long decrementDateValue(long dateValue) { int year = yearFromDateValue(dateValue); if (year == 1582) { // Use slow way instead of rarely needed large custom code. return dateValueFromAbsoluteDay(absoluteDayFromDateValue(dateValue) - 1); } if (dayFromDateValue(dateValue) > 1) { return dateValue - 1; } int month = monthFromDateValue(dateValue); if (month > 1) { month--; } else { month = 12; year--; } return dateValue(year, month, getDaysInMonth(year, month)); } /** * Append a date to the string builder. * * @param buff the target string builder * @param dateValue the date value */ public static void appendDate(StringBuilder buff, long dateValue) { int y = yearFromDateValue(dateValue); int m = monthFromDateValue(dateValue); int d = dayFromDateValue(dateValue); if (y > 0 && y < 10_000) { StringUtils.appendZeroPadded(buff, 4, y); } else { buff.append(y); } buff.append('-'); StringUtils.appendZeroPadded(buff, 2, m); buff.append('-'); StringUtils.appendZeroPadded(buff, 2, d); } /** * Append a time to the string builder. * * @param buff the target string builder * @param nanos the time in nanoseconds */ public static void appendTime(StringBuilder buff, long nanos) { if (nanos < 0) { buff.append('-'); nanos = -nanos; } /* * nanos now either in range from 0 to Long.MAX_VALUE or equals to * Long.MIN_VALUE. We need to divide nanos by 1000000 with unsigned division to * get correct result. The simplest way to do this with such constraints is to * divide -nanos by -1000000. */ long ms = -nanos / -1_000_000; nanos -= ms * 1_000_000; long s = ms / 1_000; ms -= s * 1_000; long m = s / 60; s -= m * 60; long h = m / 60; m -= h * 60; StringUtils.appendZeroPadded(buff, 2, h); buff.append(':'); StringUtils.appendZeroPadded(buff, 2, m); buff.append(':'); StringUtils.appendZeroPadded(buff, 2, s); if (ms > 0 || nanos > 0) { buff.append('.'); StringUtils.appendZeroPadded(buff, 3, ms); if (nanos > 0) { StringUtils.appendZeroPadded(buff, 6, nanos); } stripTrailingZeroes(buff); } } /** * Skip trailing zeroes. * * @param buff String buffer. */ static void stripTrailingZeroes(StringBuilder buff) { int i = buff.length() - 1; if (buff.charAt(i) == '0') { while (buff.charAt(--i) == '0') { // do nothing } buff.setLength(i + 1); } } /** * Append a time zone to the string builder. * * @param buff the target string builder * @param tz the time zone in minutes */ public static void appendTimeZone(StringBuilder buff, short tz) { if (tz < 0) { buff.append('-'); tz = (short) -tz; } else { buff.append('+'); } int hours = tz / 60; tz -= hours * 60; int mins = tz; StringUtils.appendZeroPadded(buff, 2, hours); if (mins != 0) { buff.append(':'); StringUtils.appendZeroPadded(buff, 2, mins); } } /** * Formats timestamp with time zone as string. * * @param buff the target string builder * @param dateValue the year-month-day bit field * @param timeNanos nanoseconds since midnight * @param timeZoneOffsetMins the time zone offset in minutes */ public static void appendTimestampTimeZone(StringBuilder buff, long dateValue, long timeNanos, short timeZoneOffsetMins) { appendDate(buff, dateValue); buff.append(' '); appendTime(buff, timeNanos); appendTimeZone(buff, timeZoneOffsetMins); } /** * Generates time zone name for the specified offset in minutes. * * @param offsetMins * offset in minutes * @return time zone name */ public static String timeZoneNameFromOffsetMins(int offsetMins) { if (offsetMins == 0) { return "UTC"; } StringBuilder b = new StringBuilder(9); b.append("GMT"); if (offsetMins < 0) { b.append('-'); offsetMins = -offsetMins; } else { b.append('+'); } StringUtils.appendZeroPadded(b, 2, offsetMins / 60); b.append(':'); StringUtils.appendZeroPadded(b, 2, offsetMins % 60); return b.toString(); } /** * Converts scale of nanoseconds. * * @param nanosOfDay nanoseconds of day * @param scale fractional seconds precision * @return scaled value */ public static long convertScale(long nanosOfDay, int scale) { if (scale >= 9) { return nanosOfDay; } int m = CONVERT_SCALE_TABLE[scale]; long mod = nanosOfDay % m; if (mod >= m >>> 1) { nanosOfDay += m; } return nanosOfDay - mod; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy