The calendar your code
shouldn’t get wrong.
Bank holidays that move, leap days, month ends, durations people can read. Transformations computes Easter, English bank holidays and US observances from rules, does date arithmetic that clamps instead of throwing, and formats the result for humans.
02:30 to “2 hours 30 minutes”Holidays computed from rules.
Easter comes from the computus. Bank holidays follow the substitute-day rules: when Christmas or
New Year falls on a weekend, the day moves. Every date below was produced by running
HolidayHelper in C#.
How many days of actual work?
GetEnglishWorkingDaysCount counts the days from the start date up to, but not
including, the end date, skipping weekends and England and Wales bank holidays.
Working-day counter
Computed in your browser from the same holiday data as the calendar, using the library’s rule: start included, end excluded. The three presets match what the C# library returned.
Know the coverage. The holiday rules are for England and Wales: Scotland and Northern Ireland have different days. One-off holidays are listed by year (jubilees, weddings, VE Day 2020, the 2022 funeral and the 2023 coronation), so a future royal event needs a library update. The US set is the observances the library implements, not the full federal list.
Maths that clamps instead of crashing.
AddMonthsSafely and friends stay inside the range of DateTime and land on
the last valid day, so month-end and leap-day cases do not throw. Every result below is real output.
| Expression | Result |
|---|---|
new DateTime(2024,1,31).AddMonthsSafely(1) | 2024-02-29 |
new DateTime(2024,2,29).AddYearsSafely(1) | 2025-02-28 |
DateTime.MaxValue.AddMonthsSafely(1) | 9999-12-31 |
DateTime.MinValue.AddDaysSafely(-1) | 0001-01-01 |
new DateTime(1990,6,15).CalculateAge(new DateTime(2026,6,14)) | 35 |
new DateTime(1990,6,15).CalculateAge(new DateTime(2026,6,15)) | 36 |
new DateTime(2024,2,10).LastOfMonth() | 2024-02-29 |
new DateTime(2026,10,1).FirstOfMonth(DayOfWeek.Monday) | 2026-10-05 |
new DateTime(2026,10,1).LastOfMonth(DayOfWeek.Friday) | 2026-10-30 |
new DateTime(2026,1,31).DateDiff(DateInterval.Month, new DateTime(2026,2,28)) | 0 |
new DateTime(2026,1,15).DateDiff(DateInterval.Month, new DateTime(2026,2,14)) | 0 |
new DateTime(2026,1,15).DateDiff(DateInterval.Month, new DateTime(2026,2,15)) | 1 |
Reading the DateDiff rows. DateDiff with DateInterval.Month counts
complete months, the way a person would: 15 January to 14 February is zero, 15 January to 15 February is one.
Numbers people can read.
Turn a TimeSpan into the format that suits the screen, and a byte count into a size a
person recognises. Three durations, six formats, real output.
| Format | TimeSpan 02:30:15 | TimeSpan 2.03:05:09 | TimeSpan 00:00:45.2500000 |
|---|---|---|---|
VerboseTotalTime | 2 hours | 2 days | 45 seconds |
ShorthandTotalTime | 02h | 2days | 45s |
VerboseTime | 2 hours 30 minutes 15 seconds 0ms | 2 days 3 hours 5 minutes 9 seconds 0ms | 45 seconds 250ms |
ShorthandTime | 02h:30m:15s:000ms | 2days 03h:05m:09s:000ms | 45s:250ms |
hhmm | 02:30 | 51:05 | 00:00 |
hhmmss | 02:30:15 | 51:05:09 | 00:00:45 |
File sizes
ToSizeString() on a long of bytes.
| Bytes | ToSizeString() |
|---|---|
| 0 | 0.00 B |
| 1,023 | 1023.00 B |
| 1,536 | 1.50 KB |
| 1,048,576 | 1.00 MB |
| 1,073,741,824 | 1.00 GB |
| 1,099,511,627,776 | 1.00 TB |
Short elapsed time
ToShortElapsedString() keeps the two largest units.
| TimeSpan | ToShortElapsedString() |
|---|---|
| 00:00:00.2500000 | 250ms |
| 00:01:15 | 1m 15s |
| 02:03:04 | 2h 3m |
| 3.02:01:00 | 3d 2h |
Everyday utilities, one call each.
Small helpers for the chores that sit between features: files, streams, collections and enums.
Copy, move or delete many files at once. Choose whether errors are thrown one by one, gathered into a single exception, or ignored.
FileInfoHelper.CopyToRename a file, change only its extension, or rename it without touching the extension.
FileInfoHelper.ChangeExtensionCopy to memory, read all bytes or text, and seek to the start or end, without reaching for a reader and a using block.
StreamHelper.ReadToEndProcess a huge file line by line, or copy with a progress callback, without loading it all into memory.
StreamExtensions.ForEachLineAdd only the values a collection does not already have, check for null or empty, clone lists and search ignoring case.
CollectionHelper.AddUniqueRead [Description] text, list an enum as value and description pairs, and parse strings or numbers with a default.