usememos/memos · error
timestamp() argument must be an RFC3339 string or epoch int
Error message
timestamp() argument must be an RFC3339 string or epoch int
What it means
After arity checking, evaluateTimestamp accepts only an RFC3339-formatted string or an int64 epoch. Any other literal type (double, bool, uint beyond conversion) reaches the default branch and the filter is rejected because no SQL-comparable timestamp can be produced.
Source
Thrown at internal/filter/parser.go:609
func evaluateTimestamp(call *exprv1.Expr_Call) (int64, bool, error) {
if len(call.Args) != 1 {
return 0, false, errors.New("timestamp() expects one argument")
}
value, err := getConstValue(call.Args[0])
if err != nil {
return 0, false, errors.Wrap(err, "timestamp() only supports literal arguments")
}
switch v := value.(type) {
case string:
ts, err := time.Parse(time.RFC3339, v)
if err != nil {
return 0, false, errors.Wrap(err, "invalid timestamp literal")
}
return ts.Unix(), true, nil
case int64:
return v, true, nil
default:
return 0, false, errors.New("timestamp() argument must be an RFC3339 string or epoch int")
}
}
// evaluateDuration folds duration("<go-duration>") into a number of seconds.
func evaluateDuration(call *exprv1.Expr_Call) (int64, bool, error) {
if len(call.Args) != 1 {
return 0, false, errors.New("duration() expects one argument")
}
value, err := getConstValue(call.Args[0])
if err != nil {
return 0, false, errors.Wrap(err, "duration() only supports literal arguments")
}
str, ok := value.(string)
if !ok {
return 0, false, errors.New("duration() argument must be a string")
}
d, err := time.ParseDuration(str)
if err != nil {View on GitHub (pinned to 14d757ce1f)
Solutions
- Use a full RFC3339 string with time and zone: timestamp("2024-01-01T00:00:00Z")
- Use integer epoch seconds: timestamp(1704067200)
- Convert milliseconds to integer seconds before embedding
Example fix
// before created_ts > timestamp(1704067200000.0) // after created_ts > timestamp(1704067200)
Defensive patterns
Strategy: type-guard
Validate before calling
// Go: validate the literal kind before embedding
func validTimestampArg(v any) bool { _, s := v.(string); return s }
func validEpoch(v any) bool { _, i := v.(int64); return i } Type guard
const isRfc3339OrEpoch = (v: unknown): boolean => (typeof v === 'string' && !Number.isNaN(Date.parse(v)) && /T\d\d:\d\d:\d\d/.test(v)) || (typeof v === 'number' && Number.isInteger(v));
Prevention
- Use full RFC3339 strings with time and offset, or integer epoch seconds
- Convert millisecond timestamps to whole seconds before embedding
- Reject non-string/non-integer values in filter builders
When it happens
Trigger: timestamp(1.5), timestamp(true), or a date string in a non-RFC3339 format that still parses as a CEL literal but fails the type switch after string parsing is bypassed — specifically non-string non-int literals.
Common situations: Passing Unix milliseconds as a double (1704067200000.0), booleans from a buggy template, or assuming ISO dates without time component ("2024-01-01") are accepted — note bare "2024-01-01" fails earlier with 'invalid timestamp literal' instead.
Related errors
- text match argument must be a string
- matches argument must be a string
- timestamp() expects one argument
- duration() argument must be a string
- set operations require string elements
AI-assisted analysis of usememos/memos@14d757ce1f (2026-08-15).
Data as JSON: /api/errors/c33c60389779a147.
Report an issue: GitHub.