Skip to main content

Dates

These methods cover the DateTime chores that every business app writes by hand: the start and end of a day, week, month, quarter or year, working days, ages, "5 minutes ago" labels, day-by-day loops, ISO week numbers, "the second Tuesday of the month", Unix timestamps and rounding to a 15-minute slot. Reach for them in billing, scheduling and reporting code.

All methods are extension methods on DateTime (and on long for Unix time) in the System namespace, so you do not need a using directive.

At a glance​

MethodWhat it does
StartOfDayMidnight at the start of the day.
EndOfDayThe last tick of the day (23:59:59.9999999).
StartOfWeekMidnight on the first day of the week (Monday by default).
EndOfWeekThe last tick of the last day of the week.
StartOfMonthMidnight on the 1st of the month.
EndOfMonthThe last tick of the last day of the month.
StartOfQuarterMidnight on January, April, July or October 1st.
EndOfQuarterThe last tick of the quarter.
StartOfYearMidnight on January 1st.
EndOfYearThe last tick of December 31st.
IsWeekendtrue on Saturday and Sunday.
AddBusinessDaysAdds Monday-to-Friday days, skipping weekends.
BusinessDaysUntilCounts the working days between two dates.
AgeAge in completed years on a given day.
ToRelativeTimeEnglish text such as "5 minutes ago" or "in 3 days".
ToWalks from one moment to another, one day or one step at a time.
QuarterThe calendar quarter, 1 to 4.
WeekOfYearThe ISO 8601 week number, 1 to 53.
NextThe next date that falls on a given weekday.
PreviousThe previous date that fell on a given weekday.
IsSameDayWhether two moments fall on the same calendar day.
IsSameWeekWhether two moments fall in the same week.
IsSameMonthWhether two moments fall in the same month of the same year.
NthWeekdayOfMonthThe first, second, ... fifth given weekday of the month, or null.
LastWeekdayOfMonthThe last given weekday of the month.
ToUnixTimeSecondsSeconds since 1970-01-01 UTC.
ToUnixTimeMillisecondsMilliseconds since 1970-01-01 UTC.
FromUnixTimeSecondsA UTC DateTime from Unix seconds.
FromUnixTimeMillisecondsA UTC DateTime from Unix milliseconds.
RoundToRounds to the nearest multiple of an interval.
FloorToRounds down to a multiple of an interval.
CeilingToRounds up to a multiple of an interval.
How results are shown

C# has no DateTime literal, so the examples on this page show DateTime results as yyyy-MM-dd HH:mm:ss, plus the Kind when it matters.

"Now" is always passed in​

No method on this page reads the system clock. A method that needs "today" or "now" takes it as an argument: Age(today), ToRelativeTime(reference). The same input always gives the same output, so you can test date logic with fixed dates and no clock mocking.

In application code, get "now" from one place, such as TimeProvider (.NET 8 and later, or the Microsoft.Bcl.TimeProvider package), and pass it down.

Pass the clock's value in
public sealed class MembershipService
{
private readonly TimeProvider _clock;

public MembershipService(TimeProvider clock)
{
_clock = clock;
}

public bool IsAdult(Customer customer)
{
DateTime today = _clock.GetUtcNow().UtcDateTime.Date;
return customer.BirthDate.Age(today) >= 18;
}

public string LastSeenLabel(Customer customer)
{
DateTime now = _clock.GetUtcNow().UtcDateTime;
return customer.LastSeenAt.ToRelativeTime(now);
}
}

public sealed record Customer(string Email, DateTime BirthDate, DateTime LastSeenAt);

Every method keeps the Kind (Utc, Local, Unspecified) of its input. None of them converts between time zones; convert first when two values come from different zones.

Start and end of a period​

Each Start… method returns midnight on the first day of the period. Each End… method returns the last tick (23:59:59.9999999) of the last day. The time of day of the input is ignored, and the Kind is kept.

StartOfDay and EndOfDay​

