blob: 9e37eca085bc7183bf9713361279609d7bdcc1d5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.phoenix.util;
import static org.apache.phoenix.query.QueryConstants.MAX_ALLOWED_NANOS;
import static org.apache.phoenix.query.QueryConstants.MILLIS_TO_NANOS_CONVERTOR;
import java.math.BigDecimal;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.Format;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.TimeZone;
import org.apache.commons.lang.time.FastDateFormat;
import org.apache.phoenix.schema.IllegalDataException;
import org.apache.phoenix.schema.TypeMismatchException;
import org.apache.phoenix.schema.types.PDataType;
import org.apache.phoenix.schema.types.PDataType.PDataCodec;
import org.apache.phoenix.schema.types.PDate;
import org.apache.phoenix.schema.types.PTimestamp;
import org.apache.phoenix.schema.types.PUnsignedDate;
import org.apache.phoenix.schema.types.PUnsignedTimestamp;
import org.joda.time.DateTimeZone;
import org.joda.time.chrono.ISOChronology;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.DateTimeFormatterBuilder;
import org.joda.time.format.ISODateTimeFormat;
import com.google.common.collect.Lists;
import com.sun.istack.NotNull;
@SuppressWarnings({ "serial", "deprecation" })
public class DateUtil {
public static final String DEFAULT_TIME_ZONE_ID = "GMT";
public static final String LOCAL_TIME_ZONE_ID = "LOCAL";
private static final TimeZone DEFAULT_TIME_ZONE = TimeZone.getTimeZone(DEFAULT_TIME_ZONE_ID);
public static final String DEFAULT_MS_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS";
public static final Format DEFAULT_MS_DATE_FORMATTER = FastDateFormat.getInstance(
DEFAULT_MS_DATE_FORMAT, TimeZone.getTimeZone(DEFAULT_TIME_ZONE_ID));
public static final String DEFAULT_DATE_FORMAT = DEFAULT_MS_DATE_FORMAT;
public static final Format DEFAULT_DATE_FORMATTER = DEFAULT_MS_DATE_FORMATTER;
public static final String DEFAULT_TIME_FORMAT = DEFAULT_MS_DATE_FORMAT;
public static final Format DEFAULT_TIME_FORMATTER = DEFAULT_MS_DATE_FORMATTER;
public static final String DEFAULT_TIMESTAMP_FORMAT = DEFAULT_MS_DATE_FORMAT;
public static final Format DEFAULT_TIMESTAMP_FORMATTER = DEFAULT_MS_DATE_FORMATTER;
private static final DateTimeFormatter ISO_DATE_TIME_FORMATTER = new DateTimeFormatterBuilder()
.append(ISODateTimeFormat.dateParser())
.appendOptional(new DateTimeFormatterBuilder()
.appendLiteral(' ').toParser())
.appendOptional(new DateTimeFormatterBuilder()
.append(ISODateTimeFormat.timeParser()).toParser())
.toFormatter().withChronology(ISOChronology.getInstanceUTC());
private DateUtil() {
}
@NotNull
public static PDataCodec getCodecFor(PDataType type) {
PDataCodec codec = type.getCodec();
if (codec != null) {
return codec;
}
if (type == PTimestamp.INSTANCE) {
return PDate.INSTANCE.getCodec();
} else if (type == PUnsignedTimestamp.INSTANCE) {
return PUnsignedDate.INSTANCE.getCodec();
} else {
throw new RuntimeException(TypeMismatchException.newException(PTimestamp.INSTANCE, type));
}
}
public static TimeZone getTimeZone(String timeZoneId) {
TimeZone parserTimeZone;
if (timeZoneId == null) {
parserTimeZone = DateUtil.DEFAULT_TIME_ZONE;
} else if (LOCAL_TIME_ZONE_ID.equalsIgnoreCase(timeZoneId)) {
parserTimeZone = TimeZone.getDefault();
} else {
parserTimeZone = TimeZone.getTimeZone(timeZoneId);
}
return parserTimeZone;
}
private static String[] defaultPattern;
static {
int maxOrdinal = Integer.MIN_VALUE;
List<PDataType> timeDataTypes = Lists.newArrayListWithExpectedSize(6);
for (PDataType type : PDataType.values()) {
if (java.util.Date.class.isAssignableFrom(type.getJavaClass())) {
timeDataTypes.add(type);
if (type.ordinal() > maxOrdinal) {
maxOrdinal = type.ordinal();
}
}
}
defaultPattern = new String[maxOrdinal+1];
for (PDataType type : timeDataTypes) {
switch (type.getResultSetSqlType()) {
case Types.TIMESTAMP:
defaultPattern[type.ordinal()] = DateUtil.DEFAULT_TIMESTAMP_FORMAT;
break;
case Types.TIME:
defaultPattern[type.ordinal()] = DateUtil.DEFAULT_TIME_FORMAT;
break;
case Types.DATE:
defaultPattern[type.ordinal()] = DateUtil.DEFAULT_DATE_FORMAT;
break;
}
}
}
private static String getDefaultFormat(PDataType type) {
int ordinal = type.ordinal();
if (ordinal >= 0 || ordinal < defaultPattern.length) {
String format = defaultPattern[ordinal];
if (format != null) {
return format;
}
}
throw new IllegalArgumentException("Expected a date/time type, but got " + type);
}
public static DateTimeParser getDateTimeParser(String pattern, PDataType pDataType, String timeZoneId) {
TimeZone timeZone = getTimeZone(timeZoneId);
String defaultPattern = getDefaultFormat(pDataType);
if (pattern == null || pattern.length() == 0) {
pattern = defaultPattern;
}
if(defaultPattern.equals(pattern)) {
return ISODateFormatParserFactory.getParser(timeZone);
} else {
return new SimpleDateFormatParser(pattern, timeZone);
}
}
public static DateTimeParser getDateTimeParser(String pattern, PDataType pDataType) {
return getDateTimeParser(pattern, pDataType, null);
}
public static Format getDateFormatter(String pattern) {
return DateUtil.DEFAULT_DATE_FORMAT.equals(pattern)
? DateUtil.DEFAULT_DATE_FORMATTER
: FastDateFormat.getInstance(pattern, DateUtil.DEFAULT_TIME_ZONE);
}
public static Format getTimeFormatter(String pattern) {
return DateUtil.DEFAULT_TIME_FORMAT.equals(pattern)
? DateUtil.DEFAULT_TIME_FORMATTER
: FastDateFormat.getInstance(pattern, DateUtil.DEFAULT_TIME_ZONE);
}
public static Format getTimestampFormatter(String pattern) {
return DateUtil.DEFAULT_TIMESTAMP_FORMAT.equals(pattern)
? DateUtil.DEFAULT_TIMESTAMP_FORMATTER
: FastDateFormat.getInstance(pattern, DateUtil.DEFAULT_TIME_ZONE);
}
private static long parseDateTime(String dateTimeValue) {
return ISODateFormatParser.getInstance().parseDateTime(dateTimeValue);
}
public static Date parseDate(String dateValue) {
return new Date(parseDateTime(dateValue));
}
public static Time parseTime(String timeValue) {
return new Time(parseDateTime(timeValue));
}
public static Timestamp parseTimestamp(String timestampValue) {
Timestamp timestamp = new Timestamp(parseDateTime(timestampValue));
int period = timestampValue.indexOf('.');
if (period > 0) {
String nanosStr = timestampValue.substring(period + 1);
if (nanosStr.length() > 9)
throw new IllegalDataException("nanos > 999999999 or < 0");
if(nanosStr.length() > 3 ) {
int nanos = Integer.parseInt(nanosStr);
for (int i = 0; i < 9 - nanosStr.length(); i++) {
nanos *= 10;
}
timestamp.setNanos(nanos);
}
}
return timestamp;
}
/**
* Utility function to work around the weirdness of the {@link Timestamp} constructor.
* This method takes the milli-seconds that spills over to the nanos part as part of
* constructing the {@link Timestamp} object.
* If we just set the nanos part of timestamp to the nanos passed in param, we
* end up losing the sub-second part of timestamp.
*/
public static Timestamp getTimestamp(long millis, int nanos) {
if (nanos > MAX_ALLOWED_NANOS || nanos < 0) {
throw new IllegalArgumentException("nanos > " + MAX_ALLOWED_NANOS + " or < 0");
}
Timestamp ts = new Timestamp(millis);
if (ts.getNanos() + nanos > MAX_ALLOWED_NANOS) {
int millisToNanosConvertor = BigDecimal.valueOf(MILLIS_TO_NANOS_CONVERTOR).intValue();
int overFlowMs = (ts.getNanos() + nanos) / millisToNanosConvertor;
int overFlowNanos = (ts.getNanos() + nanos) - (overFlowMs * millisToNanosConvertor);
ts = new Timestamp(millis + overFlowMs);
ts.setNanos(ts.getNanos() + overFlowNanos);
} else {
ts.setNanos(ts.getNanos() + nanos);
}
return ts;
}
/**
* Utility function to convert a {@link BigDecimal} value to {@link Timestamp}.
*/
public static Timestamp getTimestamp(BigDecimal bd) {
return DateUtil.getTimestamp(bd.longValue(), ((bd.remainder(BigDecimal.ONE).multiply(BigDecimal.valueOf(MILLIS_TO_NANOS_CONVERTOR))).intValue()));
}
public static interface DateTimeParser {
public long parseDateTime(String dateTimeString) throws IllegalDataException;
public TimeZone getTimeZone();
}
/**
* This class is used when a user explicitly provides phoenix.query.dateFormat in configuration
*/
private static class SimpleDateFormatParser implements DateTimeParser {
private String datePattern;
private SimpleDateFormat parser;
public SimpleDateFormatParser(String pattern, TimeZone timeZone) {
datePattern = pattern;
parser = new SimpleDateFormat(pattern) {
@Override
public java.util.Date parseObject(String source) throws ParseException {
java.util.Date date = super.parse(source);
return new java.sql.Date(date.getTime());
}
};
parser.setTimeZone(timeZone);
}
@Override
public long parseDateTime(String dateTimeString) throws IllegalDataException {
try {
java.util.Date date =parser.parse(dateTimeString);
return date.getTime();
} catch (ParseException e) {
throw new IllegalDataException("Unable to parse date/time '" + dateTimeString + "' using format string of '" + datePattern + "'.");
}
}
@Override
public TimeZone getTimeZone() {
return parser.getTimeZone();
}
}
private static class ISODateFormatParserFactory {
private ISODateFormatParserFactory() {}
public static DateTimeParser getParser(final TimeZone timeZone) {
// If timeZone matches default, get singleton DateTimeParser
if (timeZone.equals(DEFAULT_TIME_ZONE)) {
return ISODateFormatParser.getInstance();
}
// Otherwise, create new DateTimeParser
return new DateTimeParser() {
private final DateTimeFormatter formatter = ISO_DATE_TIME_FORMATTER
.withZone(DateTimeZone.forTimeZone(timeZone));
@Override
public long parseDateTime(String dateTimeString) throws IllegalDataException {
try {
return formatter.parseDateTime(dateTimeString).getMillis();
} catch(IllegalArgumentException ex) {
throw new IllegalDataException(ex);
}
}
@Override
public TimeZone getTimeZone() {
return timeZone;
}
};
}
}
/**
* This class is our default DateTime string parser
*/
private static class ISODateFormatParser implements DateTimeParser {
private static final ISODateFormatParser INSTANCE = new ISODateFormatParser();
public static ISODateFormatParser getInstance() {
return INSTANCE;
}
private final DateTimeFormatter formatter = ISO_DATE_TIME_FORMATTER.withZone(DateTimeZone.UTC);
private ISODateFormatParser() {}
@Override
public long parseDateTime(String dateTimeString) throws IllegalDataException {
try {
return formatter.parseDateTime(dateTimeString).getMillis();
} catch(IllegalArgumentException ex) {
throw new IllegalDataException(ex);
}
}
@Override
public TimeZone getTimeZone() {
return formatter.getZone().toTimeZone();
}
}
}