blob: 515890f180d98080614b94162ec9c4a17e5823f6 [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.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;
}
}
}