Use these to turn a moment into the bounds of its calendar day.

Bounds of the day an order was placed
DateTime orderedAt = new DateTime(2024, 5, 15, 14, 30, 15, DateTimeKind.Utc);

DateTime dayStart = orderedAt.StartOfDay(); // → 2024-05-15 00:00:00 (Utc)
DateTime dayEnd = orderedAt.EndOfDay(); // → 2024-05-15 23:59:59.9999999 (Utc)

StartOfDay() returns the same value as the built-in date.Date property. It exists so the code reads the same way as EndOfDay().

End of day in database queries

SQL Server's datetime type cannot store 23:59:59.9999999; it rounds it up to midnight of the next day, so a filter <= dayEnd can include the next day's rows. For queries, a half-open range (>= dayStart and < dayStart.AddDays(1)) is safer. datetime2 stores the full value.

StartOfWeek and EndOfWeek​

StartOfWeek returns midnight on the first day of the week that contains the date. Weeks start on Monday by default (ISO 8601). Pass firstDayOfWeek for a different start day, for example DayOfWeek.Sunday in the United States.

Weekly report bounds
DateTime orderedAt = new DateTime(2024, 5, 15, 14, 30, 15, DateTimeKind.Utc); // a Wednesday

DateTime weekStart = orderedAt.StartOfWeek(); // → 2024-05-13 00:00:00 (Monday)
DateTime weekEnd = orderedAt.EndOfWeek(); // → 2024-05-19 23:59:59.9999999 (Sunday)

DateTime usWeekStart = orderedAt.StartOfWeek(DayOfWeek.Sunday); // → 2024-05-12 00:00:00 (Sunday)
DateTime usWeekEnd = orderedAt.EndOfWeek(DayOfWeek.Sunday); // → 2024-05-18 23:59:59.9999999 (Saturday)

A Sunday belongs to the week that started the Monday before: new DateTime(2024, 5, 19).StartOfWeek() returns 2024-05-13.

StartOfMonth and EndOfMonth​

EndOfMonth knows how many days each month has, including February in leap years.

Billing period for a monthly invoice
DateTime orderedAt = new DateTime(2024, 5, 15, 14, 30, 15, DateTimeKind.Utc);

DateTime periodStart = orderedAt.StartOfMonth(); // → 2024-05-01 00:00:00 (Utc)
DateTime periodEnd = orderedAt.EndOfMonth(); // → 2024-05-31 23:59:59.9999999 (Utc)

DateTime leapFebruaryEnd = new DateTime(2024, 2, 10).EndOfMonth(); // → 2024-02-29 23:59:59.9999999
DateTime februaryEnd = new DateTime(2023, 2, 10).EndOfMonth(); // → 2023-02-28 23:59:59.9999999

StartOfQuarter and EndOfQuarter​

Calendar quarters start on January, April, July and October 1st. Fiscal years that start in another month are not supported.

Quarterly sales window
DateTime orderedAt = new DateTime(2024, 5, 15, 14, 30, 15, DateTimeKind.Utc);

DateTime quarterStart = orderedAt.StartOfQuarter(); // → 2024-04-01 00:00:00 (Utc)
DateTime quarterEnd = orderedAt.EndOfQuarter(); // → 2024-06-30 23:59:59.9999999 (Utc)

StartOfYear and EndOfYear​

Year-to-date bounds
DateTime orderedAt = new DateTime(2024, 5, 15, 14, 30, 15, DateTimeKind.Utc);

DateTime yearStart = orderedAt.StartOfYear(); // → 2024-01-01 00:00:00 (Utc)
DateTime yearEnd = orderedAt.EndOfYear(); // → 2024-12-31 23:59:59.9999999 (Utc)

Business days​

These methods treat Monday to Friday as working days and Saturday and Sunday as the weekend.

Public holidays

The library does not know any holiday calendar. Holidays are counted as working days. If you need holidays, check the result against your own holiday list.

IsWeekend​

