Skip to content

CkDateTime

Job: read moments — a date and a time of day — out of the mixed shapes real data arrives in, compare and move them safely, and write them back out.

A moment is when something happened: a record created, a token expiring, a payment taken. Its twin, CkDate, works in whole calendar days. The column type picks the class: a datetime column comes here, a date column goes there. Every question here is answered to the moment — CkDateTime.IsPast is true for something an hour ago — where CkDate answers to the day.

The reader is the one CkDate uses: ISO 8601 text, "8/31/2026 2:05 PM", a Unix timestamp, an Excel day number, a boxed DateTime or DateTimeOffset. Strict, culture-invariant, never a guess. A value that cannot be read answers the year-one sentinel, DateTime.MinValue; the OrNull twins answer null.

Every DateTime you get back is UTC

.NET's DateTime carries a Kind — UTC, Local or Unspecified — and then ignores it: comparing or subtracting two values looks at the digits only, so a local 23:30 equals a UTC 23:30. SQL Server hands every DateTime back as Unspecified, and Npgsql refuses anything but UTC for a timestamptz column. CkDateTime reads any kind, converts it, and hands out DateTimeKind.Utc — always, the sentinel included — so its answers are safe to compare and to store. The one deliberate exception is InZone, which answers a wall clock for display.

Quick look

using SafeCoder.CommonKit;
using SafeCoder.CommonKit.Enums;

CkDateTime.ToDateTime("8/31/2026 2:05 PM");    // -> 2026-08-31T14:05:00Z
CkDateTime.ToDateTimeOrNull("1900-01-01");     // -> null  (an SQL placeholder, not a real date)
CkDateTime.Iso8601("8/31/2026 2:05 PM");       // -> "2026-08-31T14:05:00Z"
CkDateTime.Unix("2026-08-31T14:05:00Z");       // -> 1788185100
CkDateTime.IsPast(token.ExpiresAt);            // -> true once it has expired, to the moment
CkDateTime.AddMinutes(now, 30);                // -> half an hour on
CkDateTime.AddMonths("2026-01-31T14:05:00Z", 1);                    // -> 2026-02-28T14:05:00Z  (never 3 March)
CkDateTime.StartOfDay(now, "America/Chicago");                      // -> where the customer's day began, in UTC
CkDateTime.TimeAgo("2026-08-28T12:00:00Z", asOf: "2026-08-31T12:00:00Z");   // -> "3 days ago"
CkDateTime.InZone("2026-08-31T14:05:00Z", "America/Chicago");       // -> 2026-08-31 09:05
CkDateTime.Explain("5/6/26");   // -> "a two-digit year is ambiguous"

Methods

ToDateTime and its OrNull twin understand ISO 8601 text ("2026-08-31T14:05:00Z"), bare "yyyyMMdd" as text or number, slashed / dashed / dotted numeric dates with an optional 24-hour or AM/PM time after the date, Unix timestamps (seconds from the epoch to the end of year 9999; from a trillion up, milliseconds — a number is never a negative moment, since -1 is what Unix answers for a value it cannot read), OLE automation day numbers (how Excel and older databases count days), and boxed .NET date types of any kind. A date alone is midnight, UTC.

The plain readers take a fallback to use when the value cannot be read — and the fallback is checked the same way, so a placeholder fallback is no fallback. When both fail, you get the year-one sentinel.

ToDateTime

Read a full moment, in UTC.

