diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java index d3f49d4349bb..6cfc280981ec 100644 --- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java +++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java @@ -182,6 +182,7 @@ import static org.apache.calcite.sql.fun.SqlLibraryOperators.DATEADD; import static org.apache.calcite.sql.fun.SqlLibraryOperators.DATETIME; import static org.apache.calcite.sql.fun.SqlLibraryOperators.DATETIME_TRUNC; +import static org.apache.calcite.sql.fun.SqlLibraryOperators.DATE_FORMAT; import static org.apache.calcite.sql.fun.SqlLibraryOperators.DATE_FROM_UNIX_DATE; import static org.apache.calcite.sql.fun.SqlLibraryOperators.DATE_PART; import static org.apache.calcite.sql.fun.SqlLibraryOperators.DATE_TRUNC; @@ -833,6 +834,7 @@ Builder populate2() { // Datetime formatting methods defineReflective(TO_CHAR, BuiltInMethod.TO_CHAR.method); defineReflective(TO_CHAR_PG, BuiltInMethod.TO_CHAR_PG.method); + defineReflective(DATE_FORMAT, BuiltInMethod.DATE_FORMAT.method); defineReflective(TO_DATE, BuiltInMethod.TO_DATE.method); defineReflective(TO_DATE_PG, BuiltInMethod.TO_DATE_PG.method); defineReflective(TO_TIMESTAMP, BuiltInMethod.TO_TIMESTAMP.method); diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java index 7da9705c1c09..59b4eb5e88b0 100644 --- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java +++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java @@ -4313,6 +4313,14 @@ public String toCharPg(long timestamp, String pattern) { return PostgresqlDateTimeFormatter.toChar(pattern, zonedDateTime).trim(); } + public String dateFormat(long timestamp, String pattern) { + final Timestamp sqlTimestamp = internalToTimestamp(timestamp); + sb.setLength(0); + withElements(FormatModels.MYSQL, pattern, elements -> + elements.forEach(element -> element.format(sb, sqlTimestamp))); + return sb.toString().trim(); + } + public int toDate(String dateString, String fmtString) { return toInt( new java.sql.Date(internalToDateTime(dateString, fmtString))); diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java index 131ac7c3a759..fd3b009c56d2 100644 --- a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java +++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java @@ -1856,6 +1856,16 @@ private static RelDataType deriveTypeMapFromEntries(SqlOperatorBinding opBinding OperandHandlers.DEFAULT, OperandTypes.TIMESTAMP_STRING, 0, SqlFunctionCategory.TIMEDATE, call -> SqlMonotonicity.NOT_MONOTONIC, false) { }; + + /** The "DATE_FORMAT(timestamp, format)" function; + * converts {@code timestamp} to string according to the given {@code format}. */ + @LibraryOperator(libraries = {MYSQL}) + public static final SqlFunction DATE_FORMAT = + SqlBasicFunction.create("DATE_FORMAT", + ReturnTypes.VARCHAR_NULLABLE, + OperandTypes.TIMESTAMP_STRING, + SqlFunctionCategory.TIMEDATE); + /** The "TO_DATE(string1, string2)" function; casts string1 * to a DATE using the format specified in string2. */ @LibraryOperator(libraries = {ORACLE, REDSHIFT}) diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java index 7d74ccd37a6e..d2b596f6751d 100644 --- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java +++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java @@ -695,6 +695,8 @@ public enum BuiltInMethod { String.class), TO_CHAR_PG(SqlFunctions.DateFormatFunction.class, "toCharPg", long.class, String.class), + DATE_FORMAT(SqlFunctions.DateFormatFunction.class, "dateFormat", long.class, + String.class), TO_DATE(SqlFunctions.DateFormatFunction.class, "toDate", String.class, String.class), TO_DATE_PG(SqlFunctions.DateFormatFunction.class, "toDatePg", String.class, diff --git a/core/src/main/java/org/apache/calcite/util/format/FormatElementEnum.java b/core/src/main/java/org/apache/calcite/util/format/FormatElementEnum.java index f4f290903ff5..0c7f58dc8d6e 100644 --- a/core/src/main/java/org/apache/calcite/util/format/FormatElementEnum.java +++ b/core/src/main/java/org/apache/calcite/util/format/FormatElementEnum.java @@ -24,8 +24,10 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.time.DayOfWeek; import java.time.LocalDate; import java.time.format.TextStyle; +import java.time.temporal.ChronoField; import java.util.Calendar; import java.util.Date; import java.util.Locale; @@ -59,6 +61,13 @@ public enum FormatElementEnum implements FormatElement { sb.append(String.format(Locale.ROOT, "%d", calendar.get(Calendar.DAY_OF_WEEK))); } }, + D0("", "The weekday (Monday as the first day of the week) as a decimal number (0-6)") { + @Override public void format(StringBuilder sb, Date date) { + final Calendar calendar = Work.get().calendar; + calendar.setTime(date); + sb.append(String.format(Locale.ROOT, "%d", calendar.get(Calendar.DAY_OF_WEEK) - 1)); + } + }, DAY("EEEE", "The full weekday name, in uppercase") { @Override public void format(StringBuilder sb, Date date) { final Work work = Work.get(); @@ -77,6 +86,21 @@ public enum FormatElementEnum implements FormatElement { sb.append(work.getDayFromDate(date, TextStyle.FULL).toLowerCase(Locale.ROOT)); } }, + DS("", "The month as a decimal number with suffix. (1st-31th)") { + @Override public void format(StringBuilder sb, Date date) { + final Calendar calendar = Work.get().calendar; + calendar.setTime(date); + int d = calendar.get(Calendar.DAY_OF_MONTH); + sb.append(String.format(Locale.ROOT, "%d%s", d, getNumericSuffix(d))); + } + }, + D1("dd", "The day of the month as a decimal number (1-31)") { + @Override public void format(StringBuilder sb, Date date) { + final Calendar calendar = Work.get().calendar; + calendar.setTime(date); + sb.append(String.format(Locale.ROOT, "%d", calendar.get(Calendar.DAY_OF_MONTH))); + } + }, DD("dd", "The day of the month as a decimal number (01-31)") { @Override public void format(StringBuilder sb, Date date) { final Calendar calendar = Work.get().calendar; @@ -221,6 +245,33 @@ public enum FormatElementEnum implements FormatElement { sb.append(String.format(Locale.ROOT, "%02d", calendar.get(Calendar.WEEK_OF_YEAR))); } }, + V("", "The number of the week (Monday as the first day of the week)," + + "If the first day of the week belongs to the previous year, " + + "calculate the week of the previous year" + + "as a decimal number (01-53)") { + @Override public void format(StringBuilder sb, Date date) { + final Calendar calendar = Work.get().calendar; + calendar.setTime(date); + calendar.setFirstDayOfWeek(Calendar.SUNDAY); + // Day of year of the first Sunday of the year + int minimalDaysInFirstWeek = + getFirstWeekdayOfYear(DayOfWeek.MONDAY, calendar.get(Calendar.YEAR)); + calendar.setMinimalDaysInFirstWeek(minimalDaysInFirstWeek); + sb.append(String.format(Locale.ROOT, "%02d", calendar.get(Calendar.WEEK_OF_YEAR))); + } + }, + v("", "The number of the week (Sunday as the first day of the week)," + + "If the first day of the week belongs to the previous year, " + + "calculate the week of the previous year" + + "as a decimal number (01-53)") { + @Override public void format(StringBuilder sb, Date date) { + final Calendar calendar = Work.get().calendar; + calendar.setTime(date); + calendar.setFirstDayOfWeek(Calendar.MONDAY); + calendar.setMinimalDaysInFirstWeek(4); + sb.append(String.format(Locale.ROOT, "%02d", calendar.get(Calendar.WEEK_OF_YEAR))); + } + }, MI("m", "The minute as a decimal number (00-59)") { @Override public void format(StringBuilder sb, Date date) { final Calendar calendar = Work.get().calendar; @@ -326,7 +377,16 @@ public enum FormatElementEnum implements FormatElement { sb.append(String.format(Locale.ROOT, "%03d", calendar.get(Calendar.MILLISECOND))); } }, - SS("s", "The second as a decimal number (00-60)") { + MCS("", "The microsecond as a decimal number (000000-999999)") { + @Override public void format(StringBuilder sb, Date date) { + final Calendar calendar = Work.get().calendar; + calendar.setTime(date); + // It exceeds the precision of Calendar, we fill it with 0. + // So the actual range is (000000-999000) + sb.append(String.format(Locale.ROOT, "%06d", calendar.get(Calendar.MILLISECOND) * 1000)); + } + }, + SS("s", "The second as a decimal number (00-59)") { @Override public void format(StringBuilder sb, Date date) { final Calendar calendar = Work.get().calendar; calendar.setTime(date); @@ -369,10 +429,31 @@ public enum FormatElementEnum implements FormatElement { @Override public void format(StringBuilder sb, Date date) { final Calendar calendar = Work.get().calendar; calendar.setTime(date); + calendar.setMinimalDaysInFirstWeek(1); calendar.setFirstDayOfWeek(Calendar.SUNDAY); sb.append(String.format(Locale.ROOT, "%02d", calendar.get(Calendar.WEEK_OF_YEAR))); } }, + WW1("w", "The week number of the year (Sunday as the first day of the week) as a decimal " + + "number (00-53)") { + @Override public void format(StringBuilder sb, Date date) { + final Calendar calendar = Work.get().calendar; + calendar.setTime(date); + calendar.setMinimalDaysInFirstWeek(1); + calendar.setFirstDayOfWeek(Calendar.SUNDAY); + sb.append(String.format(Locale.ROOT, "%02d", calendar.get(Calendar.WEEK_OF_YEAR) - 1)); + } + }, + WW2("", "The week number of the year (Monday as the first day of the week) as a decimal " + + "number (01-53)") { + @Override public void format(StringBuilder sb, Date date) { + final Calendar calendar = Work.get().calendar; + calendar.setTime(date); + calendar.setMinimalDaysInFirstWeek(1); + calendar.setFirstDayOfWeek(Calendar.MONDAY); + sb.append(String.format(Locale.ROOT, "%02d", calendar.get(Calendar.WEEK_OF_YEAR))); + } + }, Y("y", "Last digit of year") { @Override public void format(StringBuilder sb, Date date) { final Work work = Work.get(); @@ -399,6 +480,27 @@ public enum FormatElementEnum implements FormatElement { sb.append(work.yyyyFormat.format(date)); } }, + WFY("", "The year for the week where Sunday is the first day of the week") { + @Override public void format(StringBuilder sb, Date date) { + final Calendar calendar = Work.get().calendar; + calendar.setTime(date); + calendar.setFirstDayOfWeek(Calendar.SUNDAY); + // Day of year of the first Sunday of the year + int minimalDaysInFirstWeek = + getFirstWeekdayOfYear(DayOfWeek.MONDAY, calendar.get(Calendar.YEAR)); + calendar.setMinimalDaysInFirstWeek(minimalDaysInFirstWeek); + sb.append(String.format(Locale.ROOT, "%04d", calendar.getWeekYear())); + } + }, + WFY0("", "The year for the week where Monday is the first day of the week") { + @Override public void format(StringBuilder sb, Date date) { + final Calendar calendar = Work.get().calendar; + calendar.setTime(date); + calendar.setFirstDayOfWeek(Calendar.MONDAY); + calendar.setMinimalDaysInFirstWeek(4); + sb.append(String.format(Locale.ROOT, "%04d", calendar.getWeekYear())); + } + }, pctY("yyyy", "The year with century as a decimal number") { @Override public void format(StringBuilder sb, Date date) { final Calendar calendar = Work.get().calendar; @@ -464,4 +566,31 @@ private String getDayFromDate(Date date, TextStyle style) { return ld.getDayOfWeek().getDisplayName(style, Locale.ENGLISH); } } + + /** Calculate what day of the year the first weekdayName of the given year is. */ + private static int getFirstWeekdayOfYear(DayOfWeek weekdayName, int year) { + return LocalDate.of(year, 1, 1) + .with(ChronoField.DAY_OF_WEEK, weekdayName.getValue()) + .getDayOfYear(); + } + + /** Util to return the suffix of numeric. */ + private static String getNumericSuffix(int numeric) { + String outputSuffix; + switch (numeric) { + case 1: + outputSuffix = "st"; + break; + case 2: + outputSuffix = "nd"; + break; + case 3: + outputSuffix = "rd"; + break; + default: + outputSuffix = "th"; + break; + } + return outputSuffix; + } } diff --git a/core/src/main/java/org/apache/calcite/util/format/FormatModels.java b/core/src/main/java/org/apache/calcite/util/format/FormatModels.java index a46b091ab826..9f7e9c1c196d 100644 --- a/core/src/main/java/org/apache/calcite/util/format/FormatModels.java +++ b/core/src/main/java/org/apache/calcite/util/format/FormatModels.java @@ -33,9 +33,12 @@ import static org.apache.calcite.util.format.FormatElementEnum.AM_PM; import static org.apache.calcite.util.format.FormatElementEnum.CC; import static org.apache.calcite.util.format.FormatElementEnum.D; +import static org.apache.calcite.util.format.FormatElementEnum.D0; +import static org.apache.calcite.util.format.FormatElementEnum.D1; import static org.apache.calcite.util.format.FormatElementEnum.DAY; import static org.apache.calcite.util.format.FormatElementEnum.DD; import static org.apache.calcite.util.format.FormatElementEnum.DDD; +import static org.apache.calcite.util.format.FormatElementEnum.DS; import static org.apache.calcite.util.format.FormatElementEnum.DY; import static org.apache.calcite.util.format.FormatElementEnum.Day; import static org.apache.calcite.util.format.FormatElementEnum.Dy; @@ -52,6 +55,7 @@ import static org.apache.calcite.util.format.FormatElementEnum.HH12; import static org.apache.calcite.util.format.FormatElementEnum.HH24; import static org.apache.calcite.util.format.FormatElementEnum.IW; +import static org.apache.calcite.util.format.FormatElementEnum.MCS; import static org.apache.calcite.util.format.FormatElementEnum.MI; import static org.apache.calcite.util.format.FormatElementEnum.MM; import static org.apache.calcite.util.format.FormatElementEnum.MON; @@ -64,8 +68,13 @@ import static org.apache.calcite.util.format.FormatElementEnum.SS; import static org.apache.calcite.util.format.FormatElementEnum.SSSSS; import static org.apache.calcite.util.format.FormatElementEnum.TZR; +import static org.apache.calcite.util.format.FormatElementEnum.V; import static org.apache.calcite.util.format.FormatElementEnum.W; +import static org.apache.calcite.util.format.FormatElementEnum.WFY; +import static org.apache.calcite.util.format.FormatElementEnum.WFY0; import static org.apache.calcite.util.format.FormatElementEnum.WW; +import static org.apache.calcite.util.format.FormatElementEnum.WW1; +import static org.apache.calcite.util.format.FormatElementEnum.WW2; import static org.apache.calcite.util.format.FormatElementEnum.Y; import static org.apache.calcite.util.format.FormatElementEnum.YY; import static org.apache.calcite.util.format.FormatElementEnum.YYY; @@ -77,6 +86,7 @@ import static org.apache.calcite.util.format.FormatElementEnum.mon; import static org.apache.calcite.util.format.FormatElementEnum.month; import static org.apache.calcite.util.format.FormatElementEnum.pctY; +import static org.apache.calcite.util.format.FormatElementEnum.v; import static java.util.Objects.requireNonNull; @@ -110,6 +120,14 @@ private FormatModels() { */ public static final FormatModel POSTGRESQL; + /** Format model for MySQL. + * + *

MySQL format element reference: + * + * MySQL Standard SQL Format Elements. + */ + public static final FormatModel MYSQL = createMysqlFormatModel(); + static { final Map map = new LinkedHashMap<>(); for (FormatElementEnum fe : FormatElementEnum.values()) { @@ -231,6 +249,47 @@ MI, literalElement(":"), SS, literalElement(" "), POSTGRESQL = create(map); } + private static FormatModel createMysqlFormatModel() { + final Map map = new LinkedHashMap<>(); + map.put("%a", Dy); + map.put("%b", Mon); + map.put("%c", MM); + map.put("%D", DS); + map.put("%d", DD); + map.put("%e", D1); + map.put("%f", MCS); + map.put("%H", HH24); + map.put("%h", HH12); + map.put("%I", HH12); + map.put("%i", MI); + map.put("%j", DDD); + map.put("%k", HH24); + map.put("%l", HH12); + map.put("%M", Month); + map.put("%m", MM); + map.put("%p", PM); + map.put("%r", + compositeElement("The date representation in hh:mm:ss am/pm format.", + HH12, literalElement(":"), MI, literalElement(":"), SS, literalElement(" "), PM)); + map.put("%S", SS); + map.put("%s", SS); + map.put("%T", + compositeElement("The date representation in hh:mm:ss am/pm format.", + HH24, literalElement(":"), MI, literalElement(":"), SS)); + map.put("%U", WW1); + map.put("%u", WW2); + map.put("%V", V); + map.put("%v", v); + map.put("%W", Day); + map.put("%w", D0); + map.put("%X", WFY); + map.put("%x", WFY0); + map.put("%Y", YYYY); + map.put("%y", YY); + map.put("%%", literalElement("%")); + return create(map); + } + /** * Generates a {@link Pattern} using the keys of a {@link FormatModel} element * map. This pattern is used in {@link FormatModel#parse(String)} to help diff --git a/core/src/test/java/org/apache/calcite/util/format/FormatElementEnumTest.java b/core/src/test/java/org/apache/calcite/util/format/FormatElementEnumTest.java index c77baaeaaf01..2117264abbd7 100644 --- a/core/src/test/java/org/apache/calcite/util/format/FormatElementEnumTest.java +++ b/core/src/test/java/org/apache/calcite/util/format/FormatElementEnumTest.java @@ -47,6 +47,24 @@ class FormatElementEnumTest { assertFormatElement(FormatElementEnum.D, "2014-09-30T10:00:00Z", "3"); } + @Test void testD0() { + assertFormatElement(FormatElementEnum.D0, "2014-09-30T10:00:00Z", "2"); + } + + @Test void testDS() { + assertFormatElement(FormatElementEnum.DS, "2014-09-01T10:00:00Z", "1st"); + assertFormatElement(FormatElementEnum.DS, "2014-09-02T10:00:00Z", "2nd"); + assertFormatElement(FormatElementEnum.DS, "2014-09-03T10:00:00Z", "3rd"); + assertFormatElement(FormatElementEnum.DS, "2014-09-04T10:00:00Z", "4th"); + assertFormatElement(FormatElementEnum.DS, "2014-09-30T10:00:00Z", "30th"); + } + + @Test void testMCS() { + assertFormatElement(FormatElementEnum.MCS, "2014-09-01T10:00:00Z", "000000"); + assertFormatElement(FormatElementEnum.MCS, "2014-09-01T10:00:13.123456Z", "123000"); + assertFormatElement(FormatElementEnum.MCS, "2014-09-01T10:00:13.123000Z", "123000"); + } + @Test void testDD() { assertFormatElement(FormatElementEnum.DD, "2014-09-30T10:00:00Z", "30"); } @@ -144,6 +162,30 @@ class FormatElementEnumTest { assertFormatElement(FormatElementEnum.YYYY, "2014-09-30T10:00:00Z", "2014"); } + @Test void testV() { + assertFormatElement(FormatElementEnum.V, "1997-01-01T10:23:00Z", "52"); + } + + @Test void testv() { + assertFormatElement(FormatElementEnum.v, "1997-01-01T10:23:00Z", "01"); + } + + @Test void testWW1() { + assertFormatElement(FormatElementEnum.WW1, "1997-01-01T10:23:00Z", "00"); + } + + @Test void testWW2() { + assertFormatElement(FormatElementEnum.WW2, "1997-01-01T10:23:00Z", "01"); + } + + @Test void testWFY() { + assertFormatElement(FormatElementEnum.WFY, "1997-01-04T10:00:00Z", "1996"); + } + + @Test void testWFY0() { + assertFormatElement(FormatElementEnum.WFY0, "1997-01-04T10:00:00Z", "1997"); + } + private void assertFormatElement(FormatElementEnum formatElement, String date, String expected) { StringBuilder ts = new StringBuilder(); formatElement.format(ts, Date.from(Instant.parse(date))); diff --git a/site/_docs/reference.md b/site/_docs/reference.md index 0288d1f88d99..f040452714c6 100644 --- a/site/_docs/reference.md +++ b/site/_docs/reference.md @@ -2944,6 +2944,7 @@ In the following: | b | TIME_SUB(time, interval) | Returns the TIME value that is *interval* before *time* | b | TIME_TRUNC(time, timeUnit) | Truncates *time* to the granularity of *timeUnit*, rounding to the beginning of the unit | m o p r | TO_CHAR(timestamp, format) | Converts *timestamp* to a string using the format *format* +| m | DATE_FORMAT(timestamp, format) | Converts *timestamp* to a string using the format *format* | b | TO_CODE_POINTS(string) | Converts *string* to an array of integers that represent code points or extended ASCII character values | o p r | TO_DATE(string, format) | Converts *string* to a date using the format *format* | o p r | TO_TIMESTAMP(string, format) | Converts *string* to a timestamp using the format *format* diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java index 1fa79f309976..3f64f47b872e 100644 --- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java +++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java @@ -52,6 +52,7 @@ import org.apache.calcite.sql.fun.SqlStdOperatorTable; import org.apache.calcite.sql.parser.SqlParseException; import org.apache.calcite.sql.parser.SqlParserPos; +import org.apache.calcite.sql.parser.StringAndPos; import org.apache.calcite.sql.pretty.SqlPrettyWriter; import org.apache.calcite.sql.test.AbstractSqlTester; import org.apache.calcite.sql.test.SqlOperatorFixture; @@ -4919,7 +4920,8 @@ void testBitGetFunc(SqlOperatorFixture f, String functionName) { } @Test void testToCharPg() { - final SqlOperatorFixture f = fixture().withLibrary(SqlLibrary.POSTGRESQL); + final SqlOperatorFixture f = fixture() + .withTester(t -> TESTER).withLibrary(SqlLibrary.POSTGRESQL); f.setFor(SqlLibraryOperators.TO_CHAR_PG); final Locale originalLocale = Locale.getDefault(); @@ -5185,6 +5187,60 @@ void testBitGetFunc(SqlOperatorFixture f, String functionName) { } } + /** Test case for + * [CALCITE-6551] + * Add DATE_FORMAT function (enabled in MySQL library). */ + @Test void testDateFormat() { + final SqlOperatorFixture f = fixture() + .withTester(t -> TESTER).withLibrary(SqlLibrary.MYSQL); + f.setFor(SqlLibraryOperators.DATE_FORMAT); + final Locale originalLocale = Locale.getDefault(); + + try { + Locale.setDefault(Locale.US); + f.checkString("date_format(timestamp '2009-10-04 22:23:00', '%W %M %Y')", + "Sunday October 2009", + "VARCHAR NOT NULL"); + f.checkString("date_format(timestamp '2009-10-04 22:23:00', '%H:%i:%s')", + "22:23:00", + "VARCHAR NOT NULL"); + f.checkString("date_format(timestamp '1900-10-04 22:23:00', '%D %y %a %d %m %b %j')", + "4th 00 Thu 04 10 Oct 277", + "VARCHAR NOT NULL"); + f.checkString("date_format(timestamp '1997-10-04 22:23:00', '%H %k %I %r %T %S %w')", + "22 22 10 10:23:00 PM 22:23:00 00 6", + "VARCHAR NOT NULL"); + f.checkString("date_format(timestamp '1999-01-01', '%X %V')", + "1998 52", + "VARCHAR NOT NULL"); + f.checkString("date_format(timestamp '1997-10-04 22:23:00.000000', '%c %D %e %f %h')", + "10 4th 4 000000 10", + "VARCHAR NOT NULL"); + f.checkString("date_format(timestamp '1997-01-04 22:23:00', '%w %U %u %V %v %X %x')", + "6 00 01 52 01 1996 1997", + "VARCHAR NOT NULL"); + f.checkString("date_format(timestamp '1997-01-05 22:23:00', '%w %U %u %V %v %X %x')", + "0 01 01 01 01 1997 1997", + "VARCHAR NOT NULL"); + f.checkString("date_format(timestamp '1997-01-06 22:23:00', '%w %U %u %V %v %X %x')", + "1 01 02 01 02 1997 1997", + "VARCHAR NOT NULL"); + f.checkString("date_format(timestamp '1997-10-04 22:23:00', '%V %v %X %x %%')", + "39 40 1997 1997 %", + "VARCHAR NOT NULL"); + f.checkString("date_format(timestamp '1997-10-04 22:23:00', '%l %p %U %u')", + "10 PM 39 40", + "VARCHAR NOT NULL"); + f.checkQueryFails(StringAndPos.of("date_format(timestamp '000-00-01 22:23:00', '%d')"), + "Non-query expression encountered in illegal context"); + f.checkQueryFails(StringAndPos.of("date_format(timestamp '2006-06-00', '%d')"), + "Non-query expression encountered in illegal context"); + f.checkNull("date_format(timestamp '2022-06-03 12:15:48.678', NULL)"); + } finally { + Locale.setDefault(originalLocale); + } + } + @Test void testToDate() { final SqlOperatorFixture f0 = fixture().setFor(SqlLibraryOperators.TO_DATE);