IsWeekend() returns true when the date falls on a Saturday or a Sunday.

Weekend check
bool saturday = new DateTime(2024, 5, 18).IsWeekend(); // → true
bool friday = new DateTime(2024, 5, 17).IsWeekend(); // → false

AddBusinessDays​

AddBusinessDays(days) moves forward by the given number of working days and skips weekends. A negative number moves back. The time of day is kept.

Invoice due date: 10 working days after issue
DateTime issuedOn = new DateTime(2024, 5, 17); // a Friday
DateTime dueOn = issuedOn.AddBusinessDays(10); // → 2024-05-31 00:00:00

DateTime fridayMorning = new DateTime(2024, 5, 17, 9, 30, 0);
DateTime nextWorkingDay = fridayMorning.AddBusinessDays(1); // → 2024-05-20 09:30:00 (Monday)
DateTime previousWorkingDay = new DateTime(2024, 5, 20).AddBusinessDays(-1); // → 2024-05-17 00:00:00 (Friday)
Starting on a weekend

AddBusinessDays(0) returns the date unchanged, even when it is a Saturday or Sunday. From a Saturday, AddBusinessDays(1) returns the next Monday, and AddBusinessDays(5) returns the next Friday.

BusinessDaysUntil​

BusinessDaysUntil(end) counts the working days after the start date, up to and including the end date. Only the date parts are used, so the time of day does not matter. The result is negative when end is earlier, and zero for the same day.

It is the inverse of AddBusinessDays: for a start date on a weekday, start.BusinessDaysUntil(start.AddBusinessDays(n)) returns n.

Working days in a support ticket's lifetime
DateTime friday = new DateTime(2024, 5, 17);

int toMonday = friday.BusinessDaysUntil(new DateTime(2024, 5, 20)); // → 1
int toSunday = friday.BusinessDaysUntil(new DateTime(2024, 5, 19)); // → 0
int backwards = new DateTime(2024, 5, 20).BusinessDaysUntil(friday); // → -1

DateTime openedOn = new DateTime(2024, 5, 13);
int open = openedOn.BusinessDaysUntil(new DateTime(2024, 5, 31)); // → 14

int workingDaysIn2024 = new DateTime(2024, 1, 1).BusinessDaysUntil(new DateTime(2024, 12, 31)); // → 261
Counting both ends

The start date is never counted. In 2024, January 1st is a Monday, so the year has 262 weekdays and the call above returns 261. To include the start day, count from the day before: start.AddDays(-1).BusinessDaysUntil(end).

Age​

Age(today) returns the number of completed years between a birth date and today. Only the date parts are compared. Someone born on February 29 has a birthday on February 28 in non-leap years.

Customer age on a given day
DateTime birthDate = new DateTime(2000, 6, 15);

int dayBefore = birthDate.Age(new DateTime(2024, 6, 14)); // → 23
int onBirthday = birthDate.Age(new DateTime(2024, 6, 15)); // → 24

DateTime leapBirthDate = new DateTime(2000, 2, 29);
int leapAge = leapBirthDate.Age(new DateTime(2023, 2, 28)); // → 23
int leapAgeDayBefore = leapBirthDate.Age(new DateTime(2023, 2, 27)); // → 22
warning

Age throws ArgumentOutOfRangeException (parameter today) when today is before the birth date.

Relative time​

ToRelativeTime​

ToRelativeTime(reference) describes a moment relative to reference ("now") in English: "just now", "5 minutes ago", "yesterday", "in 3 days". Use it for activity feeds, comment timestamps and "last seen" labels.

Labels for a comment feed
DateTime now = new DateTime(2024, 5, 15, 12, 0, 0, DateTimeKind.Utc);

