{"record":{"id":"edafbeb8099d0c1c","repo":"langgenius/dify","slug":"str-e-edafbe","errorCode":null,"errorMessage":"{str(e)}","messagePattern":"\\{str\\(e\\)\\}","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"api/controllers/console/app/statistic.py","lineNumber":183,"sourceCode":"        sql_query = f\"\"\"SELECT\n    {converted_created_at} AS date,\n    COUNT(*) AS message_count\nFROM\n    messages\nWHERE\n    app_id = :app_id\n    AND invoke_from != :invoke_from\"\"\"\n        assert account.timezone is not None\n        arg_dict: dict[str, object] = {\n            \"tz\": account.timezone,\n            \"app_id\": app_model.id,\n            \"invoke_from\": InvokeFrom.DEBUGGER,\n        }\n\n        try:\n            start_datetime_utc, end_datetime_utc = parse_time_range(req_data.start, req_data.end, account.timezone)\n        except ValueError as e:\n            abort(400, description=str(e))\n\n        if start_datetime_utc:\n            sql_query += \" AND created_at >= :start\"\n            arg_dict[\"start\"] = start_datetime_utc\n\n        if end_datetime_utc:\n            sql_query += \" AND created_at < :end\"\n            arg_dict[\"end\"] = end_datetime_utc\n\n        sql_query += \" GROUP BY date ORDER BY date\"\n\n        response_data = []\n\n        with db.engine.begin() as conn:\n            rs = conn.execute(sa.text(sql_query), arg_dict)\n            for i in rs:\n                response_data.append({\"date\": str(i.date), \"message_count\": i.message_count})\n","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/langgenius/dify/blob/ef8544b173fd6cd7a8e71df2cab576e52bebbfbc/api/controllers/console/app/statistic.py#L165-L201","documentation":"Flask `abort(400, description=str(e))` (HTTP 400) at api/controllers/console/app/statistic.py:183 in the daily/period statistic endpoint. `parse_time_range` is called with `account.timezone` (asserted non-None just above) and the request's `start`/`end`. ValueError becomes 400; the SQL parameters (`tz`, `app_id`, `invoke_from`) are only appended to `arg_dict` after a successful parse.","triggerScenarios":"The app statistics endpoint receives `start`/`end` that do not match `YYYY-MM-DD HH:MM`, reference an unknown timezone, or define a reversed range (start > end).","commonSituations":"Dashboard requests an analytics window using a localized ISO string, or the user account's timezone is misconfigured to a non-IANA value.","solutions":["Format `start`/`end` as `YYYY-MM-DD HH:MM`.","Set `account.timezone` to a valid IANA zone.","Ensure `start <= end`."],"exampleFix":"// before\nGET /apps/<id>/statistics/day-conversations?start=2024-01-01\n// after\nGET /apps/<id>/statistics/day-conversations?start=2024-01-01%2000:00&end=2024-02-01%2000:00","handlingStrategy":"validation","validationCode":"import datetime, pytz\nTIME_FMT = \"%Y-%m-%d %H:%M\"\npytz.timezone(account.timezone)\nif req_data.start: datetime.datetime.strptime(req_data.start, TIME_FMT)\nif req_data.end:   datetime.datetime.strptime(req_data.end, TIME_FMT)\nif req_data.start and req_data.end:\n    assert datetime.datetime.strptime(req_data.start, TIME_FMT) <= datetime.datetime.strptime(req_data.end, TIME_FMT)","typeGuard":"import datetime, re\n_RE = re.compile(r\"^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}$\")\ndef valid_stat_range(s, e) -> bool:\n    return all(v is None or (_RE.match(v) and _ok(v)) for v in (s, e))\ndef _ok(v):\n    try:\n        datetime.datetime.strptime(v, \"%Y-%m-%d %H:%M\")\n        return True\n    except ValueError:\n        return False","tryCatchPattern":"try:\n    resp = client.get(f\"/console/api/apps/{app_id}/statistics/day-conversations\", params=p)\nexcept HTTPError as err:\n    if err.response.status_code == 400:\n        raise ValueError(f\"bad statistics range: {err.response.text}\") from err\n    raise","preventionTips":["Centralize the `YYYY-MM-DD HH:MM` formatting in a shared analytics client.","Surface invalid timezone errors at profile-save time, not at query time.","Ensure the dashboard cannot send start > end by clamping the picker."],"tags":["validation","datetime","http-400","statistics","app"],"backgroundTag":null,"analyzedSha":"ef8544b173fd6cd7a8e71df2cab576e52bebbfbc","analyzedAt":"2026-08-12T05:15:17.394Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}