2
\$\begingroup\$

At work there is an input-system where customers are able to specify weekdays and a starting time for an event. The weekdays are integers in the range from 1 (Monday) to 7 (Sunday). After the customer has given the needed input, I need to calculate the next possible DateTime for the event. If the event is on the same day as today and the current time is not yet over the submitted starting-time, the next possible DateTime would be today.

This is what I have came up with, but it looks awful and complicated. You can obviously shorten the linq-queries a little, but i let the intermediate objects in there for sake of debugging.

public static class Test
{ 
 public static void Main( string[] args )
 {
 var now = DateTime.Now;
 var startTimes = new List<TimeSpan> {
 now.TimeOfDay , // get the next possible starting point
 now.TimeOfDay.Add( new TimeSpan( 0 , 1 , 0 ) ) , // starting point is today
 };
 var weekdaysList = new List<int[]> {
 new [] { 1 , 4 , 5 } ,
 new [] { 1 , 2 } ,
 };
 foreach( var weekdays in weekdaysList )
 {
 foreach( var startTime in startTimes )
 {
 var dt = GetNext( weekdays , startTime );
 }
 }
 }
 public static DateTime GetNext( int[] weekdays , TimeSpan startTime )
 {
 // convert to german format (Monday:1 - Sunday:7)
 var currentWeekDay = (int)DateTime.Now.DayOfWeek;
 if( currentWeekDay == 0 )
 currentWeekDay = 7;
 // if today, and not yet passed, return todays starting time
 if( weekdays.Contains( currentWeekDay ) )
 {
 var today = DateTime.Now.Date.Add( startTime );
 if( DateTime.Now < today )
 return today;
 }
 // calculate the day index and keep the added days around (better debugging)
 var days = Enumerable.Range( 0 , 7 )
 .Select( addedDays => new { addedDays , dayIndex = ( currentWeekDay + addedDays ) % 7 } )
 .ToArray();
 // only days that are specified
 var validDays = days
 .Where( x => weekdays.Contains( x.dayIndex ) )
 .ToArray();
 // the first day is the one that has the least days added
 var daysToAdd = validDays.First().addedDays;
 // but we already sorted out today, so the next possible day is the second (if exists) or 1 week/ 7 days later
 if( daysToAdd == 0 )
 {
 if( validDays.Count() > 1 )
 daysToAdd = validDays.ElementAt( 1 ).addedDays;
 else
 daysToAdd = 7;
 }
 return DateTime.Now.Date
 .AddDays( daysToAdd )
 .Add( startTime );
 }
}

I feel like I am missing some obvious shortcuts or linq-fu to shorten this code down and make it easier understandable.

I let this code as it is, but it was bugging me the whole day, how could I improve this?

Only the basic algorithm in the GetNext()-Method is relevant, no error-checking etc.

t3chb0t
44.6k9 gold badges84 silver badges190 bronze badges
asked Mar 2, 2017 at 16:51
\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

After some fresh air I think I got around my mental blockade. I simply convert to the natural C# DayOfWeek representation beforehand instead of fiddling around with the other one.

public static IEnumerable<DayOfWeek> ConvertDaysOfWeek( int[] daysOfWeekIds )
 => daysOfWeekIds.Select( x => x == 7 ? DayOfWeek.Sunday : (DayOfWeek)x );
public static DateTime GetNext( IEnumerable<DayOfWeek> daysOfWeek , TimeSpan startTime )
{
 var nextDate = DateTime.Now.Date;
 var isToday = daysOfWeek.Contains( nextDate.DayOfWeek ) && DateTime.Now.TimeOfDay < startTime;
 if( !isToday )
 nextDate = Enumerable.Range( 1 , 7 )
 .Select( x => nextDate.AddDays( x ) )
 .First( x => daysOfWeek.Contains( x.DayOfWeek ) );
 return nextDate.Add( startTime );
}
answered Mar 2, 2017 at 17:27
\$\endgroup\$
1
  • \$\begingroup\$ In the ConvertDaysOfWeek method, couldn't you just take mod 7 of x and convert? That way 7 would be 0 and the other values would remain the same. It would save you having to code a special check for value 7. \$\endgroup\$ Commented Mar 3, 2017 at 16:37
0
\$\begingroup\$

I made some suggestions for your code:

static void Main(string[] args)
{
 //input samples
 var startTimes = new List<TimeSpan> { DateTime.Now.TimeOfDay, DateTime.Now.AddHours(4).TimeOfDay };
 var weekdaysList = new List<int>() { 1, 4, 5, 2 }; //why did you choose make it a list of integer arrays?, if thats how its populated, you could later flatten it
 //you'll want to sort the lists for convenience
 startTimes.Sort();
 weekdaysList.Sort();
 var next_event = GetNext(weekdaysList, startTimes);
}
public static DateTime GetNext(List<int> daysOfWeekIds, List<TimeSpan> startTimes)
{
 var today = DateTime.Now;
 var today_Day = (int)today.DayOfWeek;
 var today_Time = today.TimeOfDay;
 TimeSpan later_that_day = startTimes.Where(st => st > today_Time).FirstOrDefault() - today_Time;
 //check if day is the same at a later time
 if (daysOfWeekIds.Any(d => (d == today_Day)) && later_that_day.TotalMinutes > 0) //you may want to put a minimum time threshold here for same-day events
 {
 return today.Add(later_that_day);
 }
 //check if it can be done later in the same week
 int later_that_week = daysOfWeekIds.Where(d => d > today_Day).FirstOrDefault() - today_Day;
 if (later_that_week > 0)
 {
 return today.AddDays(later_that_week).Add(later_that_day);
 }
 //pick the earliest date for next week
 return today.AddDays(7 - today_Day).AddDays(daysOfWeekIds.FirstOrDefault()).Add(later_that_day);
}
answered Mar 3, 2017 at 18:05
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.