string a = now.AddSeconds(-10).ToRelativeTime(now); // → "just now"
string b = now.AddMinutes(-5).ToRelativeTime(now); // → "5 minutes ago"
string c = now.AddHours(-3).ToRelativeTime(now); // → "3 hours ago"
string d = now.AddDays(-1).ToRelativeTime(now); // → "yesterday"
string e = now.AddDays(-3).ToRelativeTime(now); // → "3 days ago"
string f = now.AddDays(61).ToRelativeTime(now); // → "in 2 months"
string g = now.AddMinutes(5).ToRelativeTime(now); // → "in 5 minutes"
string h = now.AddDays(1).ToRelativeTime(now); // → "tomorrow"
string i = now.AddDays(-730).ToRelativeTime(now); // → "2 years ago"

The distance between the two moments picks the wording. Past moments get "… ago", future moments get "in …".

DistanceText
under 45 secondsjust now
45 to 90 secondsa minute
90 seconds to 45 minutesN minutes
45 to 90 minutesan hour
90 minutes to 22 hoursN hours
22 to 36 hoursyesterday / tomorrow
36 hours to 26 daysN days
26 to 45 daysa month
45 to 320 daysN months (a month counts as 30.44 days)
320 to 548 daysa year
548 days or moreN years (a year counts as 365.25 days)

N is rounded to the nearest whole number (halves round up) and is at least 2.

note
  • "yesterday" means 22 to 36 hours ago, not "the previous calendar day". A moment from 23:00 last night, seen at 08:00, is "9 hours ago".
  • The text is always English and does not depend on the current culture.
  • The method subtracts the two values as they are. Pass a reference with the same Kind as the value, for example both UTC.

Date ranges​

To​

start.To(end) walks from start to end one day at a time and keeps the start's time of day. Every step that is not after end is included. start.To(end, step) walks in steps of any positive TimeSpan. Both are lazy sequences, so you can use them in foreach or LINQ.

For whole calendar days, pass dates without a time. Then both ends are included.

Every day of a booking, both ends included
DateTime checkIn = new DateTime(2024, 5, 1, 14, 30, 0);
DateTime checkOut = new DateTime(2024, 5, 3, 9, 0, 0);

List<DateTime> nights = checkIn.Date.To(checkOut.Date).ToList();
// → [2024-05-01, 2024-05-02, 2024-05-03]

If start has a time of day and end is midnight, the last day is left out, because 2024-05-03 14:30 is after 2024-05-03 00:00:

A time of day on the start changes the result
DateTime start = new DateTime(2024, 5, 1, 14, 30, 0);
DateTime end = new DateTime(2024, 5, 3);

List<DateTime> days = start.To(end).ToList();
// → [2024-05-01 14:30:00, 2024-05-02 14:30:00]

With a step, the sequence includes end only when a step lands on it exactly:

30-minute appointment slots
DateTime opening = new DateTime(2024, 5, 15, 9, 0, 0);

List<string> slots = opening
.To(opening.AddHours(2), 30.Minutes())
.Select(slot => slot.ToString("HH:mm", CultureInfo.InvariantCulture))
.ToList();
// → ["09:00", "09:30", "10:00", "10:30", "11:00"]

List<string> uneven = opening
.To(opening.AddHours(1), 25.Minutes())
.Select(slot => slot.ToString("HH:mm", CultureInfo.InvariantCulture))
.ToList();
// → ["09:00", "09:25", "09:50"]

30.Minutes() comes from the Durations page; TimeSpan.FromMinutes(30) works the same way.

note
  • The sequence is empty when end is before start.
  • A zero or negative step throws ArgumentOutOfRangeException (parameter step) when you call To, not when you start the loop.
  • The walk stops at DateTime.MaxValue instead of overflowing.
  • Every item keeps the Kind of start.

See foreach and deconstruction for more on looping over dates.

Calendar numbers​

Quarter​

Quarter() returns the calendar quarter: 1 for January to March, 2 for April to June, 3 for July to September, 4 for October to December.

Group orders by quarter
int q2 = new DateTime(2024, 5, 15).Quarter(); // → 2
int q4 = new DateTime(2024, 11, 2).Quarter(); // → 4

