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-2022 H2 Group. Multiple-Licensed under the MPL 2.0, and the
 * EPL 1.0 (https://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.time.Instant;

import org.h2.api.ErrorCode;
import org.h2.engine.CastDataProvider;
import org.h2.message.DbException;
import org.h2.value.TypeInfo;
import org.h2.value.Value;
import org.h2.value.ValueDate;
import org.h2.value.ValueTime;
import org.h2.value.ValueTimeTimeZone;
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; /** * 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; /** * The offset of year bits in date values. */ public static final int SHIFT_YEAR = 9; /** * The offset of month bits in date values. */ public 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; /** * Minimum possible date value. */ public static final long MIN_DATE_VALUE = (-1_000_000_000L << SHIFT_YEAR) + (1 << SHIFT_MONTH) + 1; /** * Maximum possible date value. */ public static final long MAX_DATE_VALUE = (1_000_000_000L << SHIFT_YEAR) + (12 << SHIFT_MONTH) + 31; private static final int[] NORMAL_DAYS_PER_MONTH = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; /** * Multipliers for {@link #convertScale(long, int, long)} and * {@link #appendNanos(StringBuilder, int)}. */ private static final int[] FRACTIONAL_SECONDS_TABLE = { 1_000_000_000, 100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1 }; private static volatile TimeZoneProvider LOCAL; private DateTimeUtils() { // utility class } /** * Reset the cached calendar for default timezone, for example after * changing the default timezone. */ public static void resetCalendar() { LOCAL = null; } /** * Get the time zone provider for the default time zone. * * @return the time zone provider for the default time zone */ public static TimeZoneProvider getTimeZone() { TimeZoneProvider local = LOCAL; if (local == null) { LOCAL = local = TimeZoneProvider.getDefault(); } return local; } /** * Returns current timestamp. * * @param timeZone * the time zone * @return current timestamp */ public static ValueTimestampTimeZone currentTimestamp(TimeZoneProvider timeZone) { return currentTimestamp(timeZone, Instant.now()); } /** * Returns current timestamp using the specified instant for its value. * * @param timeZone * the time zone * @param now * timestamp source, must be greater than or equal to * 1970-01-01T00:00:00Z * @return current timestamp */ public static ValueTimestampTimeZone currentTimestamp(TimeZoneProvider timeZone, Instant now) { /* * This code intentionally does not support properly dates before UNIX * epoch because such support is not required for current dates. */ long second = now.getEpochSecond(); int offset = timeZone.getTimeZoneOffsetUTC(second); second += offset; return ValueTimestampTimeZone.fromDateValueAndNanos(dateValueFromAbsoluteDay(second / SECONDS_PER_DAY), second % SECONDS_PER_DAY * 1_000_000_000 + now.getNano(), offset); } /** * 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; } /** * Parses timestamp value from the specified string. * * @param s * string to parse * @param provider * the cast information provider, may be {@code null} for * Standard-compliant literals * @param withTimeZone * if {@code true} return {@link ValueTimestampTimeZone} instead of * {@link ValueTimestamp} * @return parsed timestamp */ public static Value parseTimestamp(String s, CastDataProvider provider, boolean withTimeZone) { int dateEnd = s.indexOf(' '); if (dateEnd < 0) { // ISO 8601 compatibility dateEnd = s.indexOf('T'); if (dateEnd < 0 && provider != null && provider.getMode().allowDB2TimestampFormat) { // DB2 also allows dash between date and time dateEnd = s.indexOf('-', s.indexOf('-', s.indexOf('-') + 1) + 1); } } int timeStart; if (dateEnd < 0) { dateEnd = s.length(); timeStart = -1; } else { timeStart = dateEnd + 1; } long dateValue = parseDateValue(s, 0, dateEnd); long nanos; TimeZoneProvider tz = null; if (timeStart < 0) { nanos = 0; } else { dateEnd++; int timeEnd; if (s.endsWith("Z")) { tz = TimeZoneProvider.UTC; timeEnd = s.length() - 1; } else { int timeZoneStart = s.indexOf('+', dateEnd); if (timeZoneStart < 0) { timeZoneStart = s.indexOf('-', dateEnd); } if (timeZoneStart >= 0) { // Allow [timeZoneName] part after time zone offset int offsetEnd = s.indexOf('[', timeZoneStart + 1); if (offsetEnd < 0) { offsetEnd = s.length(); } tz = TimeZoneProvider.ofId(s.substring(timeZoneStart, offsetEnd)); if (s.charAt(timeZoneStart - 1) == ' ') { timeZoneStart--; } timeEnd = timeZoneStart; } else { timeZoneStart = s.indexOf(' ', dateEnd); if (timeZoneStart > 0) { tz = TimeZoneProvider.ofId(s.substring(timeZoneStart + 1)); timeEnd = timeZoneStart; } else { timeEnd = s.length(); } } } nanos = parseTimeNanos(s, dateEnd, timeEnd); } if (withTimeZone) { int tzSeconds; if (tz == null) { tz = provider != null ? provider.currentTimeZone() : DateTimeUtils.getTimeZone(); } if (tz != TimeZoneProvider.UTC) { tzSeconds = tz.getTimeZoneOffsetUTC(tz.getEpochSecondsFromLocal(dateValue, nanos)); } else { tzSeconds = 0; } return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, nanos, tzSeconds); } else if (tz != null) { long seconds = tz.getEpochSecondsFromLocal(dateValue, nanos); seconds += (provider != null ? provider.currentTimeZone() : DateTimeUtils.getTimeZone()) .getTimeZoneOffsetUTC(seconds); dateValue = dateValueFromLocalSeconds(seconds); nanos = nanos % 1_000_000_000 + nanosFromLocalSeconds(seconds); } return ValueTimestamp.fromDateValueAndNanos(dateValue, nanos); } /** * Parses TIME WITH TIME ZONE value from the specified string. * * @param s * string to parse * @param provider * the cast information provider, or {@code null} * @return parsed time with time zone */ public static ValueTimeTimeZone parseTimeWithTimeZone(String s, CastDataProvider provider) { int timeEnd; TimeZoneProvider tz; if (s.endsWith("Z")) { tz = TimeZoneProvider.UTC; timeEnd = s.length() - 1; } else { int timeZoneStart = s.indexOf('+', 1); if (timeZoneStart < 0) { timeZoneStart = s.indexOf('-', 1); } if (timeZoneStart >= 0) { tz = TimeZoneProvider.ofId(s.substring(timeZoneStart)); if (s.charAt(timeZoneStart - 1) == ' ') { timeZoneStart--; } timeEnd = timeZoneStart; } else { timeZoneStart = s.indexOf(' ', 1); if (timeZoneStart > 0) { tz = TimeZoneProvider.ofId(s.substring(timeZoneStart + 1)); timeEnd = timeZoneStart; } else { throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2, "TIME WITH TIME ZONE", s); } } if (!tz.hasFixedOffset()) { throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2, "TIME WITH TIME ZONE", s); } } return ValueTimeTimeZone.fromNanos(parseTimeNanos(s, 0, timeEnd), tz.getTimeZoneOffsetUTC(0L)); } /** * Calculates the seconds since epoch for the specified date value, * nanoseconds since midnight, and time zone offset. * @param dateValue * date value * @param timeNanos * nanoseconds since midnight * @param offsetSeconds * time zone offset in seconds * @return seconds since epoch in UTC */ public static long getEpochSeconds(long dateValue, long timeNanos, int offsetSeconds) { return absoluteDayFromDateValue(dateValue) * SECONDS_PER_DAY + timeNanos / NANOS_PER_SECOND - offsetSeconds; } /** * Extracts date value and nanos of day from the specified value. * * @param value * value to extract fields from * @param provider * the cast information provider * @return array with date value and nanos of day */ public static long[] dateAndTimeFromValue(Value value, CastDataProvider provider) { 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 if (value instanceof ValueTimeTimeZone) { timeNanos = ((ValueTimeTimeZone) value).getNanos(); } else { ValueTimestamp v = (ValueTimestamp) value.convertTo(TypeInfo.TYPE_TIMESTAMP, provider); 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 or ValueTimeTimeZone, 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 * @return new value with specified date value and nanos of day */ public static Value dateTimeToValue(Value original, long dateValue, long timeNanos) { switch (original.getValueType()) { case Value.DATE: return ValueDate.fromDateValue(dateValue); case Value.TIME: return ValueTime.fromNanos(timeNanos); case Value.TIME_TZ: return ValueTimeTimeZone.fromNanos(timeNanos, ((ValueTimeTimeZone) original).getTimeZoneOffsetSeconds()); case Value.TIMESTAMP: default: return ValueTimestamp.fromDateValueAndNanos(dateValue, timeNanos); case Value.TIMESTAMP_TZ: return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, timeNanos, ((ValueTimestampTimeZone) original).getTimeZoneOffsetSeconds()); } } /** * 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) { int m = monthFromDateValue(dateValue); int a = (367 * m - 362) / 12 + dayFromDateValue(dateValue); if (m > 2) { a--; long y = yearFromDateValue(dateValue); if ((y & 3) != 0 || (y % 100 == 0 && y % 400 != 0)) { a--; } } return a; } /** * 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 = getWeekYearAbsoluteStart(year, firstDayOfWeek, minimalDaysInFirstWeek); if (abs - base < 0) { base = getWeekYearAbsoluteStart(year - 1, firstDayOfWeek, minimalDaysInFirstWeek); } else if (monthFromDateValue(dateValue) == 12 && 24 + minimalDaysInFirstWeek < dayFromDateValue(dateValue)) { if (abs >= getWeekYearAbsoluteStart(year + 1, firstDayOfWeek, minimalDaysInFirstWeek)) { return 1; } } return (int) ((abs - base) / 7) + 1; } /** * Get absolute day of the first day in the week year. * * @param weekYear * the week year * @param firstDayOfWeek * first day of week, Monday as 1, Sunday as 7 or 0 * @param minimalDaysInFirstWeek * minimal days in first week of year * @return absolute day of the first day in the week year */ public static long getWeekYearAbsoluteStart(int weekYear, int firstDayOfWeek, int minimalDaysInFirstWeek) { long first = absoluteDayFromYear(weekYear); 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 = getWeekYearAbsoluteStart(year, firstDayOfWeek, minimalDaysInFirstWeek); if (abs < base) { return year - 1; } else if (monthFromDateValue(dateValue) == 12 && 24 + minimalDaysInFirstWeek < dayFromDateValue(dateValue)) { if (abs >= getWeekYearAbsoluteStart(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]; } return (year & 3) == 0 && (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) { return month >= 1 && month <= 12 && day >= 1 && day <= getDaysInMonth(year, month); } /** * 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 seconds to an encoded date. * * @param localSeconds the seconds since 1970-01-01 * @return the date value */ public static long dateValueFromLocalSeconds(long localSeconds) { long absoluteDay = localSeconds / SECONDS_PER_DAY; // Round toward negative infinity if (localSeconds < 0 && (absoluteDay * SECONDS_PER_DAY != localSeconds)) { absoluteDay--; } return dateValueFromAbsoluteDay(absoluteDay); } /** * Convert a time in seconds in local time to the nanoseconds since midnight. * * @param localSeconds the seconds since 1970-01-01 * @return the nanoseconds */ public static long nanosFromLocalSeconds(long localSeconds) { localSeconds %= SECONDS_PER_DAY; if (localSeconds < 0) { localSeconds += SECONDS_PER_DAY; } return localSeconds * NANOS_PER_SECOND; } /** * Calculate the normalized nanos of day. * * @param nanos the nanoseconds (may be negative or larger than one day) * @return the nanos of day within a day */ public static long normalizeNanosOfDay(long nanos) { nanos %= NANOS_PER_DAY; if (nanos < 0) { nanos += NANOS_PER_DAY; } return nanos; } /** * 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) { long a = 365 * year - 719_528; if (year >= 0) { a += (year + 3) / 4 - (year + 99) / 100 + (year + 399) / 400; } else { a -= year / -4 - year / -100 + year / -400; } 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) { return absoluteDay(yearFromDateValue(dateValue), monthFromDateValue(dateValue), dayFromDateValue(dateValue)); } /** * Calculate the absolute day. * * @param y year * @param m month * @param d day * @return the absolute day */ static long absoluteDay(long y, int m, int d) { long a = absoluteDayFromYear(y) + (367 * m - 362) / 12 + d - 1; if (m > 2) { a--; if ((y & 3) != 0 || (y % 100 == 0 && y % 400 != 0)) { a--; } } 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 a = 0; if (d < 0) { a = (d + 1) / 146_097 - 1; d -= a * 146_097; a *= 400; } long y = (400 * d + 591) / 146_097; int day = (int) (d - (365 * y + y / 4 - y / 100 + y / 400)); if (day < 0) { y--; day = (int) (d - (365 * y + y / 4 - y / 100 + y / 400)); } y += a; int m = (day * 5 + 2) / 153; day -= (m * 306 + 5) / 10 - 1; if (m >= 10) { y++; m -= 12; } return dateValue(y, m + 3, day); } /** * Return the next date value. * * @param dateValue * the date value * @return the next date value */ public static long incrementDateValue(long dateValue) { int day = dayFromDateValue(dateValue); if (day < 28) { return dateValue + 1; } int year = yearFromDateValue(dateValue); 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) { if (dayFromDateValue(dateValue) > 1) { return dateValue - 1; } int year = yearFromDateValue(dateValue); 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 builder the target string builder * @param dateValue the date value * @return the specified string builder */ public static StringBuilder appendDate(StringBuilder builder, long dateValue) { int y = yearFromDateValue(dateValue); if (y < 1_000 && y > -1_000) { if (y < 0) { builder.append('-'); y = -y; } StringUtils.appendZeroPadded(builder, 4, y); } else { builder.append(y); } StringUtils.appendTwoDigits(builder.append('-'), monthFromDateValue(dateValue)).append('-'); return StringUtils.appendTwoDigits(builder, dayFromDateValue(dateValue)); } /** * Append a time to the string builder. * * @param builder the target string builder * @param nanos the time in nanoseconds * @return the specified string builder */ public static StringBuilder appendTime(StringBuilder builder, long nanos) { if (nanos < 0) { builder.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 1,000,000,000 with * unsigned division to get correct result. The simplest way to do this * with such constraints is to divide -nanos by -1,000,000,000. */ long s = -nanos / -1_000_000_000; nanos -= s * 1_000_000_000; int m = (int) (s / 60); s -= m * 60; int h = m / 60; m -= h * 60; StringUtils.appendTwoDigits(builder, h).append(':'); StringUtils.appendTwoDigits(builder, m).append(':'); StringUtils.appendTwoDigits(builder, (int) s); return appendNanos(builder, (int) nanos); } /** * Append nanoseconds of time, if any. * * @param builder string builder to append to * @param nanos nanoseconds of second * @return the specified string builder */ static StringBuilder appendNanos(StringBuilder builder, int nanos) { if (nanos > 0) { builder.append('.'); for (int i = 1; nanos < FRACTIONAL_SECONDS_TABLE[i]; i++) { builder.append('0'); } if (nanos % 1_000 == 0) { nanos /= 1_000; if (nanos % 1_000 == 0) { nanos /= 1_000; } } if (nanos % 10 == 0) { nanos /= 10; if (nanos % 10 == 0) { nanos /= 10; } } builder.append(nanos); } return builder; } /** * Append a time zone to the string builder. * * @param builder the target string builder * @param tz the time zone offset in seconds * @return the specified string builder */ public static StringBuilder appendTimeZone(StringBuilder builder, int tz) { if (tz < 0) { builder.append('-'); tz = -tz; } else { builder.append('+'); } int rem = tz / 3_600; StringUtils.appendTwoDigits(builder, rem); tz -= rem * 3_600; if (tz != 0) { rem = tz / 60; StringUtils.appendTwoDigits(builder.append(':'), rem); tz -= rem * 60; if (tz != 0) { StringUtils.appendTwoDigits(builder.append(':'), tz); } } return builder; } /** * Generates time zone name for the specified offset in seconds. * * @param offsetSeconds * time zone offset in seconds * @return time zone name */ public static String timeZoneNameFromOffsetSeconds(int offsetSeconds) { if (offsetSeconds == 0) { return "UTC"; } StringBuilder b = new StringBuilder(12); b.append("GMT"); if (offsetSeconds < 0) { b.append('-'); offsetSeconds = -offsetSeconds; } else { b.append('+'); } StringUtils.appendTwoDigits(b, offsetSeconds / 3_600).append(':'); offsetSeconds %= 3_600; StringUtils.appendTwoDigits(b, offsetSeconds / 60); offsetSeconds %= 60; if (offsetSeconds != 0) { b.append(':'); StringUtils.appendTwoDigits(b, offsetSeconds); } return b.toString(); } /** * Converts scale of nanoseconds. * * @param nanosOfDay nanoseconds of day * @param scale fractional seconds precision * @param range the allowed range of values (0..range-1) * @return scaled value */ public static long convertScale(long nanosOfDay, int scale, long range) { if (scale >= 9) { return nanosOfDay; } int m = FRACTIONAL_SECONDS_TABLE[scale]; long mod = nanosOfDay % m; if (mod >= m >>> 1) { nanosOfDay += m; } long r = nanosOfDay - mod; if (r >= range) { r = range - m; } return r; } /** * Moves timestamp with time zone to a new time zone. * * @param dateValue the date value * @param timeNanos the nanoseconds since midnight * @param oldOffset old offset * @param newOffset new offset * @return timestamp with time zone with new offset */ public static ValueTimestampTimeZone timestampTimeZoneAtOffset(long dateValue, long timeNanos, int oldOffset, int newOffset) { timeNanos += (newOffset - oldOffset) * DateTimeUtils.NANOS_PER_SECOND; // Value can be 18+18 hours before or after the limit if (timeNanos < 0) { timeNanos += DateTimeUtils.NANOS_PER_DAY; dateValue = DateTimeUtils.decrementDateValue(dateValue); if (timeNanos < 0) { timeNanos += DateTimeUtils.NANOS_PER_DAY; dateValue = DateTimeUtils.decrementDateValue(dateValue); } } else if (timeNanos >= DateTimeUtils.NANOS_PER_DAY) { timeNanos -= DateTimeUtils.NANOS_PER_DAY; dateValue = DateTimeUtils.incrementDateValue(dateValue); if (timeNanos >= DateTimeUtils.NANOS_PER_DAY) { timeNanos -= DateTimeUtils.NANOS_PER_DAY; dateValue = DateTimeUtils.incrementDateValue(dateValue); } } return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, timeNanos, newOffset); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy