My app has the ability to define a daily schedule which is done by selecting the desired days of the week, Monday through Sunday and the time of the event; either in the Am, Pm, or at any time during the day. Multiple schedules can co-exist as long as no two schedules share the same Day and Time combination (e.g. Sunday-Am or Tuesday-Any). Each Schedule contains a list of ordered Activities that must be carried out at times dictated by the Schedule. This is known as an Event and is the concrete performing of the scheduled activities.
My schedule stores this information in 10 bit fields in sql server, represented as boolean fields in my data model. It’s easy to store the fields, and covered my user story at the time, a time when no details about retrieving or display events were available. In the database a Schedule has one or more Activties and an Activity can be part of one or more Sessions.
My app needs to display the currently active Event, if there is one, and if there is not it is to display the previously performed Event. On the initial consideration I thought this a difficult think to represent in code concisely. I didn’t want to write a mess of nested if-then-else statements to check each boolean flag (Monday to Sunday and Am,Pm,Any) as I stepped back in days if there was no currently happening Event.
Remembering that there are multiple Schedules, I need to find the appropriate Schedule in my collection of Schedules then I display all the Activities associated with the Schedule.
- If only one Schedule is defined, return that Schedule
- Otherwise check if there are any Schedules that define Activities for the current time period
- Otherwise find the Schedule that defines the Activities previously performed.
The first two were easy. For #1 if there is only one Schedule return it. For #2 build a query to determine if there is a currently active Schedule.
public virtual Session GetCurentSchedule()
{
// Typically there will only be one schedule so we'll just return that schedule.
if (Schedule.Count() <= 1)
return Schedule.FirstOrDefault();
// Though if there are more one schedule grab the schedule that is currently "Active"
if (Schedule.Any(ActiveScheduleFilter().Compile()))
return Schedule.FirstOrDefault(ActiveScheduleFilter().Compile());
// If there is no currently active Session grab the schedule that was last used.
return Schedule.OrderBy(x => x.TimeElapsedSinceLastScheduledEvent()).FirstOrDefault();
}
// Remember business rules state no two schedule on at the same time.
private Expression<Func<Schedule, bool>> ActiveScheduleFilter()
{
var predicate = PredicateBuilder.True<Schedule>();
switch (DateTimeHelper.Today.DayOfWeek)
{
case DayOfWeek.Monday:
predicate = predicate.And(s => s.OnMonday);
break;
case DayOfWeek.Tuesday:
predicate = predicate.And(s => s.OnTuesday);
break;
case DayOfWeek.Wednesday:
predicate = predicate.And(s => s.OnWednesday);
break;
case DayOfWeek.Thursday:
predicate = predicate.And(s => s.OnThursday);
break;
case DayOfWeek.Friday:
predicate = predicate.And(s => s.OnFriday);
break;
case DayOfWeek.Saturday:
predicate = predicate.And(s => s.OnSaturday);
break;
case DayOfWeek.Sunday:
predicate = predicate.And(s => s.OnSunday);
break;
}
predicate = DateTimeHelper.Now.Hour > 11 ? predicate.And(s => !s.InAm) : predicate.And(s => !s.InPm);
return predicate;
}
The curly problem was finding the Schedule when there isn’t one active. What I needed was a way for each Schedule to tell me when it would have previously occurred and select the Schedule that had most recently occurred. That’s where the TimeElapsedSinceLastScheduleEvent come into play. This method will tell me the time elapsed (TimeSpan) since the Schedule last triggered an Event.
protected virtual bool InternalAny
{
get
{
// If the event is not scheduled for Am or Pm it IS scheduled for Any.
return InAny || (!InAm && !InPm);
}
}
/// <summary>
/// Gets the time since the last scheduled event.
/// If today is Tuesday 14:30 and events are scheduled
/// on Monday and Thursday (Any) then the time since last event
/// would be the time elapsed since the expiration of the previous event.
/// In this case, Tuesday 12:00am / Monday 23:59:59
/// </summary>
public virtual TimeSpan TimeElapsedSinceLastScheduledEvent()
{
var currentTime = DateTimeHelper.Now;
if (ScheduledDays.Any(x => x == currentTime.DayOfWeek))
{
// There is a schedule for today.
if (InternalAny)
{
// And can be done at any time today.
return new TimeSpan();
}
if (((InPm || InternalAny) && currentTime.Hour > 11) || (InAm && currentTime.Hour < 12))
{
// There is a schedule for NOW - return no elapsed
return new TimeSpan();
}
}
var previousEvent = LastSevenDaysEvents.FirstOrDefault();
if (previousEvent != null)
{
return currentTime - previousEvent.EndDate;
}
return new TimeSpan();
}
I calculate an Event (there is no concrete event implementation – yet) by determining all Events for the last seven days (a Schedule only defines a 7 day period).
/// <summary>
/// Gets the event dates for all events in the last 7 days.
/// Doesn't get the current event.
/// Ordered with the most recent event first.
/// </summary>
public virtual IEnumerable<Event> LastSevenDaysEvents
{
get
{
var events = new List<Event>();
var now = DateTimeHelper.Now;
var currentTime = now;
while ((now - currentTime).TotalDays < 8)
{
if (ScheduledDays.Any(x => x == currentTime.DayOfWeek))
{
if (InAm || InternalAny)
{
var @event = new Event() {StartDate = currentTime.Date};
if (InAm)
@event.Duration = new TimeSpan(12, 0, 0);
if (InternalAny)
@event.Duration = new TimeSpan(1, 0, 0, 0);
events.Add(@event);
}
if (InPm && !InternalAny)
{
var @event = new Event()
{
StartDate = currentTime.Date.AddHours(12),
Duration = new TimeSpan(12, 0, 0)
};
events.Add(@event);
}
}
currentTime = currentTime.AddDays(-1);
}
return events.Where(w => w.EndDate < now).OrderByDescending(w => w.StartDate);
}
}
The events are returned with the first event being the most recently occurred (note that means in the past!) ScheduleDays is simply the days that are set for this Schedule
protected IEnumerable<DayOfWeek> ScheduledDays
{
get {
var list = new List<DayOfWeek>();
if (OnMonday) list.Add(DayOfWeek.Monday);
if (OnTuesday) list.Add(DayOfWeek.Tuesday);
if (OnWednesday) list.Add(DayOfWeek.Wednesday);
if (OnThursday) list.Add(DayOfWeek.Thursday);
if (OnFriday) list.Add(DayOfWeek.Friday);
if (OnSaturday) list.Add(DayOfWeek.Saturday);
if (OnSunday) list.Add(DayOfWeek.Sunday);
return list.OrderByDescending(x => (int) x);
}
}
Finally, once I have the correct Schedule I can show the date it was last used (either today or some day in the past)
public virtual DateTime? LastEventDate
{
get {
var timeSinceLastEvent = TimeElapsedSinceLastScheduledEvent();
if (Math.Abs(timeSinceLastEvent.TotalMilliseconds - 0) < 0)
{
// Currently in an active event.
if (InternalAny || InAm)
return DateTimeHelper.Today;
if (InPm)
return DateTimeHelper.Today.AddHours(12);
}
var @event= LastSevenDaysEvents.FirstOrDefault();
return @event != null ? @event.StartDate : (DateTime?)null;
}
}
I’m not sure it’s the best way to solve this problem (also, the problem itself seems a little strange to me – like why aren’t I storing concrete implementations of events and then simply retrieving the latest one – but at the moment there is no user story for this – Agile gone mad?)