Documentsdify
Timestamp Management
Timestamp Management
Type
Topic
Status
Published
Created
Jul 8, 2026
Updated
Jul 8, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

Timestamp Management#

Dify's codebase uses two distinct mechanisms for storing timestamps — application-generated naive UTC datetimes and database-generated server timestamps — sometimes on the same columns. Understanding when each fires, and where they can diverge, is essential for debugging ordering bugs, updated_at drift, and DST-related parsing issues.


Core Utility: naive_utc_now()#

naive_utc_now() in api/libs/datetime_utils.py is the canonical Python-side timestamp source. It calls datetime.datetime.now(datetime.UTC) then strips the timezone with .replace(tzinfo=None), returning a naive UTC datetime. The underlying _now_func is a swappable callable specifically to allow time mocking in tests .

A companion, ensure_naive_utc(), normalizes any timezone-aware input to naive UTC before persistence. parse_time_range() handles user-submitted local time strings, converting them to UTC with DST-ambiguity and non-existent-time guard logic.


Model-Level Defaults: Two Patterns#

DefaultFieldsMixin / DefaultFieldsDCMixin (new-style models)#

Models that inherit from DefaultFieldsMixin or DefaultFieldsDCMixin get:

  • created_atdefault=naive_utc_now (Python-side) + server_default=func.current_timestamp() (DB-side fallback)
  • updated_at — same defaults, plus onupdate=func.current_timestamp()

The Python-side default fires when SQLAlchemy creates the INSERT statement; the server default is a fallback for raw SQL inserts that bypass the ORM.

Legacy Conversation and Message models#

The Conversation and Message models do not inherit DefaultFieldsMixin. Their created_at and updated_at columns are defined with only server_default=func.current_timestamp() and onupdate=func.current_timestamp() — no Python-side default. This means on INSERT the database clock sets the value, not the application clock.


Explicit Assignments in Task Pipelines#

Despite the onupdate=func.current_timestamp() directive on Message.updated_at, both task pipelines explicitly overwrite it with naive_utc_now() during message finalization:

ConversationService.rename() does the same for conversations: conversation.updated_at = naive_utc_now().

Similarly, MessageBasedAppGenerator._init_generate_records() explicitly sets conversation.updated_at = naive_utc_now() when an existing conversation gets a new message, rather than relying on the DB onupdate hook.

The rationale: SQLAlchemy's onupdate only fires if a column other than updated_at is also being changed in the same UPDATE. Explicit assignment guarantees the column is always written, avoiding a silent no-op when only updated_at itself needs refreshing.


Pitfalls and Discrepancies#

ScenarioSourceRisk
New-style models (use DefaultFieldsMixin)Python naive_utc_now()Application clock; consistent with Python comparisons
Legacy Conversation/Message on INSERTDB func.current_timestamp()DB clock may differ from app server clock by milliseconds to seconds
onupdate path (no explicit assignment)DB clockApp-side objects don't reflect the new value until re-fetched
Explicit naive_utc_now() assignment in pipelinesPythonConsistent, but bypasses onupdate; value already written as payload
parse_time_range() user inputpytz conversion to UTCDST edge cases handled; assumes naive timestamps already in UTC

Key hazard: Sorting or comparing timestamps across models that use different sources can produce off-by-milliseconds ordering surprises. If code reads message.updated_at immediately after the session.commit() without a refresh, the in-memory object may hold a stale value (the Python-assigned one) while the database has the onupdate-generated value — or vice versa.


Key Files#

FileRole
api/libs/datetime_utils.pynaive_utc_now(), ensure_naive_utc(), parse_time_range()
api/models/base.pyDefaultFieldsMixin, DefaultFieldsDCMixin — standard timestamp columns
api/models/model.pyConversation and Message legacy timestamp columns
api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.pyExplicit updated_at on EasyUI message save
api/core/app/apps/advanced_chat/generate_task_pipeline.pyExplicit updated_at on AdvancedChat message save
api/core/app/apps/message_based_app_generator.pyExplicit updated_at on conversation update
api/services/conversation_service.pyExplicit updated_at on rename