string label = $"Q{new DateTime(2024, 5, 15).Quarter()} 2024"; // → "Q2 2024"

WeekOfYear​

WeekOfYear() returns the ISO 8601 week number, 1 to 53. ISO weeks start on Monday, and week 1 is the week that contains the first Thursday of the year. It returns the same value as ISOWeek.GetWeekOfYear(date).

ISO week numbers
int week = new DateTime(2024, 5, 15).WeekOfYear(); // → 20
int newYearsDay = new DateTime(2027, 1, 1).WeekOfYear(); // → 53
int lateDecember = new DateTime(2024, 12, 30).WeekOfYear(); // → 1
The week can belong to the next or previous year

Around New Year, the ISO week belongs to the neighbouring year: 2027-01-01 is in week 53 of 2026, and 2024-12-30 is in week 1 of 2025. When you build a label such as 2026-W53, take the year from ISOWeek.GetYear(date) (namespace System.Globalization), not from date.Year.

Weekday lookups​

Next and Previous​

Next(dayOfWeek) returns the next date that falls on that weekday. It is always after the date, so a Friday's next Friday is 7 days later. Previous(dayOfWeek) works the same way backwards. Both keep the time of day.

Next delivery day
DateTime orderedAt = new DateTime(2024, 5, 15, 14, 30, 15, DateTimeKind.Utc); // a Wednesday

DateTime nextFriday = orderedAt.Next(DayOfWeek.Friday); // → 2024-05-17 14:30:15 (Utc)
DateTime nextWednesday = orderedAt.Next(DayOfWeek.Wednesday); // → 2024-05-22 14:30:15 (Utc)
DateTime lastMonday = orderedAt.Previous(DayOfWeek.Monday); // → 2024-05-13 14:30:15 (Utc)
DateTime lastWednesday = orderedAt.Previous(DayOfWeek.Wednesday); // → 2024-05-08 14:30:15 (Utc)

Both cross month and year boundaries: new DateTime(2024, 1, 1).Previous(DayOfWeek.Friday) returns 2023-12-29.

"This Friday, or today if today is Friday"

Step back one day first: date.Date.AddDays(-1).Next(DayOfWeek.Friday). On a Friday this returns the same day; on any other day it returns the coming Friday. The .Date makes the result midnight; leave it out to keep the original time of day.

NthWeekdayOfMonth​

NthWeekdayOfMonth(dayOfWeek, occurrence) returns the first, second, third, fourth or fifth given weekday in the date's month, at midnight. Every month has at least four of each weekday, but not always five; the method returns null when the fifth one does not exist. Use it for rules such as "the second Tuesday" or "the fourth Thursday of November".

Recurring meeting and holiday dates
DateTime may = new DateTime(2024, 5, 15, 14, 30, 15, DateTimeKind.Utc);

DateTime? releaseDay = may.NthWeekdayOfMonth(DayOfWeek.Tuesday, 2); // → 2024-05-14 00:00:00 (Utc)
DateTime? firstMonday = may.NthWeekdayOfMonth(DayOfWeek.Monday, 1); // → 2024-05-06 00:00:00 (Utc)
DateTime? fifthFriday = may.NthWeekdayOfMonth(DayOfWeek.Friday, 5); // → 2024-05-31 00:00:00 (Utc)
DateTime? fifthMonday = may.NthWeekdayOfMonth(DayOfWeek.Monday, 5); // → null

DateTime? thanksgiving = new DateTime(2024, 11, 1).NthWeekdayOfMonth(DayOfWeek.Thursday, 4); // → 2024-11-28 00:00:00

The receiver can be any date in the month. The result keeps its Kind.

warning

An occurrence outside 1 to 5 throws ArgumentOutOfRangeException (parameter occurrence).

LastWeekdayOfMonth​

LastWeekdayOfMonth(dayOfWeek) returns the last given weekday in the date's month, at midnight. It never returns null.

