clockworklabs/SpacetimeDB · error · ArgumentException
timestamp before unix epoch
Error message
timestamp before unix epoch
What it means
Thrown by Uuid.FromCounterV7 when the passed Timestamp is before 1970-01-01T00:00:00 UTC. UUIDv7 embeds a 48-bit unix_ts_ms field which cannot represent negative unix time, so the library rejects pre-epoch timestamps via ArgumentException on the `now` parameter.
Source
Thrown at crates/bindings-csharp/BSATN.Runtime/BSATN/Uuid.cs:137
public static Uuid FromCounterV7(
ref int counter,
Timestamp now,
ReadOnlySpan<byte> randomBytes // must be length 4
)
{
if (randomBytes.Length != 4)
{
throw new ArgumentException("randomBytes must be exactly 4 bytes", nameof(randomBytes));
}
if (counter < 0)
{
throw new InvalidOperationException("uuid counter must be non-negative");
}
if (now.MicrosecondsSinceUnixEpoch < 0)
{
throw new ArgumentException("timestamp before unix epoch", nameof(now));
}
var unixTsMs = now.MicrosecondsSinceUnixEpoch / 1_000;
// monotonic 31-bit
var counterVal = counter;
counter = (counter + 1) & 0x7FFF_FFFF;
Span<byte> bytes = stackalloc byte[16];
// unix_ts_ms (48 bits, big-endian)
var ts = unixTsMs & 0x0000_FFFF_FFFF_FFFFL;
bytes[0] = (byte)(ts >> 40);
bytes[1] = (byte)(ts >> 32);
bytes[2] = (byte)(ts >> 24);
bytes[3] = (byte)(ts >> 16);
bytes[4] = (byte)(ts >> 8);
bytes[5] = (byte)ts;
View on GitHub (pinned to 524b4487d9)
Solutions
- Pass the current time (Timestamp.UtcNow / DateTimeOffset.UtcNow conversion) instead of the entity's business date
- If you must derive from a date, clamp it: now < epoch ? epoch : now
- Fix the system clock / NTP configuration if the machine reports pre-epoch time
- Validate ts.MicrosecondsSinceUnixEpoch >= 0 before calling and log the offending source value
Example fix
// before var ts = new Timestamp(new DateTimeOffset(1969, 7, 20, 0, 0, 0, TimeSpan.Zero)); var id = Uuid.FromCounterV7(ref counter, ts, rand4); // throws // after var now = Timestamp.UtcNow; // IDs use generation time, not business dates var id = Uuid.FromCounterV7(ref counter, now, rand4);
Defensive patterns
Strategy: validation
Validate before calling
static Timestamp ClampToEpoch(Timestamp ts) =>
ts.MicrosecondsSinceUnixEpoch < 0 ? new Timestamp(0) : ts;
var id = Uuid.FromCounterV7(ref counter, ClampToEpoch(now), randomBytes); Try / catch
try
{
id = Uuid.FromCounterV7(ref counter, now, randomBytes);
}
catch (ArgumentException ex) when (ex.ParamName == "now")
{
logger.LogWarning("Pre-epoch timestamp {Ts} clamped for UUIDv7", now);
id = Uuid.FromCounterV7(ref counter, new Timestamp(0), randomBytes);
} Prevention
- Derive ID timestamps from generation time (UtcNow), never from business/historical dates
- In tests, use fixed dates after 1970
- Validate clock sanity at startup (reject MicrosecondsSinceUnixEpoch < 0 from the environment)
When it happens
Trigger: Passing a Timestamp converted from a DateTimeOffset/DateTime before 1970 (historical dates, birthdates, zeroed DateTime values interpreted oddly); a machine clock set before the epoch; arithmetic on MicrosecondsSinceUnixEpoch that underflows below zero.
Common situations: Batch-importing historical records and generating IDs from their dates; misconfigured VM/container clocks; test fixtures with fixed pre-epoch dates.
Related errors
- uuid counter must be non-negative
- `fromCounterV7` `timestamp` before unix epoch
- Unknown UUID version
- `fromCounterV7` requires `randomBytes.length == 4`
- `fromCounterV7` uuid `counter` must be non-negative
AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16).
Data as JSON: /api/errors/3d346f717c1e861c.
Report an issue: GitHub.