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