prestodb/presto · error · IllegalArgumentException

Invalid time '%s'

Error message

Invalid time '%s'

What it means

DateTimeUtils.timeHasTimeZone parses a TIME literal to determine whether it includes a time-zone offset. When the value matches neither the time-with-tz nor the time-without-tz formatter, the underlying parse exception is replaced by an IllegalArgumentException formatting the bad value into "Invalid time '%s'".

Source

Thrown at presto-main-base/src/main/java/com/facebook/presto/util/DateTimeUtils.java:420

    public static String printTimeWithoutTimeZone(TimeZoneKey timeZoneKey, long value)
    {
        return TIME_FORMATTER.withZone(getDateTimeZone(timeZoneKey)).print(value);
    }

    public static boolean timeHasTimeZone(String value)
    {
        try {
            try {
                parseTimeWithTimeZone(value);
                return true;
            }
            catch (RuntimeException e) {
                parseTimeWithoutTimeZone(value);
                return false;
            }
        }
        catch (RuntimeException e) {
            throw new IllegalArgumentException(format("Invalid time '%s'", value));
        }
    }

    private static final int YEAR_FIELD = 0;
    private static final int MONTH_FIELD = 1;
    private static final int DAY_FIELD = 3;
    private static final int HOUR_FIELD = 4;
    private static final int MINUTE_FIELD = 5;
    private static final int SECOND_FIELD = 6;
    private static final int MILLIS_FIELD = 7;

    private static final PeriodFormatter INTERVAL_DAY_SECOND_FORMATTER = cretePeriodFormatter(IntervalField.DAY, IntervalField.SECOND);
    private static final PeriodFormatter INTERVAL_DAY_MINUTE_FORMATTER = cretePeriodFormatter(IntervalField.DAY, IntervalField.MINUTE);
    private static final PeriodFormatter INTERVAL_DAY_HOUR_FORMATTER = cretePeriodFormatter(IntervalField.DAY, IntervalField.HOUR);
    private static final PeriodFormatter INTERVAL_DAY_FORMATTER = cretePeriodFormatter(IntervalField.DAY, IntervalField.DAY);

    private static final PeriodFormatter INTERVAL_HOUR_SECOND_FORMATTER = cretePeriodFormatter(IntervalField.HOUR, IntervalField.SECOND);
    private static final PeriodFormatter INTERVAL_HOUR_MINUTE_FORMATTER = cretePeriodFormatter(IntervalField.HOUR, IntervalField.MINUTE);

View on GitHub (pinned to 55bb57d202)

Solutions

  1. Use the canonical format 'HH:mm:ss[.fff]' e.g. '12:30:00.000' or with offset '12:30:00 +01:00'
  2. Use try_cast(value AS TIME) to surface NULLs instead of exceptions
  3. Trim/normalize the string before parsing and validate with a regex like ^\\d{2}:\\d{2}:\\d{2}
  4. If you have a full timestamp, strip the date portion first

Example fix

-- before
SELECT TIME '12:30 PM';
-- after
SELECT TIME '12:30:00.000';
Defensive patterns

Strategy: validation

Validate before calling

boolean ok = value != null && value.matches("\\d{1,2}:\\d{2}:\\d{2}(\\.\\d{1,3})?([+-]\\d{2}:?\\d{2})? *$");

Try / catch

try { DateTimeUtils.timeHasTimeZone(value); } catch (IllegalArgumentException e) { return null; }

Prevention

When it happens

Trigger: Passing a malformed time string (e.g. '25:00:00', '12:00', 'noon', or a full timestamp string) to APIs that classify time literals, such as parsing TIME literals in SQL or type coercion paths.

Common situations: Users writing '12:30 PM' style times in SQL; accidentally passing a timestamp where a time is expected; locale formats with AM/PM or fractional fields in the wrong place; leading/trailing whitespace.

Related errors


AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04). Data as JSON: /api/errors/20da193082c15b51. Report an issue: GitHub.