Bac730989e88a593d5f23cfa4d41dc6f

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)

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 !

F9a9ba6663645458aa8630157ed5e71e

Ants

July 5, 2009, July 05, 2009 09:52, permalink

1 rating. Login to rate!

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);
        }
    }
F9a9ba6663645458aa8630157ed5e71e

Ants

July 5, 2009, July 05, 2009 10:25, permalink

1 rating. Login to rate!

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);
        }
    }
Bac730989e88a593d5f23cfa4d41dc6f

MRuston

July 7, 2009, July 07, 2009 18:46, permalink

No rating. Login to rate!

Two awesome solutions. Thank you Ants.

Your refactoring





Format Copy from initial code

or Cancel