How does BSON store a Date, and how does it differ from the BSON Timestamp type?
answer
- it is really just a big integer
- signed, so 1969 is expressible
- milliseconds, and nothing about zones
- one of the two types belongs to the oplog
- an empty one gets filled in by the server
basics
~20 sA BSON Date is a signed 64-bit count of milliseconds since the Unix epoch, in UTC, with no timezone stored. BSON Timestamp is a separate internal type used for replication — application data should always use Date.
solid answer
~50 sBSON `Date` holds a **signed** 64-bit integer of milliseconds since the Unix epoch, interpreted as UTC. Signed means dates before 1970 are representable as negative values. Crucially it stores **no timezone or offset** — `ISODate("2026-03-01T09:00:00+01:00")` and the same instant written in UTC are byte-for-byte identical, so if the local zone matters to your domain (a recurring 09:00 appointment, say) you must store the zone or offset in its own field. BSON `Timestamp` is a different type entirely: a 64-bit value combining seconds with an ordinal counter, used internally for the oplog and replication. It is not a general-purpose date type, and it has a surprising behaviour — an empty `Timestamp()` in a top-level field is replaced by the server with the current timestamp on insert. Use `Date` for anything an application or a user cares about.
code
javascript · 6 linesdb.events.insertOne({ at: new Date(), zone: "Europe/Berlin" })
db.events.find({ at: { $gte: ISODate("2026-01-01T00:00:00Z") } })
// same instant, identical stored value - the offset is not kept
ISODate("2026-03-01T09:00:00+01:00").getTime() ===
ISODate("2026-03-01T08:00:00Z").getTime() // truego deeper
Know that a BSON Date is milliseconds since the Unix epoch in UTC, that you create one with new Date() or ISODate(), and that Timestamp is a different, internal type.
Explain the representation — signed 64-bit milliseconds, no timezone stored — and why the offset in an ISODate literal is consumed at parse time rather than persisted.
Show the modelling judgment: instants versus wall-clock time, when a zone name must be stored alongside the date, and how a mixed Date/Timestamp or Date/string field makes range queries silently miss rows.
Own the temporal contract across services: one canonical instant representation, where zone information lives, and how date semantics are versioned when a domain turns out to need local time after all.
## BSON Date The `date` BSON type is a **signed 64-bit integer counting milliseconds since the Unix epoch** (1970-01-01T00:00:00Z). In mongosh you create one with `new Date()` or `ISODate("…")`, and it displays in ISO-8601 form — but the stored value is just that integer. Two consequences follow directly from the representation: - **Signed, so pre-1970 dates work.** A birth date in 1948 is a negative millisecond count and stores and compares perfectly well. - **Millisecond resolution.** Sub-millisecond precision does not survive; if you need microseconds, keep them in a separate numeric field. ## No timezone is stored A `Date` records an **instant**, not a wall-clock reading. `ISODate("2026-03-01T09:00:00+01:00")` and `ISODate("2026-03-01T08:00:00Z")` are the same instant and produce identical stored bytes; the offset in the literal is used to interpret the input and is then discarded. Drivers hand the value back as their platform's UTC-based date type, and the display timezone is a property of the client, not of the data. This is the right default — instants compare and sort correctly across regions, and range queries are unambiguous. But it means a domain that genuinely cares about local wall-clock time must store the extra information itself: keep an IANA zone name such as `"Europe/Berlin"` in a sibling field. An offset alone is not enough for future dates, because the offset a zone uses changes with daylight-saving rules. "09:00 local, every Monday" is a wall-clock fact; the instant it corresponds to next November is derived from the zone, not stored with the date. ## BSON Timestamp is not a date `Timestamp` is a separate BSON type: a 64-bit value made of a seconds component and an ordinal counter that distinguishes multiple operations within the same second. MongoDB uses it **internally**, principally in the oplog, where operations must be totally ordered. It has a special server-side behaviour that catches people out: if you insert a document containing an **empty** `Timestamp()` value in a top-level field, the server replaces it with the current timestamp. A field you expected to hold a placeholder comes back holding a value you did not write. Its resolution is coarser than `Date` (seconds plus an ordinal), it carries no timezone semantics, and drivers surface it as a special type rather than a native date. For application data — created-at, updated-at, event time, expiry — always use `Date`. ## Ordering In BSON's cross-type comparison order, `Date` and `Timestamp` sit in adjacent but distinct positions, with `Date` ranking below `Timestamp`. So a field that accidentally holds a mix of the two sorts into two blocks, and a range query written with `ISODate` bounds will not match the `Timestamp`-typed rows at all — the same silent-miss failure that mixed numeric and string types produce. ## Practical guidance Store instants as `Date`. Store the zone separately when local time is part of the domain. Query with `ISODate` bounds so both sides of the comparison are the same type. Reserve `Timestamp` for reading MongoDB's own internal metadata, and never write it into your own schema by hand. And when a date arrives as a string from an external feed, convert it at ingestion — a date stored as a string sorts lexicographically, which happens to work for full ISO-8601 UTC strings and fails for every other format.
- How would you model a recurring 09:00 local appointment across daylight-saving changes?Store the wall-clock time and an IANA zone name such as "Europe/Berlin" as data, and derive each instant when you schedule or display it. A BSON Date alone records an instant, so pinning one now would drift by an hour when the zone's offset changes. Materialise concrete Date values only for the occurrences you have already resolved.
- What goes wrong if dates are stored as strings instead of BSON Date?String comparison is lexicographic, so ordering is correct only for a fixed-width UTC ISO-8601 format and wrong for anything else. Date arithmetic and the date aggregation operators stop working, TTL indexes cannot be used, and a number-versus-string mismatch means range queries can silently miss rows. Convert at ingestion instead.
- Why should you not use BSON Timestamp for application data?It is an internal replication type: seconds plus an ordinal counter, coarser than Date, with no timezone semantics and awkward driver mapping. It also has a trap — an empty Timestamp() in a top-level field is replaced by the server with the current timestamp on insert, so a placeholder silently becomes a value you did not write.
saying these in an interview costs you the question
- Says BSON Date stores the timezone it was written in
- Uses BSON Timestamp for created-at or event-time fields
- Thinks dates before 1970 cannot be represented
- Stores dates as formatted strings and sorts them lexicographically
- Assumes an offset is enough to reconstruct future local time