@@ -620,7 +620,7 @@ public static string ToPeriodStartString(this DateTime start) =>
620620 /// <param name="end">The period end date</param>
621621 /// <returns>The formatted period end date</returns>
622622 public static string ToPeriodEndString ( this DateTime end ) =>
623- IsMidnight ( end ) || IsLastMomentOfDay ( end ) ? $ "{ end . ToShortDateString ( ) } { end . ToShortTimeString ( ) } " : end . ToShortDateString ( ) ;
623+ IsMidnight ( end ) || IsLastMomentOfDay ( end ) ? end . ToShortDateString ( ) : $ "{ end . ToShortDateString ( ) } { end . ToShortTimeString ( ) } ";
624624
625625 /// <summary>Test if the date is in UTC</summary>
626626 /// <param name="dateTime">The source date time</param>
@@ -1388,6 +1388,20 @@ public static List<DatePeriod> Split(this DatePeriod period, List<DateTime> spli
13881388 return splitPeriods ;
13891389 }
13901390
1391+ /// <summary>Get period days</summary>
1392+ /// <param name="period">The period</param>
1393+ public static List < DateTime > Days ( this DatePeriod period )
1394+ {
1395+ var days = new List < DateTime > ( ) ;
1396+ var current = period . Start . Date ;
1397+ while ( current <= period . End . Date )
1398+ {
1399+ days . Add ( current ) ;
1400+ current = current . AddDays ( 1 ) ;
1401+ }
1402+ return days ;
1403+ }
1404+
13911405 /// <summary>Test if a specific time moment is the first day of a period</summary>
13921406 /// <param name="test">The moment to test</param>
13931407 /// <param name="period">The period </param>
@@ -1618,6 +1632,181 @@ public static List<DatePeriod> Intersections(this IEnumerable<DatePeriod> datePe
16181632 }
16191633}
16201634
1635+ /// <summary><see cref="TimePeriod">TimePeriod</see> extension methods</summary>
1636+ public static class TimePeriodExtensions
1637+ {
1638+ /// <summary>Test if a specific time moment is before this period</summary>
1639+ /// <param name="period">The period</param>
1640+ /// <param name="test">The moment to test</param>
1641+ /// <returns>True, if the moment is before this period</returns>
1642+ public static bool IsBefore ( this TimePeriod period , decimal test ) =>
1643+ test < period . Start ;
1644+
1645+ /// <summary>Test if a specific time period is before this period</summary>
1646+ /// <param name="period">The period</param>
1647+ /// <param name="testPeriod">The period to test</param>
1648+ /// <returns>True, if the period is before this period</returns>
1649+ public static bool IsBefore ( this TimePeriod period , TimePeriod testPeriod ) =>
1650+ testPeriod . End < period . Start ;
1651+
1652+ /// <summary>Test if a specific time moment is after this period</summary>
1653+ /// <param name="period">The period</param>
1654+ /// <param name="test">The moment to test</param>
1655+ /// <returns>True, if the moment is after this period</returns>
1656+ public static bool IsAfter ( this TimePeriod period , decimal test ) =>
1657+ test > period . End ;
1658+
1659+ /// <summary>Test if a specific time period is after this period</summary>
1660+ /// <param name="period">The period</param>
1661+ /// <param name="testPeriod">The period to test</param>
1662+ /// <returns>True, if the period is after this period</returns>
1663+ public static bool IsAfter ( this TimePeriod period , TimePeriod testPeriod ) =>
1664+ testPeriod . Start > period . End ;
1665+
1666+ /// <summary>Test if a specific time moment is within the period, including open periods</summary>
1667+ /// <param name="period">The period</param>
1668+ /// <param name="test">The moment to test</param>
1669+ /// <returns>True, if the moment is within this period</returns>
1670+ public static bool IsWithin ( this TimePeriod period , decimal test ) =>
1671+ test . IsWithin ( period . Start , period . End ) ;
1672+
1673+ /// <summary>Test if a specific time period is within the period, including open periods</summary>
1674+ /// <param name="period">The period</param>
1675+ /// <param name="testPeriod">The period to test</param>
1676+ /// <returns>True, if the test period is within this period</returns>
1677+ public static bool IsWithin ( this TimePeriod period , TimePeriod testPeriod ) =>
1678+ IsWithin ( period , testPeriod . Start ) && IsWithin ( period , testPeriod . End ) ;
1679+
1680+ /// <summary>Test if a specific time moment is within or before the period, including open periods</summary>
1681+ /// <param name="period">The period</param>
1682+ /// <param name="test">The moment to test</param>
1683+ /// <returns>True, if the moment is within or before this period</returns>
1684+ public static bool IsWithinOrBefore ( this TimePeriod period , decimal test ) =>
1685+ test <= period . End ;
1686+
1687+ /// <summary>Test if a specific time moment is within or after the period, including open periods</summary>
1688+ /// <param name="period">The period</param>
1689+ /// <param name="test">The moment to test</param>
1690+ /// <returns>True, if the moment is within or after this period</returns>
1691+ public static bool IsWithinOrAfter ( this TimePeriod period , decimal test ) =>
1692+ test >= period . Start ;
1693+
1694+ /// <summary>Test if period is overlapping this period</summary>
1695+ /// <param name="period">The period</param>
1696+ /// <param name="testPeriod">The period to test</param>
1697+ /// <returns>True, if the period is overlapping this period</returns>
1698+ public static bool IsOverlapping ( this TimePeriod period , TimePeriod testPeriod ) =>
1699+ testPeriod . Start < period . End && period . Start < testPeriod . End ;
1700+
1701+ /// <summary>Get the intersection of a time period with this period</summary>
1702+ /// <param name="period">The period</param>
1703+ /// <param name="intersectPeriod">The period to intersect</param>
1704+ /// <returns>The intersecting time period, null if no intersection is present</returns>
1705+ public static TimePeriod Intersect ( this TimePeriod period , TimePeriod intersectPeriod )
1706+ {
1707+ if ( ! IsOverlapping ( period , intersectPeriod ) )
1708+ {
1709+ return null ;
1710+ }
1711+ return new (
1712+ Math . Max ( period . Start , intersectPeriod . Start ) ,
1713+ Math . Min ( period . End , intersectPeriod . End ) ) ;
1714+ }
1715+
1716+ /// <summary>Get the hours of intersection</summary>
1717+ /// <param name="period">The period</param>
1718+ /// <param name="intersectPeriod">The period to intersect</param>
1719+ /// <returns>The intersecting duration in hours, 0 if no intersection is present</returns>
1720+ public static decimal IntersectHours ( this TimePeriod period , TimePeriod intersectPeriod )
1721+ {
1722+ var intersect = Intersect ( period , intersectPeriod ) ;
1723+ return intersect == null ? 0 : ( decimal ) intersect . Duration . TotalHours ;
1724+ }
1725+
1726+ /// <summary>Total duration of all time periods</summary>
1727+ /// <param name="timePeriods">The time periods</param>
1728+ /// <returns>Accumulated total duration</returns>
1729+ public static TimeSpan TotalDuration ( this IEnumerable < TimePeriod > timePeriods )
1730+ {
1731+ var duration = TimeSpan . Zero ;
1732+ return timePeriods . Aggregate ( duration , ( current , period ) => current . Add ( period . Duration ) ) ;
1733+ }
1734+
1735+ /// <summary>Test if any period is overlapping another period</summary>
1736+ /// <param name="timePeriods">The time periods to test</param>
1737+ /// <returns>True, if the period is overlapping this period</returns>
1738+ public static bool HasOverlapping ( this IEnumerable < TimePeriod > timePeriods )
1739+ {
1740+ var periodList = timePeriods . ToList ( ) ;
1741+ for ( var current = 1 ; current < periodList . Count ; current ++ )
1742+ {
1743+ for ( var remain = current + 1 ; remain < periodList . Count ; remain ++ )
1744+ {
1745+ if ( periodList [ remain ] . IsOverlapping ( periodList [ current ] ) )
1746+ {
1747+ return true ;
1748+ }
1749+ }
1750+ }
1751+ return false ;
1752+ }
1753+
1754+ /// <summary>Test if a specific time moment is within any time period</summary>
1755+ /// <param name="timePeriods">The time periods to test</param>
1756+ /// <param name="test">The moment to test</param>
1757+ /// <returns>True, if the moment is within this period</returns>
1758+ public static bool IsWithinAny ( this IEnumerable < TimePeriod > timePeriods , decimal test ) =>
1759+ timePeriods . Any ( periodValue => periodValue . IsWithin ( test ) ) ;
1760+
1761+ /// <summary>Test if a specific time period is within any time period</summary>
1762+ /// <param name="timePeriods">The time periods to test</param>
1763+ /// <param name="testPeriod">The period to test</param>
1764+ /// <returns>True, if the test period is within this period</returns>
1765+ public static bool IsWithinAny ( this IEnumerable < TimePeriod > timePeriods , TimePeriod testPeriod ) =>
1766+ timePeriods . Any ( periodValue => periodValue . IsWithin ( testPeriod ) ) ;
1767+
1768+ /// <summary>Get limits period, from the earliest start to the latest end</summary>
1769+ /// <param name="timePeriods">The time periods to evaluate</param>
1770+ /// <returns>Time period including all time periods, an anytime period for empty collections</returns>
1771+ public static TimePeriod Limits ( this IEnumerable < TimePeriod > timePeriods )
1772+ {
1773+ decimal ? start = null ;
1774+ decimal ? end = null ;
1775+ foreach ( var timePeriod in timePeriods )
1776+ {
1777+ // start
1778+ if ( ! start . HasValue || timePeriod . Start < start . Value )
1779+ {
1780+ start = timePeriod . Start ;
1781+ }
1782+ // end
1783+ if ( ! end . HasValue || timePeriod . End > end . Value )
1784+ {
1785+ end = timePeriod . End ;
1786+ }
1787+ }
1788+ return new ( start , end ) ;
1789+ }
1790+
1791+ /// <summary>Get all intersections of a time period with any time period</summary>
1792+ /// <param name="timePeriods">The time periods to test</param>
1793+ /// <param name="intersectPeriod">The period to intersect</param>
1794+ /// <returns>List of intersecting time periods</returns>
1795+ public static List < TimePeriod > Intersections ( this IEnumerable < TimePeriod > timePeriods , TimePeriod intersectPeriod )
1796+ {
1797+ var intersections = new List < TimePeriod > ( ) ;
1798+ foreach ( var timePeriod in timePeriods )
1799+ {
1800+ var intersection = timePeriod . Intersect ( intersectPeriod ) ;
1801+ if ( intersection != null )
1802+ {
1803+ intersections . Add ( intersection ) ;
1804+ }
1805+ }
1806+ return intersections ;
1807+ }
1808+ }
1809+
16211810/// <summary><see cref="CaseValue">CaseValue</see> extension methods</summary>
16221811public static class CaseValueExtensions
16231812{
@@ -1693,6 +1882,23 @@ public static IEnumerable<DatePeriod> Periods(this IEnumerable<CaseValue> period
16931882 }
16941883 }
16951884
1885+ /// <summary>Get first matching period containing the test date</summary>
1886+ /// <param name="caseValues">The case period values</param>
1887+ /// <param name="date">The date of the case value</param>
1888+ /// <returns>Accumulated total duration</returns>
1889+ public static CaseValue CaseValueWithin ( this IEnumerable < CaseValue > caseValues , DateTime date )
1890+ {
1891+ foreach ( var caseValue in caseValues )
1892+ {
1893+ var period = caseValue . Period ( ) ;
1894+ if ( period . IsWithin ( date ) )
1895+ {
1896+ return caseValue ;
1897+ }
1898+ }
1899+ return null ;
1900+ }
1901+
16961902 /// <summary>Get case period values grouped by value</summary>
16971903 /// <param name="periodValues">The case period values</param>
16981904 /// <returns>Case period values grouped by value</returns>
@@ -1710,7 +1916,7 @@ public static TimeSpan TotalDuration(this IEnumerable<CaseValue> periodValues) =
17101916 /// <param name="intersectPeriod">The period to intersect</param>
17111917 /// <returns>List of intersecting date periods</returns>
17121918 public static List < CaseValue > Intersections ( this IEnumerable < CaseValue > periodValues , DatePeriod intersectPeriod ) =>
1713- [ ..periodValues . Where ( periodValue => periodValue . Period ( ) . IsOverlapping ( intersectPeriod ) ) ] ;
1919+ [ .. periodValues . Where ( periodValue => periodValue . Period ( ) . IsOverlapping ( intersectPeriod ) ) ] ;
17141920
17151921 /// <summary>Get case period values matching a period predicate</summary>
17161922 /// <param name="periodValues">The time periods to test</param>
0 commit comments