/* * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor * license agreements. See the NOTICE file distributed with this work for * additional information regarding copyright ownership. Crate 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. * * However, if you have executed another commercial license agreement * with Crate these terms will supersede the license and you may use the * software solely pursuant to the terms of the relevant commercial agreement. */ package io.crate.operation.scalar; import com.carrotsearch.hppc.CharObjectHashMap; import com.carrotsearch.hppc.CharObjectMap; import org.apache.lucene.util.BytesRef; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import java.util.Arrays; import java.util.Calendar; import java.util.Locale; /** * Formatting DateTime instances using the MySQL date_format format: * http://dev.mysql.com/doc/refman/5.6/en/date-and-time-functions.html#function_date-format */ public class TimestampFormatter { private interface FormatTimestampPartFunction { String format(DateTime timestamp); } private final static Locale LOCALE = Locale.ENGLISH; private final static CharObjectMap<FormatTimestampPartFunction> PART_FORMATTERS = new CharObjectHashMap<>(); private static void addFormatter(char character, FormatTimestampPartFunction fun) { PART_FORMATTERS.put(character, fun); } static { // %a Abbreviated weekday name (Sun..Sat) addFormatter('a', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { return timestamp.dayOfWeek().getAsShortText(LOCALE); } }); // %b Abbreviated month name (Jan..Dec) addFormatter('b', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { return timestamp.monthOfYear().getAsShortText(LOCALE); } }); // %c Month, numeric (0..12) addFormatter('c', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { return String.valueOf(timestamp.monthOfYear().get()); } }); // %D Day of the month with English suffix (0th, 1st, 2nd, 3rd, …) addFormatter('D', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { int n = timestamp.dayOfMonth().get(); StringBuilder builder = new StringBuilder(n > 9 ? 4 : 3); builder.append(n); if (n >= 11 && n <= 13) { builder.append("th"); } switch (n % 10) { case 1: builder.append("st"); break; case 2: builder.append("nd"); break; case 3: builder.append("rd"); break; default: builder.append("th"); } return builder.toString(); } }); // %d Day of the month, numeric (00..31) addFormatter('d', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { String dayOfMonth = String.valueOf(timestamp.dayOfMonth().get()); return zeroPadded(2, dayOfMonth); } }); // %e Day of the month, numeric (0..31) addFormatter('e', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { return String.valueOf(timestamp.dayOfMonth().get()); } }); // %f Microseconds (000000..999999) addFormatter('f', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { return zeroPadded(6, String.valueOf(timestamp.millisOfSecond().get() * 1000)); } }); final FormatTimestampPartFunction padded24HourFunction = new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { int hourOfDay = timestamp.getHourOfDay() % 24; return zeroPadded(2, String.valueOf(hourOfDay)); } }; // %H Hour (00..23) addFormatter('H', padded24HourFunction); final FormatTimestampPartFunction padded12HourFunction = new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { int hourOfDay = timestamp.getHourOfDay() % 12; if (hourOfDay == 0) { hourOfDay = 12; } return zeroPadded(2, String.valueOf(hourOfDay)); } }; // %h Hour (01..12) // %I Hour (01..12) addFormatter('h', padded12HourFunction); addFormatter('I', padded12HourFunction); // %i Minutes, numeric (00..59) addFormatter('i', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { return zeroPadded(2, timestamp.minuteOfHour().getAsShortText(LOCALE)); } }); // %j Day of year (001..366) addFormatter('j', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { return zeroPadded(3, timestamp.dayOfYear().getAsShortText(LOCALE)); } }); // %k Hour (0..23) addFormatter('k', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { return timestamp.hourOfDay().getAsShortText(LOCALE); } }); // %l Hour (1..12) addFormatter('l', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { int hourOfDay = timestamp.getHourOfDay() % 12; if (hourOfDay == 0) { hourOfDay = 12; } return String.valueOf(hourOfDay); } }); // %M Month name (January..December) addFormatter('M', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { return timestamp.monthOfYear().getAsText(LOCALE); } }); // %m Month, numeric (00..12) addFormatter('m', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { return zeroPadded(2, String.valueOf(timestamp.monthOfYear().get())); } }); final FormatTimestampPartFunction amPmFunc = new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { // TODO: verify correctness return timestamp.getHourOfDay() < 12 ? "AM" : "PM"; } }; // %p AM or PM addFormatter('p', amPmFunc); final FormatTimestampPartFunction paddedMinuteFunction = new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { return zeroPadded(2, timestamp.minuteOfHour().getAsShortText(LOCALE)); } }; final FormatTimestampPartFunction paddedSecondFunction = new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { return zeroPadded(2, timestamp.secondOfMinute().getAsShortText(LOCALE)); } }; // %r Time, 12-hour (hh:mm:ss followed by AM or PM) addFormatter('r', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { return padded12HourFunction.format(timestamp) + ':' + paddedMinuteFunction.format(timestamp) + ':' + paddedSecondFunction.format(timestamp) + ' ' + amPmFunc.format(timestamp); } }); // %S Seconds (00..59) // %s Seconds (00..59) addFormatter('s', paddedSecondFunction); addFormatter('S', paddedSecondFunction); // %T Time, 24-hour (hh:mm:ss) addFormatter('T', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { return padded24HourFunction.format(timestamp) + ':' + paddedMinuteFunction.format(timestamp) + ':' + paddedSecondFunction.format(timestamp); } }); // %U Week (00..53), where Sunday is the first day of the week; WEEK() mode 0 // with respect to the year that contains the first day of the week for the given date // if first week addFormatter('U', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { // if week starts in last year, return 00 // range 00 - 53 Calendar c = Calendar.getInstance(timestamp.getZone().toTimeZone(), LOCALE); c.setFirstDayOfWeek(Calendar.SUNDAY); c.setMinimalDaysInFirstWeek(7); c.setTimeInMillis(timestamp.getMillis()); int week = c.get(Calendar.WEEK_OF_YEAR); int weekYear = c.getWeekYear(); int year = c.get(Calendar.YEAR); if (weekYear < year) { week = 0; } else if (weekYear > year) { week = c.getWeeksInWeekYear(); } return zeroPadded(2, String.valueOf(week)); } }); // %u Week (00..53), where Monday is the first day of the week; WEEK() mode 1 // weeks are numbered according to ISO 8601:1988 addFormatter('u', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { Calendar c = Calendar.getInstance(timestamp.getZone().toTimeZone(), LOCALE); c.setFirstDayOfWeek(Calendar.MONDAY); c.setMinimalDaysInFirstWeek(4); c.setTimeInMillis(timestamp.getMillis()); int week = c.get(Calendar.WEEK_OF_YEAR); int weekYear = c.getWeekYear(); int year = c.get(Calendar.YEAR); if (weekYear < year) { week = 0; } else if (weekYear > year) { week = c.getWeeksInWeekYear(); } return zeroPadded(2, String.valueOf(week)); } }); // %V Week (01..53), where Sunday is the first day of the week; WEEK() mode 2; used with %X // with respect to the year that contains the first day of the week for the given date addFormatter('V', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { Calendar c = Calendar.getInstance(timestamp.getZone().toTimeZone(), LOCALE); c.setFirstDayOfWeek(Calendar.SUNDAY); c.setMinimalDaysInFirstWeek(7); c.setTimeInMillis(timestamp.getMillis()); int week = c.get(Calendar.WEEK_OF_YEAR); int weekYear = c.getWeekYear(); int year = c.get(Calendar.YEAR); if (weekYear < year) { // get weeks from last year c.add(Calendar.DAY_OF_MONTH, -7); week = c.getWeeksInWeekYear(); } return zeroPadded(2, String.valueOf(week)); } }); // %v Week (01..53), where Monday is the first day of the week; WEEK() mode 3; used with %x // weeks are numbered according to ISO 8601:1988 addFormatter('v', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { Calendar c = Calendar.getInstance(timestamp.getZone().toTimeZone(), LOCALE); c.setFirstDayOfWeek(Calendar.MONDAY); c.setMinimalDaysInFirstWeek(4); c.setTimeInMillis(timestamp.getMillis()); int week = c.get(Calendar.WEEK_OF_YEAR); int weekYear = c.getWeekYear(); int year = c.get(Calendar.YEAR); if (weekYear < year) { // get weeks from last year c.add(Calendar.DAY_OF_MONTH, -7); week = c.getWeeksInWeekYear(); } return zeroPadded(2, String.valueOf(week)); } }); // %W Weekday name (Sunday..Saturday) addFormatter('W', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { return timestamp.dayOfWeek().getAsText(LOCALE); } }); // %w Day of the week (0=Sunday..6=Saturday) addFormatter('w', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { // timestamp.dayOfWeek() returns 1=monday, 7=sunday int dayOfWeek = timestamp.dayOfWeek().get() % 7; return String.valueOf(dayOfWeek); } }); // %X Year for the week where Sunday is the first day of the week, numeric, four digits; used with %V addFormatter('X', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { Calendar c = Calendar.getInstance(timestamp.getZone().toTimeZone(), LOCALE); c.setFirstDayOfWeek(Calendar.SUNDAY); c.setMinimalDaysInFirstWeek(7); c.setTimeInMillis(timestamp.withZone(DateTimeZone.UTC).getMillis()); return zeroPadded(4, String.valueOf(c.getWeekYear())); } }); // %x Year for the week, where Monday is the first day of the week, numeric, four digits; used with %v addFormatter('x', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { Calendar c = Calendar.getInstance(timestamp.getZone().toTimeZone(), LOCALE); c.setFirstDayOfWeek(Calendar.MONDAY); c.setMinimalDaysInFirstWeek(4); c.setTimeInMillis(timestamp.withZone(DateTimeZone.UTC).getMillis()); return zeroPadded(4, String.valueOf(c.getWeekYear())); } }); // %Y Year, numeric, four digits addFormatter('Y', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { return timestamp.year().getAsShortText(Locale.ENGLISH); } }); // %y Year, numeric (two digits) addFormatter('y', new FormatTimestampPartFunction() { @Override public String format(DateTime timestamp) { return timestamp.yearOfCentury().getAsShortText(Locale.ENGLISH); } }); } private static String zeroPadded(int to, String val) { int length = val.length(); if (length >= to) { return val; } else { char[] padded = new char[to]; Arrays.fill(padded, '0'); val.getChars(0, length, padded, Math.max(0, to - length)); return new String(padded); } } public static BytesRef format(BytesRef formatString, DateTime timestamp) { StringBuilder buffer = new StringBuilder(formatString.length); String format = formatString.utf8ToString(); boolean percentEscape = false; int length = format.length(); for (int i = 0; i < length; i++) { char current = format.charAt(i); if (current == '%') { if (!percentEscape) { percentEscape = true; } else { buffer.append('%'); percentEscape = false; } } else { if (percentEscape) { FormatTimestampPartFunction partFormatter = PART_FORMATTERS.get(current); if (partFormatter == null) { buffer.append(current); } else { buffer.append(partFormatter.format(timestamp)); } } else { buffer.append(current); } percentEscape = false; } } return new BytesRef(buffer.toString()); } }