Last Friday payroll and month-end holidays
DateTime may = new DateTime(2024, 5, 15, 14, 30, 15, DateTimeKind.Utc);

DateTime payday = may.LastWeekdayOfMonth(DayOfWeek.Friday); // → 2024-05-31 00:00:00 (Utc)
DateTime lastSaturday = may.LastWeekdayOfMonth(DayOfWeek.Saturday); // → 2024-05-25 00:00:00 (Utc)

DateTime memorialDay = new DateTime(2024, 5, 1).LastWeekdayOfMonth(DayOfWeek.Monday); // → 2024-05-27 00:00:00
DateTime leapThursday = new DateTime(2024, 2, 3).LastWeekdayOfMonth(DayOfWeek.Thursday); // → 2024-02-29 00:00:00

Comparing periods​

IsSameDay(other), IsSameWeek(other) and IsSameMonth(other) check whether two moments fall in the same calendar period. The time of day does not matter. IsSameMonth also compares the year, so May 2024 and May 2023 are different months.

IsSameWeek uses Monday-to-Sunday weeks. The overload IsSameWeek(other, firstDayOfWeek) lets you choose the first day.

Group activity by period
bool sameDay = new DateTime(2024, 5, 15, 8, 0, 0).IsSameDay(new DateTime(2024, 5, 15, 23, 0, 0)); // → true

bool sameMonth = new DateTime(2024, 5, 1).IsSameMonth(new DateTime(2024, 5, 31, 23, 0, 0)); // → true
bool otherYear = new DateTime(2024, 5, 15).IsSameMonth(new DateTime(2023, 5, 15)); // → false

DateTime sunday = new DateTime(2024, 5, 19);
DateTime monday = new DateTime(2024, 5, 20);

bool isoWeek = sunday.IsSameWeek(monday); // → false
bool usWeek = sunday.IsSameWeek(monday, DayOfWeek.Sunday); // → true
The Kind is ignored

These methods compare the calendar fields only. A UTC value and a local value with the same date are "the same day" even if they are hours apart in real time. Convert both values to the same zone first.

Unix time​

ToUnixTimeSeconds and ToUnixTimeMilliseconds​

ToUnixTimeSeconds() returns the seconds since 1970-01-01 00:00:00 UTC, the same number that DateTimeOffset.ToUnixTimeSeconds() gives. ToUnixTimeMilliseconds() returns milliseconds, the number JavaScript's Date.now() uses. Use them for JWT claims, API payloads and log correlation.

Timestamps for an API payload
DateTime createdAt = new DateTime(2024, 5, 15, 12, 0, 0, DateTimeKind.Utc);

long seconds = createdAt.ToUnixTimeSeconds(); // → 1715774400

DateTime paidAt = new DateTime(2024, 5, 15, 12, 0, 0, 250, DateTimeKind.Utc);
long milliseconds = paidAt.ToUnixTimeMilliseconds(); // → 1715774400250

How the Kind is handled:

  • Utc values are used as they are.
  • Local values are converted to UTC first.
  • Unspecified values are treated as UTC. The machine's time zone is never used.

Fractions are dropped toward the earlier second or millisecond. Moments before 1970 give negative numbers: half a second before the epoch is -1.

Unspecified differs from DateTimeOffset

new DateTimeOffset(value) treats an Unspecified value as local time, so new DateTimeOffset(value).ToUnixTimeSeconds() can differ from value.ToUnixTimeSeconds() by your UTC offset. This library treats Unspecified as UTC, so the result does not depend on the server's time zone.

FromUnixTimeSeconds and FromUnixTimeMilliseconds​

FromUnixTimeSeconds() and FromUnixTimeMilliseconds() are extension methods on long. They return a DateTime with Kind set to Utc.

Read timestamps from a webhook
DateTime created = 1715774400L.FromUnixTimeSeconds(); // → 2024-05-15 12:00:00 (Utc)
DateTime paid = 1715774400250L.FromUnixTimeMilliseconds(); // → 2024-05-15 12:00:00.250 (Utc)

