| /* |
| * 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.drill.common.expression.fn; |
| |
| import com.google.common.collect.Maps; |
| import org.apache.commons.lang3.StringUtils; |
| |
| import java.util.Comparator; |
| import java.util.Map; |
| |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.POSTGRES_ABR_NAME_OF_MONTH; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.POSTGRES_DAY_OF_MONTH; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.POSTGRES_DAY_OF_WEEK; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.POSTGRES_DAY_OF_YEAR; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.POSTGRES_FULL_ERA_NAME; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.POSTGRES_FULL_NAME_OF_DAY; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.POSTGRES_HALFDAY_AM; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.POSTGRES_HALFDAY_PM; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.POSTGRES_HOUR_12_NAME; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.POSTGRES_HOUR_12_OTHER_NAME; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.POSTGRES_HOUR_24_NAME; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.POSTGRES_ISO_1YEAR; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.POSTGRES_ISO_2YEAR; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.POSTGRES_ISO_3YEAR; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.POSTGRES_ISO_4YEAR; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.POSTGRES_ISO_WEEK_OF_YEAR; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.POSTGRES_MILLISECOND_OF_MINUTE_NAME; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.POSTGRES_MINUTE_OF_HOUR_NAME; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.POSTGRES_MONTH; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.POSTGRES_NAME_OF_DAY; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.POSTGRES_NAME_OF_MONTH; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.POSTGRES_SECOND_OF_MINUTE_NAME; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.POSTGRES_WEEK_OF_YEAR; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.POSTGRES_YEAR; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.PREFIX_FM; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.PREFIX_FX; |
| import static org.apache.drill.common.expression.fn.JodaDateValidator.PostgresDateTimeConstant.PREFIX_TM; |
| |
| public class JodaDateValidator { |
| |
| public enum PostgresDateTimeConstant { |
| |
| // patterns for replacing |
| POSTGRES_FULL_NAME_OF_DAY(true, "day"), |
| POSTGRES_DAY_OF_YEAR(false, "ddd"), |
| POSTGRES_DAY_OF_MONTH(false, "dd"), |
| POSTGRES_DAY_OF_WEEK(false, "d"), |
| POSTGRES_NAME_OF_MONTH(true, "month"), |
| POSTGRES_ABR_NAME_OF_MONTH(true, "mon"), |
| POSTGRES_YEAR(false, "y"), |
| POSTGRES_ISO_4YEAR(false, "iyyy"), |
| POSTGRES_ISO_3YEAR(false, "iyy"), |
| POSTGRES_ISO_2YEAR(false, "iy"), |
| POSTGRES_ISO_1YEAR(false, "i"), |
| POSTGRES_FULL_ERA_NAME(false, "ee"), |
| POSTGRES_NAME_OF_DAY(true, "dy"), |
| POSTGRES_HOUR_12_NAME(false, "hh"), |
| POSTGRES_HOUR_12_OTHER_NAME(false, "hh12"), |
| POSTGRES_HOUR_24_NAME(false, "hh24"), |
| POSTGRES_MINUTE_OF_HOUR_NAME(false, "mi"), |
| POSTGRES_SECOND_OF_MINUTE_NAME(false, "ss"), |
| POSTGRES_MILLISECOND_OF_MINUTE_NAME(false, "ms"), |
| POSTGRES_WEEK_OF_YEAR(false, "ww"), |
| POSTGRES_ISO_WEEK_OF_YEAR(false, "iw"), |
| POSTGRES_MONTH(false, "mm"), |
| POSTGRES_HALFDAY_AM(false, "am"), |
| POSTGRES_HALFDAY_PM(false, "pm"), |
| |
| // pattern modifiers for deleting |
| PREFIX_FM(false, "fm"), |
| PREFIX_FX(false, "fx"), |
| PREFIX_TM(false, "tm"); |
| |
| private final boolean hasCamelCasing; |
| private final String name; |
| |
| PostgresDateTimeConstant(boolean hasCamelCasing, String name) { |
| this.hasCamelCasing = hasCamelCasing; |
| this.name = name; |
| } |
| |
| public boolean hasCamelCasing() { |
| return hasCamelCasing; |
| } |
| |
| public String getName() { |
| return name; |
| } |
| } |
| |
| private static final Map<PostgresDateTimeConstant, String> postgresToJodaMap = Maps.newTreeMap(new LengthDescComparator()); |
| |
| public static final String POSTGRES_ESCAPE_CHARACTER = "\""; |
| |
| // jodaTime patterns |
| public static final String JODA_FULL_NAME_OF_DAY = "EEEE"; |
| public static final String JODA_DAY_OF_YEAR = "D"; |
| public static final String JODA_DAY_OF_MONTH = "d"; |
| public static final String JODA_DAY_OF_WEEK = "e"; |
| public static final String JODA_NAME_OF_MONTH = "MMMM"; |
| public static final String JODA_ABR_NAME_OF_MONTH = "MMM"; |
| public static final String JODA_YEAR = "y"; |
| public static final String JODA_ISO_4YEAR = "xxxx"; |
| public static final String JODA_ISO_3YEAR = "xxx"; |
| public static final String JODA_ISO_2YEAR = "xx"; |
| public static final String JODA_ISO_1YEAR = "x"; |
| public static final String JODA_FULL_ERA_NAME = "G"; |
| public static final String JODA_NAME_OF_DAY = "E"; |
| public static final String JODA_HOUR_12_NAME = "h"; |
| public static final String JODA_HOUR_24_NAME = "H"; |
| public static final String JODA_MINUTE_OF_HOUR_NAME = "m"; |
| public static final String JODA_SECOND_OF_MINUTE_NAME = "ss"; |
| public static final String JODA_MILLISECOND_OF_MINUTE_NAME = "SSS"; |
| public static final String JODA_WEEK_OF_YEAR = "w"; |
| public static final String JODA_MONTH = "MM"; |
| public static final String JODA_HALFDAY = "aa"; |
| public static final String JODA_ESCAPE_CHARACTER = "'"; |
| public static final String EMPTY_STRING = ""; |
| |
| static { |
| postgresToJodaMap.put(POSTGRES_FULL_NAME_OF_DAY, JODA_FULL_NAME_OF_DAY); |
| postgresToJodaMap.put(POSTGRES_DAY_OF_YEAR, JODA_DAY_OF_YEAR); |
| postgresToJodaMap.put(POSTGRES_DAY_OF_MONTH, JODA_DAY_OF_MONTH); |
| postgresToJodaMap.put(POSTGRES_DAY_OF_WEEK, JODA_DAY_OF_WEEK); |
| postgresToJodaMap.put(POSTGRES_NAME_OF_MONTH, JODA_NAME_OF_MONTH); |
| postgresToJodaMap.put(POSTGRES_ABR_NAME_OF_MONTH, JODA_ABR_NAME_OF_MONTH); |
| postgresToJodaMap.put(POSTGRES_FULL_ERA_NAME, JODA_FULL_ERA_NAME); |
| postgresToJodaMap.put(POSTGRES_NAME_OF_DAY, JODA_NAME_OF_DAY); |
| postgresToJodaMap.put(POSTGRES_HOUR_12_NAME, JODA_HOUR_12_NAME); |
| postgresToJodaMap.put(POSTGRES_HOUR_12_OTHER_NAME, JODA_HOUR_12_NAME); |
| postgresToJodaMap.put(POSTGRES_HOUR_24_NAME, JODA_HOUR_24_NAME); |
| postgresToJodaMap.put(POSTGRES_MINUTE_OF_HOUR_NAME, JODA_MINUTE_OF_HOUR_NAME); |
| postgresToJodaMap.put(POSTGRES_SECOND_OF_MINUTE_NAME, JODA_SECOND_OF_MINUTE_NAME); |
| postgresToJodaMap.put(POSTGRES_MILLISECOND_OF_MINUTE_NAME, JODA_MILLISECOND_OF_MINUTE_NAME); |
| postgresToJodaMap.put(POSTGRES_WEEK_OF_YEAR, JODA_WEEK_OF_YEAR); |
| postgresToJodaMap.put(POSTGRES_MONTH, JODA_MONTH); |
| postgresToJodaMap.put(POSTGRES_HALFDAY_AM, JODA_HALFDAY); |
| postgresToJodaMap.put(POSTGRES_HALFDAY_PM, JODA_HALFDAY); |
| postgresToJodaMap.put(POSTGRES_ISO_WEEK_OF_YEAR, JODA_WEEK_OF_YEAR); |
| postgresToJodaMap.put(POSTGRES_YEAR, JODA_YEAR); |
| postgresToJodaMap.put(POSTGRES_ISO_1YEAR, JODA_ISO_1YEAR); |
| postgresToJodaMap.put(POSTGRES_ISO_2YEAR, JODA_ISO_2YEAR); |
| postgresToJodaMap.put(POSTGRES_ISO_3YEAR, JODA_ISO_3YEAR); |
| postgresToJodaMap.put(POSTGRES_ISO_4YEAR, JODA_ISO_4YEAR); |
| postgresToJodaMap.put(PREFIX_FM, EMPTY_STRING); |
| postgresToJodaMap.put(PREFIX_FX, EMPTY_STRING); |
| postgresToJodaMap.put(PREFIX_TM, EMPTY_STRING); |
| } |
| |
| /** |
| * Replaces all postgres patterns from {@param pattern}, |
| * available in postgresToJodaMap keys to jodaTime equivalents. |
| * |
| * @param pattern date pattern in postgres format |
| * @return date pattern with replaced patterns in joda format |
| */ |
| public static String toJodaFormat(String pattern) { |
| // replaces escape character for text delimiter |
| StringBuilder builder = new StringBuilder(pattern.replaceAll(POSTGRES_ESCAPE_CHARACTER, JODA_ESCAPE_CHARACTER)); |
| |
| int start = 0; // every time search of postgres token in pattern will start from this index. |
| int minPos; // min position of the longest postgres token |
| do { |
| // finds first value with max length |
| minPos = builder.length(); |
| PostgresDateTimeConstant firstMatch = null; |
| for (PostgresDateTimeConstant postgresPattern : postgresToJodaMap.keySet()) { |
| // keys sorted in length decreasing |
| // at first search longer tokens to consider situation where some tokens are the parts of large tokens |
| // example: if pattern contains a token "DDD", token "DD" would be skipped, as a part of "DDD". |
| int pos; |
| // some tokens can't be in upper camel casing, so we ignore them here. |
| // example: DD, DDD, MM, etc. |
| if (postgresPattern.hasCamelCasing()) { |
| // finds postgres tokens in upper camel casing |
| // example: Month, Mon, Day, Dy, etc. |
| pos = builder.indexOf(StringUtils.capitalize(postgresPattern.getName()), start); |
| if (pos >= 0 && pos < minPos) { |
| firstMatch = postgresPattern; |
| minPos = pos; |
| if (minPos == start) { |
| break; |
| } |
| } |
| } |
| // finds postgres tokens in lower casing |
| pos = builder.indexOf(postgresPattern.getName().toLowerCase(), start); |
| if (pos >= 0 && pos < minPos) { |
| firstMatch = postgresPattern; |
| minPos = pos; |
| if (minPos == start) { |
| break; |
| } |
| } |
| // finds postgres tokens in upper casing |
| pos = builder.indexOf(postgresPattern.getName().toUpperCase(), start); |
| if (pos >= 0 && pos < minPos) { |
| firstMatch = postgresPattern; |
| minPos = pos; |
| if (minPos == start) { |
| break; |
| } |
| } |
| } |
| // replaces postgres token, if found and it does not escape character |
| if (minPos < builder.length() && firstMatch != null) { |
| String jodaToken = postgresToJodaMap.get(firstMatch); |
| // checks that token is not a part of escape sequence |
| if (StringUtils.countMatches(builder.subSequence(0, minPos), JODA_ESCAPE_CHARACTER) % 2 == 0) { |
| int offset = minPos + firstMatch.getName().length(); |
| builder.replace(minPos, offset, jodaToken); |
| start = minPos + jodaToken.length(); |
| } else { |
| int endEscapeCharacter = builder.indexOf(JODA_ESCAPE_CHARACTER, minPos); |
| if (endEscapeCharacter >= 0) { |
| start = endEscapeCharacter; |
| } else { |
| break; |
| } |
| } |
| } |
| } while (minPos < builder.length()); |
| return builder.toString(); |
| } |
| |
| /** |
| * Length decreasing comparator. |
| * Compares PostgresDateTimeConstant names by length, if they have the same length, compares them lexicographically. |
| */ |
| private static class LengthDescComparator implements Comparator<PostgresDateTimeConstant> { |
| |
| public int compare(PostgresDateTimeConstant o1, PostgresDateTimeConstant o2) { |
| int result = o2.getName().length() - o1.getName().length(); |
| if (result == 0) { |
| return o1.getName().compareTo(o2.getName()); |
| } |
| return result; |
| } |
| } |
| |
| } |