/**
* Copyright 2011-2017 Asakusa Framework Team.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.asakusafw.directio.hive.util;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.BitSet;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import com.asakusafw.runtime.value.Date;
import com.asakusafw.runtime.value.DateTime;
import com.asakusafw.runtime.value.DateUtil;
/**
* date and date-time utilities.
* @since 0.7.0
* @version 0.7.4
*/
public final class TemporalUtil {
private static final ThreadLocal<DateFormat> DATE_FORMAT_CACHE = ThreadLocal
.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd")); //$NON-NLS-1$;
private static final ThreadLocal<DateFormat> TIMESTAMP_FORMAT_CACHE = ThreadLocal
.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); //$NON-NLS-1$;
private static final ThreadLocal<StringBuilder> STRING_BUILDER_CACHE = ThreadLocal
.withInitial(() -> new StringBuilder(32));
private static final char DATE_SEGMENT_SEPARATOR = '-';
private static final char DATE_TIME_SEPARATOR = ' ';
private static final char TIME_SEGMENT_SEPARATOR = ':';
private static final BitSet SEPARATOR_CHAR = new BitSet();
static {
SEPARATOR_CHAR.set(' ');
SEPARATOR_CHAR.set('-');
SEPARATOR_CHAR.set('/');
SEPARATOR_CHAR.set(':');
SEPARATOR_CHAR.set('\'');
SEPARATOR_CHAR.set('.');
}
private static final int COL_YEAR_BEGIN = 0;
private static final int COL_YEAR_END = COL_YEAR_BEGIN + 4;
private static final int COL_MONTH_BEGIN = COL_YEAR_END + 1;
private static final int COL_MONTH_END = COL_MONTH_BEGIN + 2;
private static final int COL_DAY_BEGIN = COL_MONTH_END + 1;
private static final int COL_DAY_END = COL_DAY_BEGIN + 2;
private static final int COL_HOUR_BEGIN = COL_DAY_END + 1;
private static final int COL_HOUR_END = COL_HOUR_BEGIN + 2;
private static final int COL_MINUTE_BEGIN = COL_HOUR_END + 1;
private static final int COL_MINUTE_END = COL_MINUTE_BEGIN + 2;
private static final int COL_SECOND_BEGIN = COL_MINUTE_END + 1;
private static final int COL_SECOND_END = COL_SECOND_BEGIN + 2;
// FIXME always use local time-zone
private static final long LOCAL_TIMEZONE_OFFSET =
TimeUnit.MILLISECONDS.toSeconds(TimeZone.getDefault().getRawOffset());
private static final long GREGORIAN_EPOCH = new DateTime(1582, 10, 15, 0, 0, 0).getElapsedSeconds();
private static final long GREGORIAN_EPOCH_JDN = 2299161;
private static final long JULIAN_OFFSET = TimeUnit.DAYS.toSeconds(GREGORIAN_EPOCH_JDN) - GREGORIAN_EPOCH;
/**
* The day number of the epoch date.
* @since 0.7.4
*/
public static final int DATE_EPOCH_OFFSET = DateUtil.getDayFromDate(1970, 1, 1);
/**
* Parses a {@code date} value.
* @param value the date value
* @return the days for {@link Date} object
*/
public static int parseDate(String value) {
int length = value.length();
if (length >= COL_DAY_END
&& isSeparator(value, COL_YEAR_END)
&& isSeparator(value, COL_MONTH_END)
&& (length == COL_DAY_END || isSeparator(value, COL_DAY_END))) {
int year = parse(value, COL_YEAR_BEGIN, COL_YEAR_END);
int month = parse(value, COL_MONTH_BEGIN, COL_MONTH_END);
int day = parse(value, COL_DAY_BEGIN, COL_DAY_END);
if (year > 0 && month > 0 && day > 0) {
return DateUtil.getDayFromDate(year, month, day);
}
}
try {
java.util.Date date = DATE_FORMAT_CACHE.get().parse(value);
return DateUtil.getDayFromDate(date);
} catch (ParseException e) {
return -1;
}
}
/**
* Parses a {@code timestamp} value.
* @param value the timestamp value
* @return the seconds for {@link DateTime} object
*/
public static long parseTimestamp(String value) {
int length = value.length();
if (length >= COL_SECOND_END
&& isSeparator(value, COL_YEAR_END)
&& isSeparator(value, COL_MONTH_END)
&& isSeparator(value, COL_DAY_END)
&& isSeparator(value, COL_HOUR_END)
&& isSeparator(value, COL_MINUTE_END)
&& (length == COL_SECOND_END || isSeparator(value, COL_SECOND_END))) {
int year = parse(value, COL_YEAR_BEGIN, COL_YEAR_END);
int month = parse(value, COL_MONTH_BEGIN, COL_MONTH_END);
int day = parse(value, COL_DAY_BEGIN, COL_DAY_END);
int hour = parse(value, COL_HOUR_BEGIN, COL_HOUR_END);
int minute = parse(value, COL_MINUTE_BEGIN, COL_MINUTE_END);
int second = parse(value, COL_SECOND_BEGIN, COL_SECOND_END);
if (year > 0 && month > 0 && day > 0
&& hour >= 0 && minute >= 0 && second >= 0) {
long result = DateUtil.getDayFromDate(year, month, day) * 86400L;
result += DateUtil.getSecondFromTime(hour, minute, second);
return result;
}
}
try {
java.util.Date date = TIMESTAMP_FORMAT_CACHE.get().parse(value);
return DateUtil.getSecondFromDate(date);
} catch (ParseException e) {
return -1;
}
}
private static boolean isSeparator(String string, int column) {
char c = string.charAt(column);
return SEPARATOR_CHAR.get(c);
}
private static int parse(String string, int begin, int end) {
int result = 0;
for (int i = begin; i < end; i++) {
char c = string.charAt(i);
if (c < '0' || '9' < c) {
return -1;
}
result = (result * 10) + (c - '0');
}
return result;
}
/**
* Returns a string representation of {@link Date}.
* @param elapsedDays the elapsed days from 0001/01/01
* @return string representation
*/
public static String toDateString(int elapsedDays) {
StringBuilder buf = STRING_BUILDER_CACHE.get();
buf.setLength(0);
DateUtil.toDateString(elapsedDays, DATE_SEGMENT_SEPARATOR, buf);
return buf.toString();
}
/**
* Returns a string representation of {@link DateTime}.
* @param elapsedSeconds the elapsed seconds from 0001/01/01 00:00:00
* @return string representation
*/
public static String toTimestampString(long elapsedSeconds) {
StringBuilder buf = STRING_BUILDER_CACHE.get();
buf.setLength(0);
DateUtil.toDateTimeString(
elapsedSeconds,
DATE_SEGMENT_SEPARATOR, DATE_TIME_SEPARATOR, TIME_SEGMENT_SEPARATOR,
buf);
return buf.toString();
}
/**
* Returns the Julian day number of the date.
* @param date the date
* @return the Julian day number
* @since 0.7.2
*/
public static int getJulianDayNumber(Date date) {
long julianSeconds = toJulianSecond(TimeUnit.DAYS.toSeconds(date.getElapsedDays()));
return getJulianDayNumber(julianSeconds);
}
/**
* Returns the nano time of the julian day.
* @param date the day
* @return the nano time of julian day
* @since 0.7.2
*/
public static long getTimeOfDayNanos(Date date) {
long julianSeconds = toJulianSecond(TimeUnit.DAYS.toSeconds(date.getElapsedDays()));
return TimeUnit.SECONDS.toNanos(DateUtil.getSecondOfDay(julianSeconds));
}
/**
* Returns the Julian day number of the date time.
* @param dateTime the date time
* @return the Julian day number
* @since 0.7.2
*/
public static int getJulianDayNumber(DateTime dateTime) {
long julianSeconds = toJulianSecond(dateTime.getElapsedSeconds());
return getJulianDayNumber(julianSeconds);
}
/**
* Returns the nano time of the julian day.
* @param dateTime the date time
* @return the nano time of julian day
* @since 0.7.2
*/
public static long getTimeOfDayNanos(DateTime dateTime) {
long julianSeconds = toJulianSecond(dateTime.getElapsedSeconds());
return TimeUnit.SECONDS.toNanos(DateUtil.getSecondOfDay(julianSeconds));
}
/**
* Returns the elapsed days from Julian day.
* @param julianDayNumber the Julian day
* @param nanoTime time of day in nanos
* @return the elapsed days
* @since 0.7.2
*/
public static long toElapsedSeconds(int julianDayNumber, long nanoTime) {
long julianSeconds = TimeUnit.DAYS.toSeconds(julianDayNumber) + TimeUnit.NANOSECONDS.toSeconds(nanoTime);
return fromJulianSecond(julianSeconds);
}
private static long toJulianSecond(long seconds) {
if (seconds < GREGORIAN_EPOCH) {
throw new UnsupportedOperationException(MessageFormat.format(
Messages.getString("TemporalUtil.errorBeforeEpochTimestamp"), //$NON-NLS-1$
toTimestampString(seconds),
toTimestampString(GREGORIAN_EPOCH)));
}
return seconds + JULIAN_OFFSET - LOCAL_TIMEZONE_OFFSET;
}
private static long fromJulianSecond(long seconds) {
return seconds - JULIAN_OFFSET + LOCAL_TIMEZONE_OFFSET;
}
private static int getJulianDayNumber(long julianSecond) {
return (int) TimeUnit.SECONDS.toDays(julianSecond);
}
private TemporalUtil() {
return;
}
/**
* Returns the day number since the epoch date.
* @param date the target date
* @return the day number
* @since 0.7.4
* @see #toElapsedDays(int)
*/
public static int getDaysSinceEpoch(Date date) {
return date.getElapsedDays() - DATE_EPOCH_OFFSET;
}
/**
* Returns the elapsed days from the epoch date.
* @param daysSinceEpoch the day number since epoch date
* @return the elapsed days
* @since 0.7.4
* @see #getDaysSinceEpoch(Date)
*/
public static int toElapsedDays(int daysSinceEpoch) {
return daysSinceEpoch + DATE_EPOCH_OFFSET;
}
}