long expiresAt = 1715778000; // e.g. a JWT "exp" claim
DateTime expiry = expiresAt.FromUnixTimeSeconds(); // → 2024-05-15 13:00:00 (Utc)
An int needs an L suffix

C# does not widen the receiver of an extension method, so 1715774400.FromUnixTimeSeconds() does not compile: the literal is an int. Write 1715774400L, cast to long, or use a long variable.

With a C# 14 compiler you can also call them as static members on DateTime, like DateTimeOffset has: DateTime.FromUnixTimeSeconds(1715774400). See Extension members.

warning

A value outside the DateTime range (years 0001 to 9999) throws ArgumentOutOfRangeException with the parameter name seconds or milliseconds.

Rounding to an interval​

RoundTo, FloorTo and CeilingTo move a moment to a multiple of any positive TimeSpan. Use them to bucket events into 15-minute slots, snap a booking to the hour or group metrics by day. They keep the Kind.

Multiples are counted from DateTime.MinValue (0001-01-01 00:00). Intervals that divide a day evenly, such as 5 minutes, 15 minutes, 1 hour or 1 day, therefore line up with the clock and the calendar.

RoundTo​

RoundTo(interval) returns the nearest multiple. A value exactly halfway rounds up.

Snap meeting times to a 15-minute grid
DateTime a = new DateTime(2024, 5, 15, 10, 7, 0).RoundTo(15.Minutes()); // → 2024-05-15 10:00:00
DateTime b = new DateTime(2024, 5, 15, 10, 8, 0).RoundTo(15.Minutes()); // → 2024-05-15 10:15:00
DateTime c = new DateTime(2024, 5, 15, 10, 7, 30).RoundTo(15.Minutes()); // → 2024-05-15 10:15:00 (midpoint)
DateTime d = new DateTime(2024, 5, 15, 23, 53, 0).RoundTo(15.Minutes()); // → 2024-05-16 00:00:00
DateTime e = new DateTime(2024, 5, 15, 12, 0, 0).RoundTo(1.Days()); // → 2024-05-16 00:00:00

FloorTo​

FloorTo(interval) rounds down to the previous multiple, or returns the value unchanged when it is already a multiple. This is the usual choice for bucketing events.

Bucket page views by 15 minutes
DateTime viewedAt = new DateTime(2024, 5, 15, 10, 59, 59);
DateTime bucket = viewedAt.FloorTo(15.Minutes()); // → 2024-05-15 10:45:00

DateTime loggedAt = new DateTime(2024, 5, 15, 10, 8, 0, DateTimeKind.Utc);
DateTime hour = loggedAt.FloorTo(1.Hours()); // → 2024-05-15 10:00:00 (Utc)

CeilingTo​

CeilingTo(interval) rounds up to the next multiple, or returns the value unchanged when it is already a multiple.

Bill parking by started quarter hour
DateTime a = new DateTime(2024, 5, 15, 10, 1, 0).CeilingTo(15.Minutes()); // → 2024-05-15 10:15:00
DateTime b = new DateTime(2024, 5, 15, 10, 46, 0).CeilingTo(15.Minutes()); // → 2024-05-15 11:00:00
DateTime c = new DateTime(2024, 5, 15, 10, 15, 0).CeilingTo(15.Minutes()); // → 2024-05-15 10:15:00
note
  • A zero or negative interval throws ArgumentOutOfRangeException (parameter interval).
  • RoundTo and CeilingTo throw ArgumentOutOfRangeException when the result would be after DateTime.MaxValue, for example DateTime.MaxValue.CeilingTo(1.Hours()).
  • For weeks, use StartOfWeek. FloorTo(7.Days()) happens to land on a Monday, because 0001-01-01 was a Monday, but it cannot start weeks on another day, and RoundTo(7.Days()) goes to the nearest Monday, which may be in the next week.
  • To round a TimeSpan instead of a DateTime, see Durations.

See also​