1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
public static class MidnightPartitioner { public struct SchedulePartition { public DateTime Start { get; set; } public DateTime End { get; set; } } /// <summary> /// Splits a starting and ending DateTime range into /// SchedulePartition objects. Each SchedulePartition /// represents a span of time spent in a specific date. /// </summary> /// <param name="scheduleStart"></param> /// <param name="scheduleEnd"></param> /// <returns></returns> public static IEnumerable<SchedulePartition> Break(DateTime scheduleStart, DateTime scheduleEnd) { List<SchedulePartition> dayBlocks = new List<SchedulePartition>(); if (scheduleStart <= scheduleEnd) { DateTime tomorrow = scheduleStart.Date.AddDays(1); // Determine when tomorrow is. if (tomorrow < scheduleEnd) /* Schedule ends after tomorrow */ { // Calculates the time spent spent in the scheduleStart date. TimeSpan timeSpentInScheduleStartDate = tomorrow.Subtract(scheduleStart); // Calculates the time spent in the day after the schedule start date TimeSpan timeSpentInDayAfterScheudleStart = scheduleEnd.Subtract(tomorrow); dayBlocks.Add( new SchedulePartition { Start = scheduleStart, End = scheduleStart.Add(timeSpentInScheduleStartDate) } ); if (timeSpentInDayAfterScheudleStart.TotalDays > 1) { dayBlocks.AddRange( Break(tomorrow, scheduleEnd) ); } else { dayBlocks.Add( new SchedulePartition { Start = tomorrow, End = tomorrow.Add(timeSpentInDayAfterScheudleStart) } ); } } else { /* Schedule ends before tomorrow. Cannot be partitioned around midnight. * Return the uncut schedule as a SchedulePartition */ return new[] { new SchedulePartition { Start = scheduleStart, End = scheduleEnd } }; } } else throw new ArgumentException("Chronological error with scheduleStart and scheduleEnd arguments"); return dayBlocks; } }
Refactorings
No refactoring yet !
Ants
July 5, 2009, July 05, 2009 09:52, permalink
I refactored the code to use a loop instead of recursion.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
public static class MidnightPartitioner { public struct SchedulePartition { public DateTime Start { get; set; } public DateTime End { get; set; } } static DateTime MinDateTime(DateTime a, DateTime b) { return a <= b ? a : b; } public static IEnumerable<SchedulePartition> Break(DateTime scheduleStart, DateTime scheduleEnd) { if (scheduleStart > scheduleEnd) throw new ArgumentException("Chronological error with scheduleStart and scheduleEnd arguments"); List<SchedulePartition> dayBlocks = new List<SchedulePartition>(); DateTime tomorrow = scheduleStart.Date; do { tomorrow = tomorrow.AddDays(1); dayBlocks.Add(new SchedulePartition { Start = scheduleStart, End = MinDateTime(tomorrow, scheduleEnd) }); scheduleStart = tomorrow; } while (scheduleStart < scheduleEnd); return dayBlocks; } } public class MidnightPartitionerFacts { [Fact] public void ThrowsOnOutOfOrder() { DateTime start = new DateTime(2009, 6, 12, 17, 0, 0); DateTime end = new DateTime(2009, 6, 12, 9, 0, 0); Assert.Throws<ArgumentException>(() => (MidnightPartitioner.Break(start, end))); } void AssertSum(DateTime start, DateTime end, IEnumerable<MidnightPartitioner.SchedulePartition> parts) { TimeSpan span = TimeSpan.Zero; foreach (var part in parts) { Assert.True(part.End >= part.Start); span += part.End - part.Start; } Assert.Equal(end - start, span); } [Fact] public void HandlesNoTimeSpent() { DateTime start = new DateTime(2009, 6, 12, 9, 0, 0); DateTime end = start; var parts = new List<MidnightPartitioner.SchedulePartition>(MidnightPartitioner.Break(start, end)); Assert.Equal(1, parts.Count); Assert.Equal(start, parts[0].Start); Assert.Equal(end, parts[0].End); AssertSum(start, end, parts); } [Fact] public void HandlesSameDayBlocks() { DateTime start = new DateTime(2009, 6, 12, 9, 0, 0); DateTime end = new DateTime(2009, 6, 12, 17, 0, 0); var parts = new List<MidnightPartitioner.SchedulePartition>(MidnightPartitioner.Break(start, end)); Assert.Equal(1, parts.Count); Assert.Equal(start, parts[0].Start); Assert.Equal(end, parts[0].End); AssertSum(start, end, parts); } [Fact] public void HandlesSameDayBlocksStartingAtMidnight() { DateTime start = new DateTime(2009, 6, 12, 0, 0, 0); DateTime end = new DateTime(2009, 6, 12, 17, 0, 0); var parts = new List<MidnightPartitioner.SchedulePartition>(MidnightPartitioner.Break(start, end)); Assert.Equal(1, parts.Count); Assert.Equal(start, parts[0].Start); Assert.Equal(end, parts[0].End); AssertSum(start, end, parts); } [Fact] public void HandlesSameDayBlocksEndingAtMidnight() { DateTime start = new DateTime(2009, 6, 12, 17, 0, 0); DateTime end = new DateTime(2009, 6, 13, 0, 0, 0); var parts = new List<MidnightPartitioner.SchedulePartition>(MidnightPartitioner.Break(start, end)); Assert.Equal(1, parts.Count); Assert.Equal(start, parts[0].Start); Assert.Equal(end, parts[0].End); AssertSum(start, end, parts); } [Fact] public void HandlesBlocksMidnightToMidnight() { DateTime start = new DateTime(2009, 6, 12, 0, 0, 0); DateTime end = new DateTime(2009, 6, 13, 0, 0, 0); var parts = new List<MidnightPartitioner.SchedulePartition>(MidnightPartitioner.Break(start, end)); Assert.Equal(1, parts.Count); Assert.Equal(start, parts[0].Start); Assert.Equal(end, parts[0].End); AssertSum(start, end, parts); } [Fact] public void HandlesShortBlocksAcrossMidnight() { DateTime start = new DateTime(2009, 6, 12, 23, 59, 59); DateTime midnight = new DateTime(2009, 6, 13, 0, 0, 0); DateTime end = new DateTime(2009, 6, 13, 0, 0, 1); var parts = new List<MidnightPartitioner.SchedulePartition>(MidnightPartitioner.Break(start, end)); Assert.Equal(2, parts.Count); Assert.Equal(start, parts[0].Start); Assert.Equal(midnight, parts[0].End); Assert.Equal(midnight, parts[1].Start); Assert.Equal(end, parts[1].End); AssertSum(start, end, parts); } [Fact] public void HandlesBlocksAcrossTwoMidnights() { DateTime start = new DateTime(2009, 6, 12, 9, 0, 0); DateTime midnight1 = new DateTime(2009, 6, 13, 0, 0, 0); DateTime midnight2 = new DateTime(2009, 6, 14, 0, 0, 0); DateTime end = new DateTime(2009, 6, 14, 17, 0, 0); var parts = new List<MidnightPartitioner.SchedulePartition>(MidnightPartitioner.Break(start, end)); Assert.Equal(3, parts.Count); Assert.Equal(start, parts[0].Start); Assert.Equal(midnight1, parts[0].End); Assert.Equal(midnight1, parts[1].Start); Assert.Equal(midnight2, parts[1].End); Assert.Equal(midnight2, parts[2].Start); Assert.Equal(end, parts[2].End); AssertSum(start, end, parts); } }
Ants
July 5, 2009, July 05, 2009 10:25, permalink
A variation of the above implementation. This flavor gets rid of the dayBlocks list by using the compiler support for IEnumerable.
Additionally, the partEnd variable introduced in line 23 will make it easier to change the block sizes in the future, by simply changing how the tomorrow variable is initialized and incremented.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
public static class MidnightPartitioner { public struct SchedulePartition { public DateTime Start { get; set; } public DateTime End { get; set; } } static DateTime MinDateTime(DateTime a, DateTime b) { return a <= b ? a : b; } public static IEnumerable<SchedulePartition> Break(DateTime scheduleStart, DateTime scheduleEnd) { if (scheduleStart > scheduleEnd) throw new ArgumentException("Chronological error with scheduleStart and scheduleEnd arguments"); DateTime tomorrow = scheduleStart.Date; do { tomorrow = tomorrow.AddDays(1); DateTime partEnd = MinDateTime(tomorrow, scheduleEnd); yield return new SchedulePartition { Start = scheduleStart, End = partEnd }; scheduleStart = partEnd; } while (scheduleStart < scheduleEnd); } } public class MidnightPartitionerFacts { [Fact] public void ThrowsOnOutOfOrder() { DateTime start = new DateTime(2009, 6, 12, 17, 0, 0); DateTime end = new DateTime(2009, 6, 12, 9, 0, 0); Assert.Throws<ArgumentException>(() => MidnightPartitioner.Break(start, end).GetEnumerator().MoveNext()); } void AssertSum(DateTime start, DateTime end, IEnumerable<MidnightPartitioner.SchedulePartition> parts) { TimeSpan span = TimeSpan.Zero; foreach (var part in parts) { Assert.True(part.End >= part.Start); span += part.End - part.Start; } Assert.Equal(end - start, span); } [Fact] public void HandlesNoTimeSpent() { DateTime start = new DateTime(2009, 6, 12, 9, 0, 0); DateTime end = start; var parts = new List<MidnightPartitioner.SchedulePartition>(MidnightPartitioner.Break(start, end)); Assert.Equal(1, parts.Count); Assert.Equal(start, parts[0].Start); Assert.Equal(end, parts[0].End); AssertSum(start, end, parts); } [Fact] public void HandlesSameDayBlocks() { DateTime start = new DateTime(2009, 6, 12, 9, 0, 0); DateTime end = new DateTime(2009, 6, 12, 17, 0, 0); var parts = new List<MidnightPartitioner.SchedulePartition>(MidnightPartitioner.Break(start, end)); Assert.Equal(1, parts.Count); Assert.Equal(start, parts[0].Start); Assert.Equal(end, parts[0].End); AssertSum(start, end, parts); } [Fact] public void HandlesSameDayBlocksStartingAtMidnight() { DateTime start = new DateTime(2009, 6, 12, 0, 0, 0); DateTime end = new DateTime(2009, 6, 12, 17, 0, 0); var parts = new List<MidnightPartitioner.SchedulePartition>(MidnightPartitioner.Break(start, end)); Assert.Equal(1, parts.Count); Assert.Equal(start, parts[0].Start); Assert.Equal(end, parts[0].End); AssertSum(start, end, parts); } [Fact] public void HandlesSameDayBlocksEndingAtMidnight() { DateTime start = new DateTime(2009, 6, 12, 17, 0, 0); DateTime end = new DateTime(2009, 6, 13, 0, 0, 0); var parts = new List<MidnightPartitioner.SchedulePartition>(MidnightPartitioner.Break(start, end)); Assert.Equal(1, parts.Count); Assert.Equal(start, parts[0].Start); Assert.Equal(end, parts[0].End); AssertSum(start, end, parts); } [Fact] public void HandlesBlocksMidnightToMidnight() { DateTime start = new DateTime(2009, 6, 12, 0, 0, 0); DateTime end = new DateTime(2009, 6, 13, 0, 0, 0); var parts = new List<MidnightPartitioner.SchedulePartition>(MidnightPartitioner.Break(start, end)); Assert.Equal(1, parts.Count); Assert.Equal(start, parts[0].Start); Assert.Equal(end, parts[0].End); AssertSum(start, end, parts); } [Fact] public void HandlesShortBlocksAcrossMidnight() { DateTime start = new DateTime(2009, 6, 12, 23, 59, 59); DateTime midnight = new DateTime(2009, 6, 13, 0, 0, 0); DateTime end = new DateTime(2009, 6, 13, 0, 0, 1); var parts = new List<MidnightPartitioner.SchedulePartition>(MidnightPartitioner.Break(start, end)); Assert.Equal(2, parts.Count); Assert.Equal(start, parts[0].Start); Assert.Equal(midnight, parts[0].End); Assert.Equal(midnight, parts[1].Start); Assert.Equal(end, parts[1].End); AssertSum(start, end, parts); } [Fact] public void HandlesBlocksAcrossTwoMidnights() { DateTime start = new DateTime(2009, 6, 12, 9, 0, 0); DateTime midnight1 = new DateTime(2009, 6, 13, 0, 0, 0); DateTime midnight2 = new DateTime(2009, 6, 14, 0, 0, 0); DateTime end = new DateTime(2009, 6, 14, 17, 0, 0); var parts = new List<MidnightPartitioner.SchedulePartition>(MidnightPartitioner.Break(start, end)); Assert.Equal(3, parts.Count); Assert.Equal(start, parts[0].Start); Assert.Equal(midnight1, parts[0].End); Assert.Equal(midnight1, parts[1].Start); Assert.Equal(midnight2, parts[1].End); Assert.Equal(midnight2, parts[2].Start); Assert.Equal(end, parts[2].End); AssertSum(start, end, parts); } }
I have this little class called MidnightPartitioner which I use to split schedules (represented by a DateTime starting and ending) around midnight. I'm pretty sure my logic is correct in my code, but I have the feeling there is a cleaner and more concise way to split my schedule start and end times into 'blocks' around midnight. Any ideas on how I can clean up this code would be appreciated.
Example:
If scheduleStart and scheduleEnd spans 3 days, then the amount of time spent in each day would make give us 3 SchedulePartition objects. Thus, the TimeSpan length of all schedule partition objects should equal the TimeSpan of scheduleEnd.Subtract(scheduleStart)