/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.server.types.texpressions; import java.text.DateFormatSymbols; import java.util.HashMap; import java.util.Locale; import org.joda.time.DateTimeFieldType; import org.joda.time.MutableDateTime; /** * Specifiers for <b>str_to_date</b> and <b>date_format</b> * See http://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html */ public enum DateTimeField { /** * abbreviated weekday name: Sun, Sat, Fri, ... */ a { @Override public long [] get(String str) { return new long[] {abbWeekday.get(str.substring(0, 3).toUpperCase()), 3}; } @Override public String get(MutableDateTime datetime) { return datetime.dayOfWeek().getAsShortText(); } @Override public int getFieldType () { return 1; } @Override public DateTimeField equivalentField () { return DateTimeField.W; } }, /** * abbreviated month name: Dec, Nov, Oct, ... */ b { @Override public long [] get(String str) { return new long [] {abbMonth.get(str.substring(0, 3).toUpperCase()), 3}; } @Override public String get(MutableDateTime datetime) { return datetime.monthOfYear().getAsShortText(); } @Override public int getFieldType () { return 1; } @Override public DateTimeField equivalentField () { return DateTimeField.m; } }, /** * month in numeric: 12, 11, 10, ... */ c { @Override public long [] get(String str) { return parseLeadingChars(str); } @Override public String get(MutableDateTime datetime) { return datetime.getMonthOfYear() + ""; } @Override public int getFieldType () { return 1; } @Override public DateTimeField equivalentField () { return DateTimeField.m; } }, /** * day of month with suffix: 31st, 30th, 29th, ... */ D { @Override public long [] get(String str) { int n = 0; int limit = 4 < str.length() ? 4 : str.length(); for (n = 0; n < limit && str.charAt(n) >= '0' && str.charAt(n) <= '9'; ++n ) ; // looking for the first char that is NOT a digit return new long[] { Long.parseLong(str.substring(0, n)), n +2 }; } @Override public String get(MutableDateTime datetime) { int d = datetime.getDayOfMonth(); switch(d%10) { case 1: return d + (d == 11 ? "th" :"st"); case 2: return d + (d == 12? "th" : "nd"); case 3: return d + (d == 13 ? "th" : "rd"); default: return d + "th"; } } @Override public int getFieldType () { return 1; } @Override public DateTimeField equivalentField () { return DateTimeField.d; } }, /** * day of month in numeric: 31, 30, 29, ..., 00 */ d { @Override public long [] get(String str) { return parseLeadingChars(str); } @Override public String get(MutableDateTime datetime) { return String.format("%02d", datetime.getDayOfMonth()); } @Override public int getFieldType () { return 1; } @Override public DateTimeField equivalentField () { return DateTimeField.d; } }, /** * same as d: day in numeric * [0, ..., 59] */ e { @Override public long [] get(String str) { return parseLeadingChars(str); } @Override public String get(MutableDateTime datetime) { return datetime.getDayOfMonth() + ""; } @Override public int getFieldType () { return 1; } @Override public DateTimeField equivalentField () { return DateTimeField.d; } }, /** * micro seconds */ f { @Override public long [] get(String str) { int n = 0; while (n < str.length() && Character.isDigit(str.charAt(n))) ++n; return new long[] {month.get(str.substring(0, n).toUpperCase()),n}; } @Override public String get(MutableDateTime datetime) { return "0"; // only second was supplied, => micro = 0 } @Override public int getFieldType () { return 2; } @Override public DateTimeField equivalentField () { return DateTimeField.f; } }, /** * hour in 24-hr format * [23, 22, ..., 01, 00] */ H { @Override public long [] get(String str) { return parseLeadingChars(str); } @Override public String get(MutableDateTime datetime) { return String.format("%02d", datetime.getHourOfDay()); } @Override public int getFieldType () { return 2; } @Override public DateTimeField equivalentField () { return DateTimeField.H; } }, /** * hour in 12-hr format * [12, 11, ...,01] */ h { @Override public long [] get(String str) { int i = 2 <= str.length() ? 2 : 1; // adjust hour to 24hr format long h = Long.parseLong(str.substring(0, i)); if (h > 12) throw new NumberFormatException(); h %= 12; return new long [] {h, i}; } @Override public String get(MutableDateTime datetime) { return String.format("%02d", datetime.get(DateTimeFieldType.clockhourOfHalfday())); } @Override public int getFieldType () { return 2; } @Override public DateTimeField equivalentField () { return DateTimeField.H; } }, /** * I same as h: hour in 12-hr format * [12, 11, ...,01] */ I { @Override public long [] get(String str) { int i = 2 <= str.length() ? 2 : 1; // adjust hour to 24hr format long h = Long.parseLong(str.substring(0, i)); if (h > 12) throw new NumberFormatException(); h %= 12; return new long [] {h, i}; } @Override public String get(MutableDateTime datetime) { return String.format("%02d", datetime.get(DateTimeFieldType.clockhourOfHalfday())); } @Override public int getFieldType () { return 2; } @Override public DateTimeField equivalentField () { return DateTimeField.H; } }, /** * i: minutes, numeric 00..59 */ i { @Override public long [] get(String str) { return parseLeadingChars(str); } @Override public String get(MutableDateTime datetime) { return String.format("%02d", datetime.getMinuteOfHour()); } @Override public int getFieldType () { return 2; } @Override public DateTimeField equivalentField () { return DateTimeField.i; } }, /** * day of year in numeric */ j { @Override public long [] get(String str) { int i = 0; for (; i < 3 && i < str.length() && str.charAt(i) >= '0' && str.charAt(i) <= '9'; ++i) ; // looking for the first char that is not a digit return new long[] {Long.parseLong(str.substring(0,i)), i}; } @Override public String get(MutableDateTime datetime) { return datetime.getDayOfYear() + ""; } @Override public int getFieldType () { return 1; } @Override public DateTimeField equivalentField () { return DateTimeField.j; } }, /** * hour : 24-hr format. * Same as H, but do not pre-pend 0 to one-digit hour * [23, 22, ...1,0] */ k { @Override public long [] get(String str) { return parseLeadingChars(str); } @Override public String get(MutableDateTime datetime) { return datetime.getHourOfDay() + ""; } @Override public int getFieldType () { return 2; } @Override public DateTimeField equivalentField () { return DateTimeField.H; } }, /** * hour: 12-hr format * Same as h, but does not pre-pend 0 to a one-digit value * [12, 11, ..., 1] */ l { @Override public long [] get(String str) { int i = 2 <= str.length() ? 2 : 1; // adjust hour to 24hr format long h = Long.parseLong(str.substring(0, i)); if (h > 12) throw new NumberFormatException(); h %= 12; return new long [] {h, i}; } @Override public String get(MutableDateTime datetime) { return datetime.get(DateTimeFieldType.clockhourOfHalfday()) + ""; } @Override public int getFieldType () { return 2; } @Override public DateTimeField equivalentField () { return DateTimeField.H; } }, /** * month name: December, November, ... */ M { @Override public long [] get(String str) { int n = 0; while (n < str.length() && !Character.isDigit(str.charAt(n))) ++n; return new long[] {month.get(str.substring(0, n).toUpperCase()),n}; } @Override public String get(MutableDateTime datetime) { return datetime.monthOfYear().getAsText(); } @Override public int getFieldType () { return 1; } @Override public DateTimeField equivalentField () { return DateTimeField.m; } }, /** * month in numeric: 12, 11, 10, ... */ m { @Override public long [] get(String str) { return parseLeadingChars(str); } @Override public String get(MutableDateTime datetime) { return String.format("%02d", datetime.getMonthOfYear()); } @Override public int getFieldType () { return 1; } @Override public DateTimeField equivalentField () { return DateTimeField.m; } }, /** * specify pm or am * to be used with %r (12-hr format time) or %h (12-hr format hour) * return NULL if used with %T (24-hr format time) or %H (24-hr format hour) */ p { @Override public long [] get (String str) { String ap = str.substring(0, 2); return new long[] {ap.equalsIgnoreCase("am") ? 0 : ap.equalsIgnoreCase("pm") ? 12 : -1 , 2}; } @Override public String get(MutableDateTime datetime) { int hr = datetime.getHourOfDay(); if(hr < 12) return "AM"; else return "PM"; } @Override public int getFieldType () { return 2; } @Override public DateTimeField equivalentField () { return DateTimeField.p; } }, /** * Time hh:mm:ss 12hr format */ r { @Override public long [] get(String str) { int i = Math.min(8, str.length()); String time = str.substring(0,i); String t[] = time.split("\\:"); // adjust time to 24hr format long hr = Long.parseLong(t[0]); if (hr > 12) throw new NumberFormatException(); hr %= 12; long m = Long.parseLong(t[1]); long s = Long.parseLong(t[2]); return new long [] {10000L * hr + 100 * m + s, i}; } @Override public String get(MutableDateTime datetime) { int h = datetime.get(DateTimeFieldType.clockhourOfHalfday()); int m = datetime.getMinuteOfHour(); int s = datetime.getSecondOfMinute(); String halfDay = datetime.get(DateTimeFieldType.halfdayOfDay()) == 0 ? "AM" : "PM"; return String.format("%02d:%02d:%02d %s" , h,m,s, halfDay); } @Override public int getFieldType () { return 2; } @Override public DateTimeField equivalentField () { return DateTimeField.T; } }, /** * second, same as s * [00, ..., 59] */ S { @Override public long [] get(String str) { return parseLeadingChars(str); } @Override public String get(MutableDateTime datetime) { return String.format("%02d", datetime.getSecondOfMinute()); } @Override public int getFieldType () { return 2; } @Override public DateTimeField equivalentField () { return DateTimeField.s; } }, /** * second * [00, ..., 59] */ s { @Override public long [] get(String str) { return parseLeadingChars(str); } @Override public String get(MutableDateTime datetime) { return String.format("%02d", datetime.getSecondOfMinute()); } @Override public int getFieldType () { return 2; } @Override public DateTimeField equivalentField () { return DateTimeField.s; } }, /** * Time: hh:mm:ss in 24hr format. */ T { @Override public long [] get(String str) { int i = Math.min(8, str.length()); String time = str.substring(0, i); String t[] = time.split("\\:"); return new long [] {10000L * Long.parseLong(t[0]) + 100 * Long.parseLong(t[1]) + Long.parseLong(t[2]), i}; } @Override public String get(MutableDateTime datetime) { int h = datetime.getHourOfDay(); int m = datetime.getMinuteOfHour(); int s = datetime.getSecondOfMinute(); return String.format("%02d:%02d:%02d", h,m,s); } @Override public int getFieldType () { return 2; } @Override public DateTimeField equivalentField () { return DateTimeField.T; } }, /** * week of year [0,...53], Sunday is the first day of week */ U { @Override public long [] get(String str) { // TO DO: not sure how this actually gets used throw new UnsupportedOperationException("%U is not supported in str_to_date"); /* int i; return new long [] { Long.parseLong(str.substring(0,i = 2 <= str.length() ? 2 :1)), i}; * */ } @Override public String get(MutableDateTime datetime) { // last parameter: 7 means SUNDAY return getWeek(datetime, datetime.getYear(), datetime.getMonthOfYear(), datetime.getDayOfMonth(), 7, true) + ""; } @Override public int getFieldType () { return 1; } @Override public DateTimeField equivalentField () { return DateTimeField.U; } }, /** * week of year [0...53], Monday is the first day of week */ u { @Override public long [] get(String str) { // TO DO: not sure how this actually gets used throw new UnsupportedOperationException("%u is not supported in str_to_date"); } @Override public String get(MutableDateTime datetime) { // last parameter: 1 means MONDAY return getWeek(datetime, datetime.getYear(), datetime.getMonthOfYear(), datetime.getDayOfMonth(), 1, true) + ""; } @Override public int getFieldType () { return 1; } @Override public DateTimeField equivalentField () { return DateTimeField.u; } }, /** * week of year: [1,..,53]: where Sunday is the first day * to be used with %X */ V { @Override public long [] get(String str) { return parseLeadingChars(str); } @Override public String get(MutableDateTime datetime) { return getWeek(datetime, datetime.getYear(), datetime.getMonthOfYear(), datetime.getDayOfMonth(), 7, false) + ""; } @Override public int getFieldType () { return 1; } @Override public DateTimeField equivalentField () { return DateTimeField.V; } }, /** * week of year [1,..,53]: where Monday is the first day * to be used with %x */ v { @Override public long [] get(String str) { return parseLeadingChars(str); } @Override public String get(MutableDateTime datetime) { // the last parameter: 1 means MONDAY return getWeek(datetime, datetime.getYear(), datetime.getMonthOfYear(), datetime.getDayOfMonth(), 1, false) + ""; } @Override public int getFieldType () { return 1; } @Override public DateTimeField equivalentField () { return DateTimeField.v; } }, /** * week day name: Sunday, Saturday, ... */ W { @Override public long [] get(String str) { int n = 0; while (n < str.length() && !Character.isDigit(str.charAt(n))) ++n; return new long[] {weekDay.get(str.substring(0, n).toUpperCase()),n}; } @Override public String get(MutableDateTime datetime) { return datetime.dayOfWeek().getAsText(); } @Override public int getFieldType () { return 1; } @Override public DateTimeField equivalentField () { return DateTimeField.W; } }, /** * week day in numeric: Sunday = 0, .... Saturday = 6 */ w { @Override public long [] get(String str) { return new long[] { Long.parseLong(str.charAt(0) + ""), 1}; } @Override public String get(MutableDateTime datetime) { return datetime.getDayOfWeek() % 7 + ""; } @Override public int getFieldType () { return 1; } @Override public DateTimeField equivalentField () { return DateTimeField.W; } }, /** * year for the week, to be used with %V: Sunday is the first day */ X { @Override public long [] get(String str) { int i = 4 % str.length(); i = (i == 0 ? 4 :i); return new long[] { Long.parseLong(str.substring(0, i)),i }; } @Override public String get(MutableDateTime datetime) { // Last parameter: 7 means SUNDAY return getYear(datetime, datetime.getYear(), datetime.getMonthOfYear(), datetime.getDayOfMonth(), 7) + ""; } @Override public int getFieldType () { return 1; } @Override public DateTimeField equivalentField () { return DateTimeField.X; } }, /** * year for the week, to be used with %v: Monday is the first day */ x { @Override public long [] get(String str) { int i = 4 % str.length(); i = (i == 0 ? 4 :i); return new long[] { Long.parseLong(str.substring(0, i)),i }; } @Override public String get(MutableDateTime datetime) { // the last parameter: 1 means MONDAY return getYear(datetime, datetime.getYear(), datetime.getMonthOfYear(), datetime.getDayOfMonth(), 1) + ""; } @Override public int getFieldType () { return 1; } @Override public DateTimeField equivalentField () { return DateTimeField.x; } }, /** * year : 2 digits, relative to 2000 */ y { @Override public long [] get(String str) { int i; return new long[] {2000 + Long.parseLong(str.substring(0, i = 2 <= str.length() ? 2: 1)), i}; } @Override public String get(MutableDateTime datetime) { return String.format("%02d", datetime.getYear() % 100); } @Override public int getFieldType () { return 1; } @Override public DateTimeField equivalentField () { return DateTimeField.Y; } }, /** * year: 4 digits */ Y { @Override public long [] get(String str) { int i = 4 % str.length(); i = (i == 0 ? 4 :i); return new long[] { Long.parseLong(str.substring(0, i)),i }; } @Override public String get(MutableDateTime datetime) { return datetime.getYear() + ""; } @Override public int getFieldType () { return 1; } @Override public DateTimeField equivalentField () { return DateTimeField.Y; } }, /** * literal % */ percent { @Override public long [] get(String str) { throw new UnsupportedOperationException("literal % is not supported in str_to_date"); } @Override public int getFieldType () { throw new UnsupportedOperationException("literal % is not supported for this method"); } @Override public String get(MutableDateTime datetime) { return "%"; } @Override public DateTimeField equivalentField () { return DateTimeField.percent; } }; /** * parse str to get value for this DateTimeField. * return an array of long where array[0] is the parsed value, and array[1] is n where * str.substring(0,n) contains the parsed value. This is used to remove parsed DateTimeField from str. */ abstract long [] get(String str); /** * * @param datetime: given a datetime object * @return the value of this field as a string */ abstract String get(MutableDateTime datetime); /** * * @return 1 if the DateTimeField specifies date-related info (i.e., year, month, day, date, week, etc..) * 2 if the DateTimeField specifies time-related info (i.e., hour, minute, second,) */ abstract int getFieldType (); /** * * @return the equivalent DateTimeField (that's ultimately used to map values to) * For example, %b, %m, and %M are all referred to as %m because the all mean day of month * %y and %Y => %Y year */ abstract DateTimeField equivalentField (); /** * * @param str * * - Looks at the first two chars of the string and try to parse that substring * into a LONG * - But if the length is only 1, then it only looks at the first char * @return the parsed value along with the length of the substring that was parsed * The index is passed back to the calling function to cut-off the substring * that has been passed */ private static long [] parseLeadingChars(String str) { int i = 2 <= str.length() ? 2 :1; return new long[] { Long.parseLong(str.substring(0, i )), i}; } /** * to be used in X and x * @param cal * @param yr * @param mo * @param da * @param firstDay: the first day of week * @return the year for this week, could be the same as yr or different * , depending on the first day of year */ private static int getYear(MutableDateTime cal, int yr, int mo, int da, int firstDay) { if (mo > 1 || da >7 ) return yr; cal.setYear(yr); cal.setMonthOfYear(1); cal.setDayOfMonth(1); int firstD = 1; while (cal.getDayOfWeek() != firstDay) cal.setDayOfMonth(++firstD); // reset cal cal.setYear(yr); cal.setMonthOfYear(mo); cal.setDayOfMonth(da); if (da < firstD) return yr -1; else return yr; } /** * to be used in V, v, U and u * @param cal * @param yr * @param mo * @param da * @param firstDay * @param lowestIs0: whether the lowest value could be zero or not * @return the week for this date, if the lowest value is not supposed to be zero, then it returns that * the number of the last week in the previous year */ private static int getWeek(MutableDateTime cal, int yr, int mo, int da, int firstDay, boolean lowestIs0) { cal.setYear(yr); cal.setMonthOfYear(1); cal.setDayOfMonth(1); int firstD = 1; while (cal.getDayOfWeek() != firstDay) cal.setDayOfMonth(++firstD); cal.setYear(yr); cal.setMonthOfYear(mo); cal.setDayOfMonth(da); int dayOfYear = cal.getDayOfYear(); int result; if (dayOfYear < firstD) result = (lowestIs0 ? 0 : getWeek(cal, yr-1, 12, 31, firstDay, lowestIs0)); else result = (dayOfYear - firstD) / 7 +1; // reset cal cal.setYear(yr); cal.setMonthOfYear(mo); cal.setDayOfMonth(da); return result; } /** * Takes a (Mutable)DateTime object and a mysql format string * * @param date * @param format * @return a string formatted accordingly */ public static String getFormatted (MutableDateTime date, String format) { String[] frmList = format.split("\\%"); StringBuilder builder = new StringBuilder(frmList[0]); for (int n = 1; n < frmList.length; ++n) { if (frmList[n].length() == 0 ) { builder.append('%'); if ( n+1 < frmList.length && frmList[n+1].length() == 0) ++n; } else { try { String s = Character.toString(frmList[n].charAt(0)); builder.append(frmList[n].replaceFirst(s, DateTimeField.valueOf(s).get(date))); } catch (IllegalArgumentException ex) // unknown specifiers are treated as regular chars { builder.append(frmList[n]); } } } for (int m = format.length() -1; format.charAt(m) == '%'; m -= 2) builder.append('%'); return builder.toString(); } // class static data DateTimeFields static protected final HashMap<String, Integer> abbWeekday = new HashMap<>(); static protected final HashMap<String, Integer> weekDay = new HashMap<>(); static protected final HashMap<String, Integer> abbMonth = new HashMap<>(); static protected final HashMap<String, Integer> month = new HashMap<>(); static { DateFormatSymbols fm = new DateFormatSymbols(new Locale(System.getProperty("user.language"))); String mon [] = fm.getMonths(), shortMon[] = fm.getShortMonths(), wk[] = fm.getWeekdays(), shortWk[] = fm.getShortWeekdays(); for (int n = 0; n < 12; ++n) { month.put(mon[n].toUpperCase(), n+1); abbMonth.put(shortMon[n].toUpperCase(), n+1); } for (int n = 1; n < 8; ++n) { weekDay.put(wk[n].toUpperCase(), n-1); abbWeekday.put(shortWk[n].toUpperCase(), n-1); } } }