{"record":{"id":"c33c60389779a147","repo":"usememos/memos","slug":"timestamp-argument-must-be-an-rfc3339-string-or","errorCode":null,"errorMessage":"timestamp() argument must be an RFC3339 string or epoch int","messagePattern":"timestamp\\(\\) argument must be an RFC3339 string or epoch int","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"internal/filter/parser.go","lineNumber":609,"sourceCode":"func evaluateTimestamp(call *exprv1.Expr_Call) (int64, bool, error) {\n\tif len(call.Args) != 1 {\n\t\treturn 0, false, errors.New(\"timestamp() expects one argument\")\n\t}\n\tvalue, err := getConstValue(call.Args[0])\n\tif err != nil {\n\t\treturn 0, false, errors.Wrap(err, \"timestamp() only supports literal arguments\")\n\t}\n\tswitch v := value.(type) {\n\tcase string:\n\t\tts, err := time.Parse(time.RFC3339, v)\n\t\tif err != nil {\n\t\t\treturn 0, false, errors.Wrap(err, \"invalid timestamp literal\")\n\t\t}\n\t\treturn ts.Unix(), true, nil\n\tcase int64:\n\t\treturn v, true, nil\n\tdefault:\n\t\treturn 0, false, errors.New(\"timestamp() argument must be an RFC3339 string or epoch int\")\n\t}\n}\n\n// evaluateDuration folds duration(\"<go-duration>\") into a number of seconds.\nfunc evaluateDuration(call *exprv1.Expr_Call) (int64, bool, error) {\n\tif len(call.Args) != 1 {\n\t\treturn 0, false, errors.New(\"duration() expects one argument\")\n\t}\n\tvalue, err := getConstValue(call.Args[0])\n\tif err != nil {\n\t\treturn 0, false, errors.Wrap(err, \"duration() only supports literal arguments\")\n\t}\n\tstr, ok := value.(string)\n\tif !ok {\n\t\treturn 0, false, errors.New(\"duration() argument must be a string\")\n\t}\n\td, err := time.ParseDuration(str)\n\tif err != nil {","sourceCodeStart":591,"sourceCodeEnd":627,"githubUrl":"https://github.com/usememos/memos/blob/14d757ce1fb31c78590f374bc042f8dbedbc20d7/internal/filter/parser.go#L591-L627","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\ncreated_ts > timestamp(1704067200000.0)\n\n// after\ncreated_ts > timestamp(1704067200)","handlingStrategy":"type-guard","validationCode":"// Go: validate the literal kind before embedding\nfunc validTimestampArg(v any) bool { _, s := v.(string); return s }\nfunc validEpoch(v any) bool { _, i := v.(int64); return i }","typeGuard":"const isRfc3339OrEpoch = (v: unknown): boolean =>\n  (typeof v === 'string' && !Number.isNaN(Date.parse(v)) && /T\\d\\d:\\d\\d:\\d\\d/.test(v)) ||\n  (typeof v === 'number' && Number.isInteger(v));","tryCatchPattern":null,"preventionTips":["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"],"tags":["cel","timestamp","type-mismatch","filter"],"backgroundTag":null,"analyzedSha":"14d757ce1fb31c78590f374bc042f8dbedbc20d7","analyzedAt":"2026-08-15T09:27:36.538Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}