benbjohnson/litestream · error

could not parse time: %s

Error message

could not parse time: %s

What it means

parseTimeValue() in vfs.go failed to produce a usable time for a PRAGMA litestream_time value. The value passed the RFC3339 attempts and dateparser.Parse() returned no error, but the resulting time.Time was the zero value, meaning dateparser recognized the string grammatically (or silently failed to match) but could not resolve it to an actual date. The library throws this because a zero time is not a valid time-travel target, so it is rejected with the raw input echoed back.

Source

Thrown at vfs.go:2419

	if t, err := time.Parse(time.RFC3339Nano, value); err == nil {
		return t, nil
	}

	// Try RFC3339 (without nanoseconds)
	if t, err := time.Parse(time.RFC3339, value); err == nil {
		return t, nil
	}

	// Fall back to dateparser for relative expressions
	cfg := &dateparser.Configuration{
		CurrentTime: time.Now().UTC(),
	}
	result, err := dateparser.Parse(cfg, value)
	if err != nil {
		return time.Time{}, fmt.Errorf("invalid timestamp (expected RFC3339 or relative time like '5 minutes ago'): %s", value)
	}
	if result.Time.IsZero() {
		return time.Time{}, fmt.Errorf("could not parse time: %s", value)
	}
	return result.Time.UTC(), nil
}

// FileControl handles file control operations, specifically PRAGMA commands for time travel.
func (f *VFSFile) FileControl(op int, pragmaName string, pragmaValue *string) (*string, error) {
	const SQLITE_FCNTL_PRAGMA = 14

	if op != SQLITE_FCNTL_PRAGMA {
		return nil, fmt.Errorf("unsupported file control op: %d", op)
	}

	name := strings.ToLower(pragmaName)

	f.logger.Debug("file control", "pragma", name, "value", pragmaValue)

	switch name {
	case "litestream_txid":

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Wrap the input in try/catch or error-check and print the exact value that failed
  2. Rewrite the value as RFC3339, e.g. '2024-01-01T10:00:00Z' with a 'T' separator and timezone
  3. Use a supported relative expression like '5 minutes ago' or '2 hours ago'
  4. Use the keyword 'latest' to reset time travel to the newest state
  5. Validate the timestamp string in application code before issuing the PRAGMA

Example fix

// before
PRAGMA litestream_time = '2024-01-01 10:00:00';
// after
PRAGMA litestream_time = '2024-01-01T10:00:00Z';
// or relative:
PRAGMA litestream_time = '5 minutes ago';
Defensive patterns

Strategy: validation

Validate before calling

// Go: validate before issuing PRAGMA litestream_time
func validTimeTravelValue(v string) bool {
    if strings.EqualFold(v, "latest") { return true }
    if _, err := time.Parse(time.RFC3339Nano, v); err == nil { return true }
    if _, err := time.Parse(time.RFC3339, v); err == nil { return true }
    // relative forms: e.g. '5 minutes ago', '2 hours ago'
    rel := regexp.MustCompile(`(?i)^\d+\s+(second|minute|hour|day|week|month|year)s?\s+ago$`)
    return rel.MatchString(v)
}

Type guard

func isZeroTime(t time.Time) bool { return t.IsZero() }

Try / catch

// driver-level
_, err := db.Exec("PRAGMA litestream_time = ?", value)
if err != nil && strings.Contains(err.Error(), "could not parse time") {
    // fall back to a safe default
    _, err = db.Exec("PRAGMA litestream_time = 'latest'")
}

Prevention

When it happens

Trigger: A SQLite connection executes `PRAGMA litestream_time='<value>'` where <value> is not an RFC3339 timestamp and is not a relative expression dateparser can resolve, yet dateparser returns err==nil with a zero Time (e.g. empty-adjacent strings, strings with only a timezone or day name it half-recognizes, or locale-dependent formats it parses to nothing).

Common situations: Developers pass human-friendly but ambiguous values like 'yesterday', 'last week', bare dates ('2024-01-01'), or misspell RFC3339 timestamps ('2024-01-01 10:00:00' with a space instead of T); dateparser partially matches them and yields a zero time instead of an error, surfacing this message rather than the clearer 'invalid timestamp' error.

Related errors


AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06). Data as JSON: /api/errors/e912547c2d1208f1. Report an issue: GitHub.