using System.Text; [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("UnitTests")] namespace System { /// /// Provides extension methods for date and time manipulation. /// public static class DateTimeExtensions { #region Kind /// /// Specifies the as UTC. /// /// The instance. /// A with correct . public static DateTime AsUtc(this DateTime dt) { return dt.Kind switch { DateTimeKind.Local => dt.ToUniversalTime(), DateTimeKind.Utc => dt, _ => DateTime.SpecifyKind(dt, DateTimeKind.Utc), }; } /// /// Specifies the as local time. /// /// The instance. /// A with correct . public static DateTime AsLocal(this DateTime dt) { return dt.Kind switch { DateTimeKind.Local => dt, DateTimeKind.Utc => dt.ToLocalTime(), _ => DateTime.SpecifyKind(dt, DateTimeKind.Local), }; } #endregion Kind #region Aligned Interval /// /// Aligns the to the UTC clock. /// /// The timespan to align. /// A specific offset to the timespan. /// The timespan until the aligned time. public static TimeSpan GetAlignedIntervalUtc(this TimeSpan timeSpan, TimeSpan offset = default) => timeSpan.GetAlignedInterval(DateTime.UtcNow, offset); /// /// Aligns the to the local clock and respects daylight saving time. /// /// The timespan to align. /// A specific offset to the timespan. /// The timespan until the aligned time. public static TimeSpan GetAlignedIntervalLocal(this TimeSpan timeSpan, TimeSpan offset = default) => timeSpan.GetAlignedInterval(DateTime.Now, offset); internal static TimeSpan GetAlignedInterval(this TimeSpan timeSpan, DateTime now, TimeSpan offset = default) { var nowWithOffset = new DateTimeOffset(now); var nextTime = new DateTime(nowWithOffset.Ticks / timeSpan.Ticks * timeSpan.Ticks, now.Kind).Add(offset); var nextTimeWithOffset = new DateTimeOffset(nextTime); if (nextTimeWithOffset <= nowWithOffset) nextTimeWithOffset = nextTimeWithOffset.Add(timeSpan); if (now.Kind == DateTimeKind.Local) return nextTimeWithOffset.LocalDateTime - nowWithOffset.LocalDateTime; return nextTimeWithOffset - nowWithOffset; } #endregion Aligned Interval /// /// Prints the timespan as shortended string. /// /// The timespan /// A value indicating whether to show milliseconds. /// The timespan as string. public static string ToShortString(this TimeSpan timeSpan, bool withMilliseconds = false) { var sb = new StringBuilder(); if (timeSpan < TimeSpan.Zero) sb.Append("-"); if (timeSpan.TotalDays != 0) sb.Append(Math.Abs(timeSpan.Days)).Append("d "); if (timeSpan.TotalHours != 0) sb.Append(Math.Abs(timeSpan.Hours)).Append("h "); if (timeSpan.TotalMinutes != 0) sb.Append(Math.Abs(timeSpan.Minutes)).Append("m "); sb.Append(Math.Abs(timeSpan.Seconds)).Append("s "); if (withMilliseconds) sb.Append(Math.Abs(timeSpan.Milliseconds)).Append("ms"); return sb.ToString().Trim(); } #region Round DateTime /// /// Rounds the to full seconds. /// /// The time value to round. /// public static DateTime RoundToSecond(this DateTime dt) => new(RoundTicks(dt.Ticks, TimeSpan.TicksPerSecond), dt.Kind); /// /// Rounds the to full minutes. /// /// The time value to round. /// public static DateTime RoundToMinute(this DateTime dt) => new(RoundTicks(dt.Ticks, TimeSpan.TicksPerMinute), dt.Kind); /// /// Rounds the to full hours. /// /// The time value to round. /// public static DateTime RoundToHour(this DateTime dt) => new(RoundTicks(dt.Ticks, TimeSpan.TicksPerHour), dt.Kind); /// /// Rounds the to full days. /// /// The time value to round. /// public static DateTime RoundToDay(this DateTime dt) => new(RoundTicks(dt.Ticks, TimeSpan.TicksPerDay), dt.Kind); #endregion Round DateTime #region Round TimeSpan /// /// Rounds the to full seconds. /// /// The time value to round. /// public static TimeSpan RoundToSecond(this TimeSpan timeSpan) => new(RoundTicks(timeSpan.Ticks, TimeSpan.TicksPerSecond)); /// /// Rounds the to full minutes. /// /// The time value to round. /// public static TimeSpan RoundToMinute(this TimeSpan timeSpan) => new(RoundTicks(timeSpan.Ticks, TimeSpan.TicksPerMinute)); /// /// Rounds the to full hours. /// /// The time value to round. /// public static TimeSpan RoundToHour(this TimeSpan timeSpan) => new(RoundTicks(timeSpan.Ticks, TimeSpan.TicksPerHour)); /// /// Rounds the to full days. /// /// The time value to round. /// public static TimeSpan RoundToDay(this TimeSpan timeSpan) => new(RoundTicks(timeSpan.Ticks, TimeSpan.TicksPerDay)); #endregion Round TimeSpan private static long RoundTicks(long ticks, long value) => (ticks + value / 2) / value * value; } }