DateTimeRange, Business Day and various DateTime, DateTimeOffset, TimeSpan extension methods.
Getting Started (Development)
This package can be installed via the NuGet package manager. If you need help, please contact us via in-app support or open an issue. We're always here to help if you have any questions!
- You will need to have Visual Studio Code installed.
- Open the root folder.
Using DateTimeExtensions
Below is a small sampling of the things you can accomplish with DateTimeExtensions, so check it out!
Business Day
Quickly calculate if a datetime is within your hours of business. Check out our unit tests for more usage samples.
var date = DateTime.Now.StartOfDay().AddHours(8); var day = new BusinessDay(date.Date.DayOfWeek, date.Subtract(TimeSpan.FromHours(1)).TimeOfDay, date.AddHours(1).TimeOfDay); bool isDay = day.IsBusinessDay(date);
DateTime Ranges
Quickly work with date ranges with support for Elasticsearch-style date math expressions and bracket notation. Check out our unit tests for more usage samples.
// Basic range parsing var range = DateTimeRange.Parse("yesterday", DateTime.Now); if (range.Contains(DateTime.Now.Subtract(TimeSpan.FromHours(6)))) { //... } // Elasticsearch Date Math support with proper timezone handling var elasticRange = DateTimeRange.Parse("2025-01-01T01:25:35Z||+3d/d", DateTime.Now); // Supports timezone-aware operations: Z (UTC), +05:00, -08:00 // Bracket notation support [start TO end] var bracketRange = DateTimeRange.Parse("[2023-01-01 TO 2023-12-31]", DateTime.Now); // Wildcard support for open-ended ranges var wildcardRange = DateTimeRange.Parse("[2023-01-01 TO *]", DateTime.Now); // From date to infinity
Date Math Features
Supports full Elasticsearch date math syntax:
- Anchors:
now, explicit dates with||separator - Operations:
+1d(add),-1h(subtract),/d(round) - Units:
y(years),M(months),w(weeks),d(days),h/H(hours),m(minutes),s(seconds) - Timezone Support: Preserves explicit timezones (
Z,+05:00,-08:00) or uses system timezone as fallback - Escaped slashes:
\/dis treated identically to/dfor contexts where/must be escaped
Real-World Patterns
// Common date range queries var last15Minutes = DateTimeRange.Parse("[now-15m TO now]", now); // 15 minutes ago through now var lastHour = DateTimeRange.Parse("[now-1h/h TO now-1h/h]", now); // Start through end of last hour var last24Hours = DateTimeRange.Parse("[now-24h TO now]", now); // 24 hours ago through now var today = DateTimeRange.Parse("[now/d TO now/d]", now); // Start of day through end of day var yesterday = DateTimeRange.Parse("[now-1d/d TO now-1d/d]", now); // Start through end of yesterday var tomorrow = DateTimeRange.Parse("[now+1d/d TO now+1d/d]", now); // Start through end of tomorrow var last7Days = DateTimeRange.Parse("[now-7d/d TO now-1d/d]", now); // Last 7 full days (not including today) var thisWeek = DateTimeRange.Parse("[now/w TO now/w]", now); // Start of week through end of week var lastWeek = DateTimeRange.Parse("[now-1w/w TO now-1w/w]", now); // Start through end of last week var thisMonth = DateTimeRange.Parse("[now/M TO now/M]", now); // Start of month through end of month var lastMonth = DateTimeRange.Parse("[now-1M/M TO now-1M/M]", now); // Start through end of last month var yearToDate = DateTimeRange.Parse("[now/y TO now]", now); // Start of year through now var lastYear = DateTimeRange.Parse("[now-1y/y TO now-1y/y]", now); // Start through end of last year // Short-form comparison operators var recentItems = DateTimeRange.Parse(">=now-1h", now); // From 1 hour ago to max var futureOnly = DateTimeRange.Parse(">now", now); // After now to max var beforeToday = DateTimeRange.Parse("<now/d", now); // Min to start of today var throughToday = DateTimeRange.Parse("<=now/d", now); // Min to end of today
Rounding Behavior with Boundaries
Rounding direction (/d, /M, etc.) is controlled by boundary inclusivity, following Elasticsearch range query rounding rules:
| Boundary | Bracket | Operator | Rounding direction | now/d resolves to |
|---|---|---|---|---|
| Inclusive lower | [ |
>= |
Floor (start of period) | 2025-02-16T00:00:00 |
| Exclusive lower | { |
> |
Ceiling (end of period) | 2025-02-16T23:59:59.999 |
| Inclusive upper | ] |
<= |
Ceiling (end of period) | 2025-02-16T23:59:59.999 |
| Exclusive upper | } |
< |
Floor (start of period) | 2025-02-16T00:00:00 |
All four bracket combinations are supported: [], [}, {], {}.
// [now/d TO now/d] → start of today through end of today (full day, inclusive both ends) // [now/d TO now/d} → start of today through start of today (exclusive upper) // {now/d TO now/d] → end of today through end of today (exclusive lower) // {now/d TO now/d} → collapsed to start of today (exclusive both ends inverts the range, which is then normalized)
Examples:
now+1h- One hour from nownow-1d/d- Start of yesterday2025-01-01T01:25:35Z||+3d/d- January 4th, 2025 (start of day) in UTC2023-06-15T14:30:00+05:00||+1M-2d- One month minus 2 days from the specified date/time in +05:00 timezone
Rounding with Inclusive/Exclusive Ranges
Rounding behavior changes depending on whether a range boundary is inclusive or exclusive, following Elasticsearch's conventions:
Inclusive boundaries round to maximize the matched range:
- Inclusive min (
[): rounds down (start of period) -- e.g.,[now/drounds to start of today - Inclusive max (
]): rounds up (end of period) -- e.g.,now/d]rounds to end of today
Exclusive boundaries round to minimize the matched range:
- Exclusive min (
{): rounds up (end of period) -- e.g.,{now/drounds to end of today - Exclusive max (
}): rounds down (start of period) -- e.g.,now/d}rounds to start of today
All four bracket combinations are supported (including mixed):
| Query | Rounding | Effective |
|---|---|---|
[now/h TO now/h] |
min: start of hour, max: end of hour | Entire current hour |
[now/d TO now/d] |
min: start, max: end | Entire current day |
[now/d TO now/d} |
min: start, max: start | Empty (start = start) |
{now/d TO now/d] |
min: end, max: end | Empty (end = end) |
[now/M TO now/M] |
min: start of month, max: end of month | Entire current month |
Common date range patterns:
// Last 15 minutes (rolling)
[now-15m TO now]
// Last hour (rounded to hour boundaries)
[now-1h/h TO now-1h/h]
// Last 4 full hours (rounded to hour boundaries)
[now-4h/h TO now/h]
// Last 24 hours (rolling, including partial today)
[now-24h TO now]
// Today (start of day through end of day)
[now/d TO now/d]
// Yesterday
[now-1d/d TO now-1d/d]
// Last 7 full days (not including today)
[now-7d/d TO now-1d/d]
// Last 30 days (rolling, including partial today)
[now-30d TO now]
// This week
[now/w TO now/w]
// Last week
[now-1w/w TO now-1w/w]
// This month
[now/M TO now/M]
// Last month
[now-1M/M TO now-1M/M]
// Year to date (start of year through now)
[now/y TO now]
// Last year
[now-1y/y TO now-1y/y]
Important:
[now-1d/d TO now-1d/d}(same date, exclusive upper) produces a zero-width range because both sides round to start-of-day. Use[now-1d/d TO now-1d/d](inclusive both ends) instead.
DateMath Utility
For applications that need standalone date math parsing without the range functionality, the DateMath utility class provides direct access to Elasticsearch date math expression parsing. Check out our unit tests for more usage samples.
using Exceptionless.DateTimeExtensions; // Parse date math expressions with standard .NET conventions var baseTime = DateTimeOffset.Now; // Parse method - throws ArgumentException on invalid input var result = DateMath.Parse("now+1h", baseTime); var rounded = DateMath.Parse("now-1d/d", baseTime, isUpperLimit: false); // Start of yesterday // TryParse method - returns bool for success/failure if (DateMath.TryParse("2023.06.15||+1M/d", baseTime, false, out var parsed)) { // Successfully parsed: June 15, 2023 + 1 month, rounded to start of day Console.WriteLine($"Parsed: {parsed:O}"); } // Upper limit behavior affects rounding var startOfDay = DateMath.Parse("now/d", baseTime, isUpperLimit: false); // 00:00:00 var endOfDay = DateMath.Parse("now/d", baseTime, isUpperLimit: true); // 23:59:59.999 // Explicit dates with timezone preservation var utcResult = DateMath.Parse("2025-01-01T01:25:35Z||+3d/d", baseTime); var offsetResult = DateMath.Parse("2023-06-15T14:30:00+05:00||+1M", baseTime);
TimeZone-Aware DateMath
The DateMath utility also provides overloads that work directly with TimeZoneInfo for better timezone handling:
using Exceptionless.DateTimeExtensions; // Parse expressions using a specific timezone var utcTimeZone = TimeZoneInfo.Utc; var easternTimeZone = TimeZoneInfo.FindSystemTimeZoneById("US/Eastern"); // "now" will use current time in the specified timezone var utcResult = DateMath.Parse("now+1h", utcTimeZone); var easternResult = DateMath.Parse("now/d", easternTimeZone, isUpperLimit: false); // TryParse with timezone if (DateMath.TryParse("now+2d-3h", easternTimeZone, false, out var result)) { Console.WriteLine($"Eastern time result: {result:O}"); } // Dates without explicit timezone use the provided TimeZoneInfo var localDate = DateMath.Parse("2023-06-15T14:30:00||+1M", easternTimeZone); // Dates with explicit timezone are preserved regardless of TimeZoneInfo parameter var preservedTz = DateMath.Parse("2023-06-15T14:30:00+05:00||+1M", easternTimeZone); // Result will still have +05:00 offset, not Eastern time offset
The DateMath utility supports the same comprehensive syntax as DateTimeRange but provides a simpler API for direct parsing operations.
TimeUnit
Quickly work with time units. . Check out our unit tests for more usage samples.
TimeSpan oneNanosecond = TimeUnit.Parse("1nanos"); TimeSpan oneMicrosecond = TimeUnit.Parse("1micros"); TimeSpan oneMillisecond = TimeUnit.Parse("1ms"); TimeSpan oneSecond = TimeUnit.Parse("1s"); TimeSpan oneMinute = TimeUnit.Parse("1m"); TimeSpan oneHour = TimeUnit.Parse("1h"); TimeSpan oneDay = TimeUnit.Parse("1d");
DateTime Extension methods
Helper methods that makes working with DateTimes easier. Check out the source for all of the extension methods you can use.
using Exceptionless.DateTimeExtensions; DateTime.Now.ToApproximateAgeString(); // "Just now" var time = DateTime.Now.StartOfMinute(); var lastWeek = DateTime.Now.LastWeek(); var nextWeek = DateTime.Now.NextWeek();
DateTimeOffset Extension methods
Helper methods that makes working with DateTimeOffsets easier. Check out the source for all of the extension methods you can use.
using Exceptionless.DateTimeExtensions; DateTimeOffset.Now.ToApproximateAgeString(); // "Just now" var startOfMonth = DateTimeOffset.Now.ToStartOfMonth(); var endOfMonth = DateTimeOffset.Now.ToEndOfMonth();
Timespan Extension methods
Helper methods that makes working with TimeSpans easier. Check out the source for all of the extension methods you can use.
using Exceptionless.DateTimeExtensions; var years = TimeSpan.FromHours(6).GetYears(); var totalYears = TimeSpan.FromHours(6).GetTotalYears();