DateTime ToDateTime(
    object? input = null,
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    string? assumeZone = CkSettings.DefaultAssumeZone,
    bool treatSentinelsAsMissing = true,
    DateTime? fallback = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a date, a number, or text in any supported shape.
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads: month first or day first.
format string? null An exact .NET format string, e.g. "dd.MM.yyyy HH:mm". When given, text must match it and nothing else is tried.
assumeZone string? CkSettings.DefaultAssumeZone (null) The time zone the value's digits are wall-clock in, when the value declares no zone of its own. null means zone-less digits count as UTC. An id the library does not know counts as UTC.
treatSentinelsAsMissing bool true Counts the placeholder dates — 0001-01-01, 1899-12-30, 1900-01-01, 2079-06-06, 9999-12-31 — as missing rather than real.
fallback DateTime? null Moment to use when the value cannot be read. It is normalized to UTC and checked the same way — a placeholder fallback is no fallback.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of the year-one sentinel, when neither the value nor the fallback can be used.

Returns

When Default With throwIfInvalid: true
The value (or the checked fallback) reads as a moment The UTC moment: 2026-08-31T14:05:00Z The UTC moment: 2026-08-31T14:05:00Z
Rejected: missing, unreadable, an impossible date under the declared order, a placeholder read as missing, or a moment its own offset puts outside years 1 to 9999 (0001-01-01T00:00:00+01:00 is the evening of year zero) — and no usable fallback Year one (DateTime.MinValue as UTC, 0001-01-01T00:00:00Z) Throws CkDateTimeException — carries Input, Expected and Reason

Zone-less digits are declared UTC, never shifted — a value that declares its own zone always wins.

ToDateTime reads the same shapes but keeps the time of day, and always answers a DateTime in UTC (Coordinated Universal Time — the world's zero time zone, the one payloads and databases should live in). It takes the same order parameter as CkDate.ToDateOnly.

CkDateTime.ToDateTime("8/31/2026 2:05 PM");         // -> 2026-08-31T14:05:00Z  (digits kept, counted as UTC)
CkDateTime.ToDateTime("2026-08-31T14:05:00+02:00"); // -> 2026-08-31T12:05:00Z  (its own zone wins — converted)
CkDateTime.ToDateTime(1788185100);                  // -> 2026-08-31T14:05:00Z  (Unix seconds)

assumeZone handles wall-clock data — values written from a local clock with no zone attached. Name the zone the digits were written in, and they are converted to UTC. A value that declares its own zone (an offset, a trailing Z, a local-kind DateTime, a Unix timestamp) always wins; assumeZone never overrides it. Both IANA ids ("America/Chicago") and Windows ids ("Central Standard Time") work on every OS — the id is mapped by the TimeZoneConverter package, not by the host's ICU. Two things stay with the host: daylight-saving history comes from its zone data (tzdata on macOS and Linux; the Windows registry, which is shallow before about 2007, so an old wall-clock value may convert with today's rules there), and a local-kind DateTime is converted with the machine's own zone, which is what local-kind means.

CkDateTime.ToDateTime(
    input: "8/31/2026 20:30",
    assumeZone: "America/Chicago"
);   // -> 2026-09-01T01:30:00Z  (Central wall-clock to UTC, crossing midnight)

ToDateTimeOrNull

The same, for a full moment.

DateTime? ToDateTimeOrNull(
    object? input = null,
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    string? assumeZone = CkSettings.DefaultAssumeZone,
    bool treatSentinelsAsMissing = true
)
Parameter Type Default What it does
input object? null The value: a date, a number, or text in any supported shape.
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads: month first or day first.
format string? null An exact .NET format the text must match, when one is declared.
assumeZone string? CkSettings.DefaultAssumeZone (null) A time zone id for zone-less values, converted to UTC — see ToDateTime. An id the library does not know counts as UTC.
treatSentinelsAsMissing bool true Reads SQL placeholder dates as missing, so they come back as null too.

Returns

When You get
The value reads as a moment The UTC moment: 2026-09-02T10:30:00Z
Missing (null or DBNull), unreadable, or a placeholder date read as missing null — this method never throws

There is no fallback parameter — null is the answer for missing.

Works like CkDate.ToDateOnlyOrNull but answers a nullable DateTime, always UTC — the reader for a nullable datetime column. It takes the same order and assumeZone parameters as ToDateTime.

CkDateTime.ToDateTimeOrNull("2026-09-02T10:30:00Z");  // -> 2026-09-02T10:30:00Z
CkDateTime.ToDateTimeOrNull("1900-01-01");            // -> null  (an SQL placeholder, not a real date)

ToTimeOnly

Read a time of day.

TimeOnly ToTimeOnly(
    object? input = null,
    TimeOnly? fallback = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a time, a date with a time, or text.
fallback TimeOnly? null Time to use when the value cannot be read; midnight when not given.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of returning the fallback, when the value cannot be read.

Returns

When Default With throwIfInvalid: true
The value reads as a time of day, or a fallback was given The time of day: 14:05 The time of day: 14:05
Rejected: missing, unreadable, or a bare number — and no fallback Midnight (TimeOnly.MinValue, 00:00) — which real data also contains Throws CkDateTimeException — carries Input, Expected and Reason

Midnight is a real time — prefer ToTimeOnlyOrNull whenever "no time" and 00:00 must not blur together.

For an SQL time column, a shift start, an opening hour. Understood: a boxed TimeOnly, the time part of a DateTime, DateTimeOffset or in-range TimeSpan, and text like "14:05", "14:05:30", "2:05 PM" (with or without the space), "10pm", "T14:05", or the compact "1405" / "140530". Fractional seconds are dropped. A bare number is not a time of day — 1405 the number could mean too many things.

Careful: the plain ToTimeOnly rejection value is TimeOnly.MinValue — midnight — and midnight is a real time that real data contains. The dates get an impossible sentinel; the times cannot. So ToTimeOnlyOrNull is the honest time reader: prefer it whenever "no time" and "midnight" must not blur together.

CkDateTime.ToTimeOnly("14:05");        // -> 14:05
CkDateTime.ToTimeOnly("2:05 PM");      // -> 14:05
CkDateTime.ToTimeOnly("10pm");         // -> 22:00
CkDateTime.ToTimeOnly("140530");       // -> 14:05:30
CkDateTime.ToTimeOnly("25:00");        // -> 00:00  (rejected — but that looks like real midnight!)

ToTimeOnly also takes a fallback time to use instead of midnight when the value cannot be read.

ToTimeOnlyOrNull

The honest time reader.

TimeOnly? ToTimeOnlyOrNull(object? input = null)
Parameter Type Default What it does
input object? null The value: a time, a date with a time, or text.

Returns

When You get
The value reads as a time of day The time of day: 14:05
Missing (null or DBNull) or unreadable null — this method never throws

A genuine midnight comes back as 00:00, never blurred into null.

Reads the same shapes as ToTimeOnly, but answers null for a missing or unreadable value — so "no time" and a genuine midnight never blur together. Prefer it for a nullable time column.

CkDateTime.ToTimeOnlyOrNull("25:00");  // -> null   (honest)
CkDateTime.ToTimeOnlyOrNull("00:00");  // -> 00:00  (midnight is real, and kept)

Iso8601

The one true timestamp shape.

string Iso8601(
    object? input = null,
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    string? assumeZone = CkSettings.DefaultAssumeZone,
    bool treatSentinelsAsMissing = true,
    string? fallback = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a date, a number, or text.
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads: month first or day first.
format string? null An exact .NET format string for reading, e.g. "dd.MM.yyyy". When given, text must match it and nothing else is tried.
assumeZone string? CkSettings.DefaultAssumeZone (null) The time zone the value's digits are wall-clock in, when the value declares no zone of its own. null means zone-less digits count as UTC. An id the library does not know counts as UTC.
treatSentinelsAsMissing bool true Counts the placeholder dates as missing rather than real.
fallback string? null Value to use when the input cannot be read. It goes through the same reading, never returned as typed; a fallback that is itself rejected answers "".
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of answering "", when neither the value nor the fallback can be used.

Returns

When Default With throwIfInvalid: true
The value (or the checked fallback) reads as a moment The ISO 8601 UTC text: "2026-08-31T00:00:00Z" The ISO 8601 UTC text: "2026-08-31T00:00:00Z"
Rejected: missing, unreadable, or a placeholder read as missing — the fallback too Empty string Throws CkDateTimeException — carries Input, Expected and Reason

A fallback goes through the same reading first, never echoed as typed.

Writes any readable value as ISO 8601 UTC text — "2026-08-31T14:05:00Z" — the shape every payload and API timestamp should use. Rejected values answer "" (empty text); a fallback value goes through the same reading first, never echoed as typed.

CkDateTime.Iso8601("8/31/2026");                  // -> "2026-08-31T00:00:00Z"
CkDateTime.Iso8601("2026-08-31T14:05:00+02:00");  // -> "2026-08-31T12:05:00Z"  (converted to UTC)

Format

Write any other shape.

string Format(
    object? input = null,
    string? into = "yyyy-MM-dd HH:mm:ss",
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    string? assumeZone = CkSettings.DefaultAssumeZone,
    bool treatSentinelsAsMissing = true,
    string? fallback = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a date, a number, or text.
into string? "yyyy-MM-dd HH:mm:ss" The .NET format to write, e.g. "MM/dd/yyyy" or "dd.MM.yyyy HH:mm".
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads: month first or day first.
format string? null An exact .NET format string for reading, e.g. "dd.MM.yyyy". When given, text must match it and nothing else is tried.
assumeZone string? CkSettings.DefaultAssumeZone (null) The time zone the value's digits are wall-clock in, when the value declares no zone of its own. null means zone-less digits count as UTC. An id the library does not know counts as UTC.
treatSentinelsAsMissing bool true Counts the placeholder dates as missing rather than real.
fallback string? null Value to use when the input cannot be read. It goes through the same reading, never returned as typed; a fallback that is itself rejected answers "".
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of answering "", when a value or the into shape cannot be used.

Returns

When Default With throwIfInvalid: true
The value (or the checked fallback) reads, and into is a usable shape The text in the into shape: "08/31/2026 14:05" The text in the into shape: "08/31/2026 14:05"
Rejected: missing, unreadable, a placeholder read as missing, or an unusable into Empty string Throws CkDateTimeException — carries Input, Expected and Reason

Always the invariant culture, so month and day names come out in English no matter what the machine is set to.

For display, file names and legacy feeds. The into parameter is a .NET format string; it defaults to "yyyy-MM-dd". Always the invariant culture, so month and day names come out in English no matter what the machine is set to. Rejected values answer "".

CkDateTime.Format("junk");    // -> ""  (rejected)
CkDateTime.Format(
    input: "2026-08-31T14:05:00Z",
    into: "MM/dd/yyyy HH:mm"
);                        // -> "08/31/2026 14:05"
CkDateTime.Format(
    input: 20260831,
    into: "dd.MM.yyyy"
);                        // -> "31.08.2026"
CkDateTime.Format(
    input: "1/2/2026",
    into: "MMMM d, yyyy"
);                        // -> "January 2, 2026"  (always English)

Unix

Seconds since 1970.

long Unix(
    object? input = null,
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    string? assumeZone = CkSettings.DefaultAssumeZone,
    bool treatSentinelsAsMissing = true,
    long fallback = -1,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a date, a number, or text.
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads: month first or day first.
format string? null An exact .NET format string, e.g. "dd.MM.yyyy". When given, text must match it and nothing else is tried.
assumeZone string? CkSettings.DefaultAssumeZone (null) The time zone the value's digits are wall-clock in, when the value declares no zone of its own. null means zone-less digits count as UTC. An id the library does not know counts as UTC.
treatSentinelsAsMissing bool true Counts the placeholder dates as missing rather than real.
fallback long -1 What comes back when the value cannot be read — pick another when pre-1970 moments are real data.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of returning the fallback, when the value cannot be used.

Returns

When Default With throwIfInvalid: true
The value reads as a moment The Unix seconds: 1788185100 The Unix seconds: 1788185100
Rejected: missing, unreadable, or a placeholder read as missing The fallback (default -1) Throws CkDateTimeException — carries Input, Expected and Reason

A pre-1970 moment is a valid negative — it can collide with the default -1.

Writes a value as Unix time: whole seconds since 1970-01-01T00:00:00Z, the timestamp shape most JSON APIs and payment processors use. Fractional seconds are floored. A moment before 1970 comes back negative — which is valid Unix time, so if pre-1970 moments are real data for you, pick a different fallback than the default -1 — and know that the readers never read a negative number as a moment, for that very reason. From the epoch to the end of year 9999, what Unix writes, ToDateTime reads back.

CkDateTime.Unix("1970-01-02");            // -> 86400
CkDateTime.Unix("2026-08-31T14:05:00Z");  // -> 1788185100
CkDateTime.Unix("junk");                  // -> -1  (the default fallback)

IsDateTime

Does the value read as a moment at all?

bool IsDateTime(
    object? input = null,
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    string? assumeZone = CkSettings.DefaultAssumeZone,
    bool treatSentinelsAsMissing = true,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a date, a number, or text.
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads.
format string? null An exact .NET format string; when given, text must match it and nothing else is tried.
assumeZone string? CkSettings.DefaultAssumeZone (null) The time zone the value's digits are wall-clock in, when the value declares no zone of its own. An id the library does not know counts as UTC.
treatSentinelsAsMissing bool true Counts the placeholder dates as missing rather than real.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of answering false, when a value cannot be used.

Returns

When Default With throwIfInvalid: true
The value reads true true
Rejected: unreadable, or a placeholder false Throws CkDateTimeException

The yes-or-no form of ToDateTime. A date alone reads — it is midnight, UTC — so the verdict is the one CkDate.IsDate gives; HasTime tells the two apart.

CkDateTime.IsDateTime("8/31/2026 2:05 PM");   // -> true
CkDateTime.IsDateTime("14:05");               // -> false  (a time alone is no moment)

IsTime

Does it read as a time of day?

bool IsTime(
    object? input = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a time, a date with a time, or text.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of answering false, when the value cannot be read.

Returns

When Default With throwIfInvalid: true
The value reads as a time of day — midnight included true true
Rejected: out of range, minutes missing, a bare number, not a time false Throws CkDateTimeException — carries Input, Expected and Reason

The honest time check. ToTimeOnly's rejection value is 00:00, which real data also contains, so testing its result for midnight cannot tell "unreadable" from "midnight"; IsTime can. Same shapes as the reader: "14:05", "2:05 PM", "T14:05", the compact "1405", a TimeOnly, the time part of a DateTime. A bare number is never a time.

CkDateTime.IsTime("2:05 PM");    // -> true
CkDateTime.IsTime("00:00");      // -> true   (midnight is real)
CkDateTime.IsTime("25:00");      // -> false
CkDateTime.IsTime(1405);         // -> false  (a number is not a time)

IsIso8601

Is it written as ISO 8601 text?

bool IsIso8601(
    object? input = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The raw value.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of answering false, when the value is not ISO 8601 date text.

Returns

When Default With throwIfInvalid: true
Text in one of the ISO 8601 shapes the readers accept, and a real calendar date — the moment it names, its offset applied, within years 1 to 9999 true true
Rejected: another date shape, a number, a date value, ISO-shaped text that is not a date or whose offset moves it outside the calendar ("0001-01-01T00:00:00+01:00") false Throws CkDateTimeException — carries Input, Expected and Reason

A shape check for API contracts and feeds that must carry ISO text — the same idea as CkCountry.IsAlpha2. The accepted shapes are the extended ones: "2026-08-31", "2026-08-31T14:05:00Z", "2026-08-31T14:05:00.123+02:00", "2026-08-31 14:05", and the space form with fractions or an offset as SQL exports write it, "2026-08-31 14:05:30.123", "2026-08-31 14:05:00+02:00". "8/31/2026" and 20260831 are dates but not ISO text; "2026-02-30" is ISO-shaped but not a date. Whether the date means anything is CkDate.IsDate's question: a placeholder like "1900-01-01" is still ISO text.

CkDateTime.IsIso8601("2026-08-31T14:05:00Z");   // -> true
CkDateTime.IsIso8601("8/31/2026");              // -> false  (a date, not ISO)
CkDateTime.IsIso8601("2026-02-30");             // -> false  (ISO-shaped, not a date)

HasTime

Does it say when, or only which day?

bool HasTime(
    object? input = null,
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    bool treatSentinelsAsMissing = true,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a date, a number, or text.
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads: month first or day first.
format string? null An exact .NET format string, e.g. "dd.MM.yyyy HH:mm". When given, text must match it and nothing else is tried; the value has a time when the format reads an hour, a minute or a second.
treatSentinelsAsMissing bool true Counts the placeholder dates as missing rather than real.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of answering false, when the value is not a date at all.

Returns

When Default With throwIfInvalid: true
The value is a date that declares a time of day true true
The value is a date without one false false
Rejected: not a date false Throws CkDateTimeException — carries Input, Expected and Reason

Judged by the shape of the value, not its digits. A DateTime or DateTimeOffset always carries a time — a date alone is a DateOnly — and so does text with a clock in it, "2026-08-31T00:00:00Z" included, which says midnight rather than nothing. A Unix timestamp is a moment; an Excel day number has a time when it has a fraction. "2026-08-31", 20260831 and a DateOnly do not. There is no assumeZone: a zone changes which moment the digits mean, never whether they include a clock. Useful for choosing between a date and a date-time display, and for a field that must say when.

CkDateTime.HasTime("2026-08-31T14:05:00Z");   // -> true
CkDateTime.HasTime("8/31/2026 2:05 PM");      // -> true
CkDateTime.HasTime("2026-08-31");             // -> false
CkDateTime.HasTime(new DateOnly(2026, 8, 31));   // -> false

IsBefore

Does the start precede the end?

bool IsBefore(
    object? input = null,
    object? other = null,
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    string? assumeZone = CkSettings.DefaultAssumeZone,
    bool treatSentinelsAsMissing = true,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The moment to test.
other object? null The moment it is compared with.
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads: month first or day first.
format string? null An exact .NET format string, e.g. "dd.MM.yyyy". When given, text must match it and nothing else is tried.
assumeZone string? CkSettings.DefaultAssumeZone (null) The time zone the value's digits are wall-clock in, when the value declares no zone of its own. null means zone-less digits count as UTC. An id the library does not know counts as UTC.
treatSentinelsAsMissing bool true Counts the placeholder dates as missing — for both values. To compare against an open-ended bound such as 9999-12-31, use IsBetween, whose bounds are always literal.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of answering false, when either value cannot be used.

Returns

When Default With throwIfInvalid: true
Both read and input is an earlier moment than other true true
Both read, and input is the same moment or later false false
Rejected: either value cannot be read false Throws CkDateTimeException — carries Input, Expected and Reason

Compared as moments, strictly: 09:00 is before 10:00 on the same day, and two readings of the same moment are neither before nor after each other. A date without a time is midnight, UTC. For "earlier than today" see IsPast, which compares calendar days. A placeholder on either side is missing, not a bound — switch the flag off, or use IsBetween, for an open-ended 9999-12-31.

CkDateTime.IsBefore("2026-08-30", "2026-08-31");                          // -> true
CkDateTime.IsBefore("2026-08-31T09:00:00Z", "2026-08-31T10:00:00Z");      // -> true
CkDateTime.IsBefore("2026-08-31", "2026-08-31");                          // -> false  (the same moment)
CkDateTime.IsBefore(form["start"], form["end"], throwIfInvalid: true);    // the "start before end" check

IsAfter

Does the end follow the start?

bool IsAfter(
    object? input = null,
    object? other = null,
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    string? assumeZone = CkSettings.DefaultAssumeZone,
    bool treatSentinelsAsMissing = true,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The moment to test.
other object? null The moment it is compared with.
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads: month first or day first.
format string? null An exact .NET format string, e.g. "dd.MM.yyyy". When given, text must match it and nothing else is tried.
assumeZone string? CkSettings.DefaultAssumeZone (null) The time zone the value's digits are wall-clock in, when the value declares no zone of its own. null means zone-less digits count as UTC. An id the library does not know counts as UTC.
treatSentinelsAsMissing bool true Counts the placeholder dates as missing — for both values. To compare against an open-ended bound such as 9999-12-31, use IsBetween, whose bounds are always literal.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of answering false, when either value cannot be used.

Returns

When Default With throwIfInvalid: true
Both read and input is a later moment than other true true
Both read, and input is the same moment or earlier false false
Rejected: either value cannot be read false Throws CkDateTimeException — carries Input, Expected and Reason

The mirror of IsBefore, with the same rules: moments, strictly. For "later than today" see IsFuture, which compares calendar days.

CkDateTime.IsAfter("2026-08-31", "2026-08-30");   // -> true
CkDateTime.IsAfter("8/31/2026 2:05 PM", "8/31/2026");   // -> true  (a date alone is midnight)

IsBetween

Does a moment lie between two others?

bool IsBetween(
    object? input = null,
    object? min = null,
    object? max = null,
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    string? assumeZone = CkSettings.DefaultAssumeZone,
    bool treatSentinelsAsMissing = true,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The moment to test.
min object? null The lower bound, inclusive.
max object? null The upper bound, inclusive.
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads.
format string? null An exact .NET format string; when given, text must match it and nothing else is tried.
assumeZone string? CkSettings.DefaultAssumeZone (null) The time zone the value's digits are wall-clock in, when the value declares no zone of its own. An id the library does not know counts as UTC.
treatSentinelsAsMissing bool true Counts the placeholder dates as missing rather than real.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of answering false, when a value cannot be used.

Returns

When Default With throwIfInvalid: true
All three read, and min ≤ input ≤ max true true
All three read, otherwise false false
Rejected: any unreadable, or the bounds contradict each other false Throws CkDateTimeException

Inclusive on both ends, to the moment. The bounds are taken literally — 9999-12-31 is a legitimate open upper bound — while the value itself is still checked for placeholders. For whole days see CkDate.IsBetween.

CkDateTime.IsBetween("2026-08-31T14:05:00Z", "2026-08-31T14:00:00Z", "2026-08-31T15:00:00Z");   // -> true
CkDateTime.IsBetween("2026-08-31T15:00:01Z", "2026-08-31T14:00:00Z", "2026-08-31T15:00:00Z");   // -> false  (a second late)

IsPast

Has this moment passed?

bool IsPast(
    object? input = null,
    object? asOf = null,
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    string? assumeZone = CkSettings.DefaultAssumeZone,
    bool treatSentinelsAsMissing = true,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The moment to test.
asOf object? null The moment to measure against; null for now.
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads.
format string? null An exact .NET format string; when given, text must match it and nothing else is tried.
assumeZone string? CkSettings.DefaultAssumeZone (null) The time zone the value's digits are wall-clock in, when the value declares no zone of its own. An id the library does not know counts as UTC.
treatSentinelsAsMissing bool true Counts the placeholder dates as missing rather than real.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of answering false, when a value cannot be used.

Returns

When Default With throwIfInvalid: true
Reads, and is earlier than asOf true true
Reads, and is not false false
Rejected: either unreadable false Throws CkDateTimeException

To the moment: a token that expired an hour ago is past. CkDate.IsPast asks the same of a day, and answers false for anything dated today.

CkDateTime.IsPast(token.ExpiresAt);                                          // -> true once it has expired
CkDateTime.IsPast("2026-08-31T13:05:00Z", asOf: "2026-08-31T14:05:00Z");     // -> true
CkDate.IsPast("2026-08-31T13:05:00Z", asOf: "2026-08-31T14:05:00Z");         // -> false  (the same day)

IsFuture

Is this moment still to come?

bool IsFuture(
    object? input = null,
    object? asOf = null,
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    string? assumeZone = CkSettings.DefaultAssumeZone,
    bool treatSentinelsAsMissing = true,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The moment to test.
asOf object? null The moment to measure against; null for now.
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads.
format string? null An exact .NET format string; when given, text must match it and nothing else is tried.
assumeZone string? CkSettings.DefaultAssumeZone (null) The time zone the value's digits are wall-clock in, when the value declares no zone of its own. An id the library does not know counts as UTC.
treatSentinelsAsMissing bool true Counts the placeholder dates as missing rather than real.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of answering false, when a value cannot be used.

Returns

When Default With throwIfInvalid: true
Reads, and is later than asOf true true
Reads, and is not false false
Rejected: either unreadable false Throws CkDateTimeException

To the moment. Now itself is neither past nor future.

CkDateTime.IsFuture("2026-08-31T15:05:00Z", asOf: "2026-08-31T14:05:00Z");   // -> true

SameDay

Do two moments fall on the same calendar day?

bool SameDay(
    object? input = null,
    object? other = null,
    string? zone = CkSettings.DefaultZone,
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    string? assumeZone = CkSettings.DefaultAssumeZone,
    bool treatSentinelsAsMissing = true,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The first moment.
other object? null The moment it is compared with.
zone string? CkSettings.DefaultZone (null) The time zone whose calendar day counts, as an IANA or Windows id. null for UTC days. An id the library does not know counts as UTC.
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads.
format string? null An exact .NET format string; when given, text must match it and nothing else is tried.
assumeZone string? CkSettings.DefaultAssumeZone (null) The time zone the value's digits are wall-clock in, when the value declares no zone of its own. An id the library does not know counts as UTC.
treatSentinelsAsMissing bool true Counts the placeholder dates as missing rather than real.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of answering false, when either value or the zone cannot be used.

Returns

When Default With throwIfInvalid: true
Both read, same day true true
Both read, different days false false
Rejected: unreadable false Throws CkDateTimeException — carries Input, Expected and Reason

"The same day" depends on whose day. 23:30 and 00:30 UTC are two days in London and one evening in New York, so name the customer's zone when the question is theirs. Two missing values are not the same day.

CkDateTime.SameDay("2026-08-31T23:30:00Z", "2026-09-01T00:30:00Z");   // -> false  (two UTC days)
CkDateTime.SameDay(
    input: "2026-08-31T23:30:00Z",
    other: "2026-09-01T00:30:00Z",
    zone: "America/New_York"
);                                                                // -> true

Add

Calendar units and clock units, in one move, the time of day kept.

DateTime Add(
    object? input = null,
    int years = 0,
    int months = 0,
    int days = 0,
    long hours = 0,
    long minutes = 0,
    long seconds = 0,
    string? zone = CkSettings.DefaultZone,
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    string? assumeZone = CkSettings.DefaultAssumeZone,
    bool treatSentinelsAsMissing = true,
    DateTime? fallback = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a date, a number, or text.
years, months, days int 0 Calendar units to add; negative to go back.
hours, minutes, seconds long 0 Elapsed time to add, after the calendar units; negative to go back.
zone string? CkSettings.DefaultZone (null) The time zone whose wall clock the calendar is counted on, as an IANA or Windows id. null for the UTC clock, which has no daylight saving. An id the library does not know counts as UTC.
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads.
format string? null An exact .NET format string; when given, text must match it and nothing else is tried.
assumeZone string? CkSettings.DefaultAssumeZone (null) The time zone the value's digits are wall-clock in, when the value declares no zone of its own. An id the library does not know counts as UTC.
treatSentinelsAsMissing bool true Counts the placeholder dates as missing rather than real.
fallback DateTime? null A moment to use when the value cannot be used. A placeholder fallback is no fallback. It comes back as given, in UTC, never moved.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of answering the year-one sentinel, when neither the value nor the fallback can be used.

Returns

When Default With throwIfInvalid: true
The value reads The moment reached, in UTC The moment
Rejected: unreadable, or the result is outside the calendar (years 1 to 9999), and no usable fallback DateTime.MinValue, the year-one sentinel Throws CkDateTimeException — carries Input, Expected and Reason

The two kinds of unit are different things, and mixing them up is the bug.

Years, months and days are counted on a calendar. The years and months are added together, as months; the day is held to the end of a shorter month, as in CkDate.Add; and the time of day stays what it was. Name the zone and it stays what it was on that zone's wall clock: "9:00 in Chicago, a day later" is 9:00 in Chicago across a daylight-saving change — which is 23 or 25 hours away, not 24.

Hours, minutes and seconds are elapsed time, added last: 24 hours is always 24 hours, whatever any clock did in between.

When the calendar leads to a wall-clock time the zone's clocks skip, it moves on by the length of the gap — 02:30 becomes 03:30. One that happens twice reads as standard time, as everywhere in the library.

CkDateTime.Add("2026-01-31T14:05:00Z", months: 1);      // -> 2026-02-28T14:05:00Z

// 9:00 in Chicago on 7 March; the clocks go forward that night
CkDateTime.Add("2026-03-07T15:00:00Z", days: 1);                            // -> 2026-03-08T15:00:00Z  (10:00 in Chicago)
CkDateTime.Add(
    input: "2026-03-07T15:00:00Z",
    days: 1,
    zone: "America/Chicago"
);                                                                           // -> 2026-03-08T14:00:00Z  (9:00 in Chicago)
CkDateTime.Add("2026-03-07T15:00:00Z", hours: 24, zone: "America/Chicago"); // -> 2026-03-08T15:00:00Z  (hours are hours)

AddDays

Calendar days on, the time of day kept.

DateTime AddDays(
    object? input = null,
    int days = 0,
    string? zone = CkSettings.DefaultZone,
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    string? assumeZone = CkSettings.DefaultAssumeZone,
    bool treatSentinelsAsMissing = true,
    DateTime? fallback = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a date, a number, or text.
days int 0 Calendar days to add; negative to go back.
zone string? CkSettings.DefaultZone (null) The time zone whose wall clock the calendar is counted on, as an IANA or Windows id. null for the UTC clock, which has no daylight saving. An id the library does not know counts as UTC.
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads.
format string? null An exact .NET format string; when given, text must match it and nothing else is tried.
assumeZone string? CkSettings.DefaultAssumeZone (null) The time zone the value's digits are wall-clock in, when the value declares no zone of its own. An id the library does not know counts as UTC.
treatSentinelsAsMissing bool true Counts the placeholder dates as missing rather than real.
fallback DateTime? null A moment to use when the value cannot be used. A placeholder fallback is no fallback. It comes back as given, in UTC, never moved.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of answering the year-one sentinel, when neither the value nor the fallback can be used.

Returns

When Default With throwIfInvalid: true
The value reads The moment reached, in UTC The moment
Rejected: unreadable, or the result is outside the calendar (years 1 to 9999), and no usable fallback DateTime.MinValue, the year-one sentinel Throws CkDateTimeException — carries Input, Expected and Reason

With a zone, the same time on that zone's clock — 23 or 25 hours away across a daylight-saving change. For exactly 24 hours use AddHours. See Add for the rules.

CkDateTime.AddDays("2026-08-31T14:05:00Z", 30);                              // -> 2026-09-30T14:05:00Z
CkDateTime.AddDays("2026-10-31T14:00:00Z", 1, zone: "America/Chicago");      // -> 2026-11-01T15:00:00Z  (25 hours on)

AddMonths

Months on, the day held to the month's end, the time kept.

DateTime AddMonths(
    object? input = null,
    int months = 0,
    string? zone = CkSettings.DefaultZone,
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    string? assumeZone = CkSettings.DefaultAssumeZone,
    bool treatSentinelsAsMissing = true,
    DateTime? fallback = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a date, a number, or text.
months int 0 Months to add; negative to go back.
zone string? CkSettings.DefaultZone (null) The time zone whose wall clock the calendar is counted on, as an IANA or Windows id. null for the UTC clock, which has no daylight saving. An id the library does not know counts as UTC.
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads.
format string? null An exact .NET format string; when given, text must match it and nothing else is tried.
assumeZone string? CkSettings.DefaultAssumeZone (null) The time zone the value's digits are wall-clock in, when the value declares no zone of its own. An id the library does not know counts as UTC.
treatSentinelsAsMissing bool true Counts the placeholder dates as missing rather than real.
fallback DateTime? null A moment to use when the value cannot be used. A placeholder fallback is no fallback. It comes back as given, in UTC, never moved.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of answering the year-one sentinel, when neither the value nor the fallback can be used.

Returns

When Default With throwIfInvalid: true
The value reads The moment reached, in UTC The moment
Rejected: unreadable, or the result is outside the calendar (years 1 to 9999), and no usable fallback DateTime.MinValue, the year-one sentinel Throws CkDateTimeException — carries Input, Expected and Reason

"The same time next month." See Add for the rules.

CkDateTime.AddMonths("2026-01-31T14:05:00Z", 1);   // -> 2026-02-28T14:05:00Z

AddYears

Years on, the time kept.

DateTime AddYears(
    object? input = null,
    int years = 0,
    string? zone = CkSettings.DefaultZone,
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    string? assumeZone = CkSettings.DefaultAssumeZone,
    bool treatSentinelsAsMissing = true,
    DateTime? fallback = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a date, a number, or text.
years int 0 Years to add; negative to go back.
zone string? CkSettings.DefaultZone (null) The time zone whose wall clock the calendar is counted on, as an IANA or Windows id. null for the UTC clock, which has no daylight saving. An id the library does not know counts as UTC.
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads.
format string? null An exact .NET format string; when given, text must match it and nothing else is tried.
assumeZone string? CkSettings.DefaultAssumeZone (null) The time zone the value's digits are wall-clock in, when the value declares no zone of its own. An id the library does not know counts as UTC.
treatSentinelsAsMissing bool true Counts the placeholder dates as missing rather than real.
fallback DateTime? null A moment to use when the value cannot be used. A placeholder fallback is no fallback. It comes back as given, in UTC, never moved.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of answering the year-one sentinel, when neither the value nor the fallback can be used.

Returns

When Default With throwIfInvalid: true
The value reads The moment reached, in UTC The moment
Rejected: unreadable, or the result is outside the calendar (years 1 to 9999), and no usable fallback DateTime.MinValue, the year-one sentinel Throws CkDateTimeException — carries Input, Expected and Reason

A leap day lands on 28 February in a year without one.

CkDateTime.AddYears("2024-02-29T14:05:00Z", 1);   // -> 2025-02-28T14:05:00Z

AddHours

Hours of elapsed time.

DateTime AddHours(
    object? input = null,
    long hours = 0,
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    string? assumeZone = CkSettings.DefaultAssumeZone,
    bool treatSentinelsAsMissing = true,
    DateTime? fallback = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a date, a number, or text.
hours long 0 Hours to add; negative to go back.
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads.
format string? null An exact .NET format string; when given, text must match it and nothing else is tried.
assumeZone string? CkSettings.DefaultAssumeZone (null) The time zone the value's digits are wall-clock in, when the value declares no zone of its own. An id the library does not know counts as UTC.
treatSentinelsAsMissing bool true Counts the placeholder dates as missing rather than real.
fallback DateTime? null A moment to use when the value cannot be used. A placeholder fallback is no fallback. It comes back as given, in UTC, never moved.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of answering the year-one sentinel, when neither the value nor the fallback can be used.

Returns

When Default With throwIfInvalid: true
The value reads The moment reached, in UTC The moment
Rejected: unreadable, or the result is outside the calendar (years 1 to 9999), and no usable fallback DateTime.MinValue, the year-one sentinel Throws CkDateTimeException — carries Input, Expected and Reason

Elapsed time: no zone comes into it. For "the same time tomorrow" on a wall clock see AddDays.

CkDateTime.AddHours("2026-08-31T14:05:00Z", 24);   // -> 2026-09-01T14:05:00Z

AddMinutes

Minutes of elapsed time.

DateTime AddMinutes(
    object? input = null,
    long minutes = 0,
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    string? assumeZone = CkSettings.DefaultAssumeZone,
    bool treatSentinelsAsMissing = true,
    DateTime? fallback = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a date, a number, or text.
minutes long 0 Minutes to add; negative to go back.
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads.
format string? null An exact .NET format string; when given, text must match it and nothing else is tried.
assumeZone string? CkSettings.DefaultAssumeZone (null) The time zone the value's digits are wall-clock in, when the value declares no zone of its own. An id the library does not know counts as UTC.
treatSentinelsAsMissing bool true Counts the placeholder dates as missing rather than real.
fallback DateTime? null A moment to use when the value cannot be used. A placeholder fallback is no fallback. It comes back as given, in UTC, never moved.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of answering the year-one sentinel, when neither the value nor the fallback can be used.

Returns

When Default With throwIfInvalid: true
The value reads The moment reached, in UTC The moment
Rejected: unreadable, or the result is outside the calendar (years 1 to 9999), and no usable fallback DateTime.MinValue, the year-one sentinel Throws CkDateTimeException — carries Input, Expected and Reason

A session that ends in 30, a code that expires in 10.

CkDateTime.AddMinutes("2026-08-31T14:05:00Z", 30);   // -> 2026-08-31T14:35:00Z

AddSeconds

Seconds of elapsed time.

DateTime AddSeconds(
    object? input = null,
    long seconds = 0,
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    string? assumeZone = CkSettings.DefaultAssumeZone,
    bool treatSentinelsAsMissing = true,
    DateTime? fallback = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a date, a number, or text.
seconds long 0 Seconds to add; negative to go back.
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads.
format string? null An exact .NET format string; when given, text must match it and nothing else is tried.
assumeZone string? CkSettings.DefaultAssumeZone (null) The time zone the value's digits are wall-clock in, when the value declares no zone of its own. An id the library does not know counts as UTC.
treatSentinelsAsMissing bool true Counts the placeholder dates as missing rather than real.
fallback DateTime? null A moment to use when the value cannot be used. A placeholder fallback is no fallback. It comes back as given, in UTC, never moved.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of answering the year-one sentinel, when neither the value nor the fallback can be used.

Returns

When Default With throwIfInvalid: true
The value reads The moment reached, in UTC The moment
Rejected: unreadable, or the result is outside the calendar (years 1 to 9999), and no usable fallback DateTime.MinValue, the year-one sentinel Throws CkDateTimeException — carries Input, Expected and Reason

The expires_in of a token, added to the moment it was issued.

CkDateTime.AddSeconds(issuedAt, 3600);   // -> an hour later

StartOfDay

Where a day starts, as a UTC moment — in the zone you name.

DateTime StartOfDay(
    object? input = null,
    string? zone = CkSettings.DefaultZone,
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    string? assumeZone = CkSettings.DefaultAssumeZone,
    bool treatSentinelsAsMissing = true,
    DateTime? fallback = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a date, a number, or text.
zone string? CkSettings.DefaultZone (null) The time zone whose day it is, as an IANA or Windows id. null for the UTC day. An id the library does not know counts as UTC.
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads.
format string? null An exact .NET format string; when given, text must match it and nothing else is tried.
assumeZone string? CkSettings.DefaultAssumeZone (null) The time zone the value's digits are wall-clock in, when the value declares no zone of its own. An id the library does not know counts as UTC.
treatSentinelsAsMissing bool true Counts the placeholder dates as missing rather than real.
fallback DateTime? null A moment to use when the value cannot be used. A placeholder fallback is no fallback. It comes back as given, in UTC, never moved.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of answering the year-one sentinel, when neither the value nor the fallback can be used.

Returns

When Default With throwIfInvalid: true
The value reads The first moment of its day, in UTC The moment
Rejected: unreadable, and no usable fallback DateTime.MinValue, the year-one sentinel Throws CkDateTimeException — carries Input, Expected and Reason

"Today's orders" for a customer in Chicago are not the orders since UTC midnight. Their day starts at 05:00 UTC in summer and 06:00 in winter, and this is the method that knows. Where a zone's clocks skip midnight, the day starts at the first moment that exists.

CkDateTime.StartOfDay("2026-08-31T14:05:00Z");                       // -> 2026-08-31T00:00:00Z
CkDateTime.StartOfDay("2026-08-31T14:05:00Z", "America/Chicago");    // -> 2026-08-31T05:00:00Z

StartOfNextDay

Where a day ends: the first moment of the next one.

DateTime StartOfNextDay(
    object? input = null,
    string? zone = CkSettings.DefaultZone,
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    string? assumeZone = CkSettings.DefaultAssumeZone,
    bool treatSentinelsAsMissing = true,
    DateTime? fallback = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a date, a number, or text.
zone string? CkSettings.DefaultZone (null) The time zone whose day it is, as an IANA or Windows id. null for the UTC day. An id the library does not know counts as UTC.
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads.
format string? null An exact .NET format string; when given, text must match it and nothing else is tried.
assumeZone string? CkSettings.DefaultAssumeZone (null) The time zone the value's digits are wall-clock in, when the value declares no zone of its own. An id the library does not know counts as UTC.
treatSentinelsAsMissing bool true Counts the placeholder dates as missing rather than real.
fallback DateTime? null A moment to use when the value cannot be used. A placeholder fallback is no fallback. It comes back as given, in UTC, never moved.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of answering the year-one sentinel, when neither the value nor the fallback can be used.

Returns

When Default With throwIfInvalid: true
The value reads The first moment of the day after, in UTC The moment
Rejected: unreadable, and no usable fallback DateTime.MinValue, the year-one sentinel Throws CkDateTimeException — carries Input, Expected and Reason

The exclusive upper edge of a day. There is deliberately no "end of day": 23:59:59 leaves the last second out, and 23:59:59.999 is rounded up into the next day by an SQL Server datetime column. A range closed below and open above has neither problem:

DateTime from = CkDateTime.StartOfDay(now, "America/Chicago");
DateTime to = CkDateTime.StartOfNextDay(now, "America/Chicago");

// WHERE created >= @from AND created < @to     — never <=

InZone

A moment on a zone's wall clock.

DateTime InZone(
    object? input = null,
    string? zone = CkSettings.DefaultZone,
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    string? assumeZone = CkSettings.DefaultAssumeZone,
    bool treatSentinelsAsMissing = true,
    DateTime? fallback = null,
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The value: a date, a number, or text.
zone string? CkSettings.DefaultZone (null) The time zone to show the moment in — an IANA id like "Europe/Berlin" or a Windows id like "W. Europe Standard Time". An id the library does not know counts as UTC.
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads.
format string? null An exact .NET format string; when given, text must match it and nothing else is tried.
assumeZone string? CkSettings.DefaultAssumeZone (null) The time zone the value's digits are wall-clock in, when the value declares no zone of its own. An id the library does not know counts as UTC.
treatSentinelsAsMissing bool true Counts the placeholder dates as missing rather than real.
fallback DateTime? null A moment to use when the value cannot be read, taken as already in the zone. A placeholder fallback is no fallback.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of answering the year-one sentinel, when the value, the fallback or the zone cannot be used.

Returns

When Default With throwIfInvalid: true
The value reads and the zone is known The wall-clock moment, DateTimeKind.Unspecified The moment
Rejected: an unreadable value with no usable fallback DateTime.MinValue Throws CkDateTimeException — carries Input, Expected and Reason — Expected says "a time zone" or "a date"

The reverse of what assumeZone does on the way in: the value is read into UTC first, as ToDateTime reads it, then converted. The result carries no kind, since its digits belong to that zone and to no machine, and Format writes those digits as they are. For emails, reports and anything a person reads in their own time.

CkDateTime.InZone("2026-08-31T14:05:00Z", "America/Chicago");   // -> 2026-08-31 09:05
CkDateTime.Format(CkDateTime.InZone(when, "Europe/Berlin"), into: "HH:mm");   // -> "16:05"

TimeAgo

A moment as people say it.

string TimeAgo(
    object? input = null,
    object? asOf = null,
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    string? assumeZone = CkSettings.DefaultAssumeZone,
    bool treatSentinelsAsMissing = true,
    string fallback = "",
    bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
Parameter Type Default What it does
input object? null The moment: a date, a number, or text.
asOf object? null The moment to measure from. null means now (UTC) — pass a value in tests.
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads.
format string? null An exact .NET format string; when given, text must match it and nothing else is tried.
assumeZone string? CkSettings.DefaultAssumeZone (null) The time zone the value's digits are wall-clock in, when the value declares no zone of its own. An id the library does not know counts as UTC.
treatSentinelsAsMissing bool true Counts the placeholder dates as missing rather than real.
fallback string "" What comes back when a moment cannot be read.
throwIfInvalid bool CkSettings.DefaultThrowIfInvalid (false) Pass true to throw a CkDateTimeException that says why, instead of answering the fallback, when a moment cannot be used.

Returns

When Default With throwIfInvalid: true
Both moments read "just now", "5 minutes ago", "an hour ago", "yesterday", "3 weeks ago", "in 2 days", "a year ago"… The phrase
Rejected: either cannot be read The fallback Throws CkDateTimeException — carries Input, Expected and Reason

English, and deliberately coarse: under 45 seconds is "just now", then minutes, hours, "yesterday" or "tomorrow", days to a week, weeks to a month, months to a year, then years, each rounded to the nearest whole, halves up. For activity feeds and "last seen" labels; an exact timestamp belongs beside it, not instead of it.

CkDateTime.TimeAgo("2026-08-28T12:00:00Z", asOf: "2026-08-31T12:00:00Z");   // -> "3 days ago"
CkDateTime.TimeAgo(DateTime.UtcNow.AddHours(2));                            // -> "in 2 hours"

Explain

Why not — in words, without an exception.

string Explain(
    object? input = null,
    DateOrder order = CkSettings.DefaultDateOrder,
    string? format = null,
    string? assumeZone = CkSettings.DefaultAssumeZone,
    bool treatSentinelsAsMissing = true
)
Parameter Type Default What it does
input object? null The value: a date, a number, or text.
order DateOrder CkSettings.DefaultDateOrder (DateOrder.MonthFirst) How an ambiguous all-numeric date like "3/4/2026" reads.
format string? null An exact .NET format string; when given, text must match it and nothing else is tried.
assumeZone string? CkSettings.DefaultAssumeZone (null) The time zone the value's digits are wall-clock in, when the value declares no zone of its own. An id the library does not know counts as UTC.
treatSentinelsAsMissing bool true Counts the placeholder dates as missing rather than real.

Returns

When You get
The value passes the reader ""
It does not The reason, the same words the exception would carry — an answer, not a failure; it never throws

The form-friendly twin of throwIfInvalid: the message to show beside the field, without a try and catch. Every class with a reader has one, on the reader's own rules, so the reason is exactly what the reader would say.

CkDateTime.Explain("8/31/2026");    // -> ""
CkDateTime.Explain("5/6/26");       // -> "a two-digit year is ambiguous"
CkDateTime.Explain("1900-01-01");   // -> "a placeholder date that means missing (1900-01-01)"

Good to know

  • Moments here, days in CkDate. IsBefore, IsAfter, IsBetween, IsPast and IsFuture compare to the moment, strictly: 09:00 is before 10:00 on the same day, and equal moments are neither. The same names in CkDate compare calendar days.
  • Calendar units and clock units are different things. AddYears, AddMonths and AddDays move by the calendar and keep the time of day — on a zone's wall clock when you name the zone. AddHours, AddMinutes and AddSeconds are elapsed time. A day is 23, 24 or 25 hours long, which is the whole difference.
  • Set your zone once. CkSettings.DefaultZone stands behind every zone parameter and CkSettings.DefaultAssumeZone behind every assumeZone — see Your time zone, set once, and read its warning before setting the second.
  • Two zone parameters, two questions. assumeZone says which wall clock a zone-less value was written on. zone says whose calendar the answer is counted on — in Add, StartOfDay, SameDay. A value that declares its own zone always wins over assumeZone.
  • Daylight saving, once and for all. Reading a wall-clock time that never existed — the spring-forward hour — is rejected: someone wrote down a time that was not on the clock. When the calendar leads there ("02:30, a day later"), it moves on by the length of the gap. The repeated fall-back hour reads as standard time in both cases.
  • A day is a range, closed below and open above. StartOfDay and StartOfNextDay; compare with >= and <. There is no "end of day" to get wrong.
  • Month-first is the default order, two-digit years are rejected, and the placeholder dates — 0001-01-01, 1899-12-30, 1900-01-01, 2079-06-06, 9999-12-31 — count as missing by default, exactly as in CkDate.
  • Midnight is real. ToTimeOnly's rejection value is 00:00 — which collides with a genuine midnight. Use ToTimeOnlyOrNull whenever that difference matters.
  • Fractional seconds are dropped from times of day, and a bare number is never a time of day. Moments keep theirs.
  • IsIso8601 judges the shape, IsDateTime the meaning. "8/31/2026" is a moment but not ISO text; "1900-01-01" is ISO text but, being a placeholder, not a moment by default.
  • Now is UTC. IsPast, IsFuture and TimeAgo measure against this moment when you give no asOf. Pass asOf in tests.
  • Reader versus sentinel. Sending a DateTime through ToDateTimeOrNull normalizes its kind to UTC; CkSentinel.ToNull never touches the value, it only nulls the placeholders. Use the reader when parsing, the sentinel in save hooks.

If you want errors instead

Pass throwIfInvalid: true and a value that cannot be used raises a CkDateTimeException (from SafeCoder.CommonKit.Exceptions) instead of a sentinel, empty text or a fallback. The exception carries the Input, what was Expected and a short Reason, flattened into a one-line message. The OrNull twins have no such switch — they never throw; null is their answer for anything unusable.

CkDateTime.ToDateTime(
    input: "junk",
    throwIfInvalid: true
);   // throws CkDateTimeException — the message says why

CkDateTime.ToDateTimeOrNull("junk");   // -> null  (the OrNull twins never throw)