| /* |
| * 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(); |
| } |
| } |
| } |