Skip to content

Commit

Permalink
Merge pull request #3759 from katzyn/formatdatetime
Browse files Browse the repository at this point in the history
Improve support of time values in FORMATDATETIME function
  • Loading branch information
katzyn committed Mar 18, 2023
2 parents 14bd90c + 6a146ce commit 2113b32
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 5 deletions.
12 changes: 12 additions & 0 deletions h2/src/docsrc/html/changelog.html
Expand Up @@ -21,6 +21,18 @@ <h1>Change Log</h1>

<h2>Next Version (unreleased)</h2>
<ul>
<li>Issue #3757: FORMATDATETIME function doesn't handle time with time zone properly
</li>
<li>PR #3756: Limit the row list allocation based on the row count
</li>
<li>PR #3753: Add missing NEWSEQUENTIALID function in MSSQLServer mode
</li>
<li>Issue #3730: FILE_READ from JAR filesystem on classpath results in file of length 0
</li>
<li>PR #3749: Fix min/max description for sequences
</li>
<li>PR #3739: Fix count(*) for linked table to Oracle
</li>
<li>Issue #3731: Division result exceeds numeric precision constraint
</li>
<li>PR #3718: Add test coverage for JDK 17
Expand Down
45 changes: 40 additions & 5 deletions h2/src/main/org/h2/expression/function/DateTimeFormatFunction.java
Expand Up @@ -5,17 +5,20 @@
*/
package org.h2.expression.function;

import java.time.DateTimeException;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalQueries;
import java.time.zone.ZoneRules;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Objects;
Expand Down Expand Up @@ -162,7 +165,23 @@ public static String formatDateTime(SessionLocal session, Value date, String for
CacheValue formatAndZone = getDateFormat(format, locale, timeZone);
ZoneId zoneId = formatAndZone.zoneId;
TemporalAccessor value;
if (date instanceof ValueTimestampTimeZone) {
switch (date.getValueType()) {
case Value.DATE:
case Value.TIMESTAMP:
value = JSR310Utils.valueToLocalDateTime(date, session)
.atZone(zoneId != null ? zoneId : ZoneId.of(session.currentTimeZone().getId()));
break;
case Value.TIME: {
LocalTime time = JSR310Utils.valueToLocalTime(date, session);
value = zoneId != null ? time.atOffset(getTimeOffset(zoneId, timeZone)) : time;
break;
}
case Value.TIME_TZ: {
OffsetTime time = JSR310Utils.valueToOffsetTime(date, session);
value = zoneId != null ? time.withOffsetSameInstant(getTimeOffset(zoneId, timeZone)) : time;
break;
}
case Value.TIMESTAMP_TZ: {
OffsetDateTime dateTime = JSR310Utils.valueToOffsetDateTime(date, session);
ZoneId zoneToSet;
if (zoneId != null) {
Expand All @@ -172,11 +191,27 @@ public static String formatDateTime(SessionLocal session, Value date, String for
zoneToSet = ZoneId.ofOffset(offset.getTotalSeconds() == 0 ? "UTC" : "GMT", offset);
}
value = dateTime.atZoneSameInstant(zoneToSet);
} else {
LocalDateTime dateTime = JSR310Utils.valueToLocalDateTime(date, session);
value = dateTime.atZone(zoneId != null ? zoneId : ZoneId.of(session.currentTimeZone().getId()));
break;
}
default:
throw DbException.getInvalidValueException("dateTime", date.getTraceSQL());
}
try {
return formatAndZone.formatter.format(value);
} catch (DateTimeException e) {
throw DbException.getInvalidValueException(e, "format", format);
}
}

private static ZoneOffset getTimeOffset(ZoneId zoneId, String timeZone) {
if (zoneId instanceof ZoneOffset) {
return (ZoneOffset) zoneId;
}
ZoneRules zoneRules = zoneId.getRules();
if (!zoneRules.isFixedOffset()) {
throw DbException.getInvalidValueException("timeZone", timeZone);
}
return formatAndZone.formatter.format(value);
return zoneRules.getOffset(Instant.EPOCH);
}

/**
Expand Down
12 changes: 12 additions & 0 deletions h2/src/main/org/h2/message/DbException.java
Expand Up @@ -298,6 +298,18 @@ public static DbException getInvalidValueException(String param, Object value) {
return get(INVALID_VALUE_2, value == null ? "null" : value.toString(), param);
}

/**
* Gets a SQL exception meaning this value is invalid.
*
* @param cause the cause of the exception
* @param param the name of the parameter
* @param value the value passed
* @return the exception
*/
public static DbException getInvalidValueException(Throwable cause, String param, Object value) {
return get(INVALID_VALUE_2, cause, value == null ? "null" : value.toString(), param);
}

/**
* Gets a SQL exception meaning this value is too long.
*
Expand Down
5 changes: 5 additions & 0 deletions h2/src/main/org/h2/res/help.csv
Expand Up @@ -5938,8 +5938,13 @@ Formats a date, time or timestamp as a string.
The most important format characters are:
y year, M month, d day, H hour, m minute, s second.
For details of the format, see ""java.time.format.DateTimeFormatter"".
Allowed format characters depend on data type of passed date/time value.

If timeZoneString is specified, it is used in formatted string if formatString has time zone.
For TIME and TIME WITH TIME ZONE values the specified time zone must have a fixed offset.

If TIME WITH TIME ZONE is passed and timeZoneString is specified,
the time is converted to the specified time zone offset and its UTC value is preserved.
If TIMESTAMP WITH TIME ZONE is passed and timeZoneString is specified,
the timestamp is converted to the specified time zone and its UTC value is preserved.

Expand Down
Expand Up @@ -23,3 +23,27 @@ SELECT FORMATDATETIME(TIMESTAMP WITH TIME ZONE '2010-05-06 07:08:09.123Z', 'yyyy

SELECT FORMATDATETIME(TIMESTAMP WITH TIME ZONE '2010-05-06 07:08:09.123+13:30', 'yyyy-MM-dd HH:mm:ss.SSS z');
>> 2010-05-06 07:08:09.123 GMT+13:30

SELECT FORMATDATETIME(TIME '10:15:20.123456789', 'HH:mm:ss.SSSSSSSSS');
>> 10:15:20.123456789

SELECT FORMATDATETIME(TIME '10:15:20.123456789', 'HH:mm:ss.SSS z', 'en', 'UTC-05');
>> 10:15:20.123 UTC-05:00

SELECT FORMATDATETIME(TIME '10:15:20.123456789', 'dd HH:mm:ss.SSS');
> exception INVALID_VALUE_2

SELECT FORMATDATETIME(TIME WITH TIME ZONE '03:04:05.123+13:30', 'HH:mm:ss.SSS z');
> exception INVALID_VALUE_2

SELECT FORMATDATETIME(TIME WITH TIME ZONE '03:04:05.123+13:30', 'HH:mm:ss.SSSx');
>> 03:04:05.123+1330

SELECT FORMATDATETIME(TIME WITH TIME ZONE '03:04:05.123+13:30', 'HH:mm:ss.SSSx');
>> 03:04:05.123+1330

SELECT FORMATDATETIME(TIME WITH TIME ZONE '03:04:05.123+13:30', 'HH:mm:ss.SSSx', 'en', 'Asia/Jakarta');
> exception INVALID_VALUE_2

SELECT FORMATDATETIME(TIME WITH TIME ZONE '03:04:05.123+13:30', 'HH:mm:ss.SSSx', 'en', 'UTC+12');
>> 01:34:05.123+12

0 comments on commit 2113b32

Please sign in to comment.