Benyom
Benyom

Reputation: 92

Determine if two dates are within an allowed time span

I got a StartDateTime and EndDateTime that I have to validate.
For them to be valid they have to be within allowed hours which are fully customizable.

// allowed hours
allowedStart = new TimeSpan(08, 00, 0);
allowedEnd   = new TimeSpan(20, 00, 0);

Now the two dates (StartDateTime and EndDateTime) are coming in (some example test cases)

    // Valid date
    obj1.StartDateTime = new DateTime(2020, 1, 30, 19, 10, 0);
    obj1.EndDateTime = new DateTime(2020, 1, 30, 19, 20, 0);

    // End date exceeding
    obj2.StartDateTime = new DateTime(2020, 1, 30, 19, 50, 0);
    obj2.EndDateTime = new DateTime(2020, 1, 30, 20, 15, 0);

    // Start and end date exceeding
    obj3.StartDateTime = new DateTime(2020, 1, 30, 20, 10, 0);
    obj3.EndDateTime = new DateTime(2020, 1, 30, 20, 35, 0);

    // Invalid (overnight) both exceeding
    obj4.StartDateTime = new DateTime(2020, 1, 30, 23, 50, 0);
    obj4.EndDateTime = new DateTime(2020, 1, 31, 0, 35, 0);

    // Start to early
    obj5.StartDateTime = new DateTime(2020, 1, 31, 7, 50, 0);
    obj5.EndDateTime = new DateTime(2020, 1, 31, 8, 15, 0);

I was wondering if there isn't some already implemented function I haven't found since my brain is dying right now. I've been trying to implement this myself like this but the obj4 testcase still kills it:

    if ((obj.StartDateTime.Date.Add(allowedStart) <= obj.StartDateTime) &&
        (allowedEnd < allowedStart 
           ? obj.EndDateTime <= obj.EndDateTime.Date.AddDays(1).Add(allowedEnd) 
           : obj.EndDateTime <= obj.EndDateTime.Date.Add(allowedEnd)))) 
    {
        // valid
    } 
    else 
    {
        // invalid
    }

Upvotes: 1

Views: 943

Answers (2)

Dmitrii Bychenko
Dmitrii Bychenko

Reputation: 186833

Let's start from single value

private static bool WithinSpan(DateTime value, TimeSpan from, TimeSpan to) =>
  value >= value.Date.Add(from) && value <= value.Date.Add(to);

Now we can implement the same with two values:

private static bool WithinSpan(DateTime startDate, DateTime endDate,
                               TimeSpan from, TimeSpan to) =>
// startDate <= endDate &&  // you may want to add this condition as well 
   startDate >= startDate.Date.Add(from) && startDate <= startDate.Date.Add(to) &&
     endDate >= startDate.Date.Add(from) &&   endDate <= startDate.Date.Add(to);

Demo:

  TimeSpan allowedStart = new TimeSpan(08, 00, 0);
  TimeSpan allowedEnd = new TimeSpan(20, 00, 0);

  (DateTime, DateTime)[] tests = new (DateTime, DateTime)[] {
    (new DateTime(2020, 1, 30, 19, 10, 0), new DateTime(2020, 1, 30, 19, 20, 0)),
    (new DateTime(2020, 1, 30, 19, 50, 0), new DateTime(2020, 1, 30, 20, 15, 0)),
    (new DateTime(2020, 1, 30, 20, 10, 0), new DateTime(2020, 1, 30, 20, 35, 0)),
    (new DateTime(2020, 1, 30, 23, 50, 0), new DateTime(2020, 1, 31,  0, 35, 0)),
    (new DateTime(2020, 1, 31,  7, 50, 0), new DateTime(2020, 1, 31,  8, 15, 0)),
  };

  Func<DateTime, DateTime, string> within = 
    (t1, t2) => $"{(WithinSpan(t1, t2, allowedStart, allowedEnd) ? "Yes" : "No")}";

  string report = string.Join(Environment.NewLine, tests
    .Select(test => $"{test.Item1:yyyy-MM-dd HH:mm:ss} .. {test.Item2:yyyy-MM-dd HH:mm:ss} : {within(test.Item1, test.Item2)}"));

  Console.Write(report);

Outcome:

2020-01-30 19:10:00 .. 2020-01-30 19:20:00 : Yes
2020-01-30 19:50:00 .. 2020-01-30 20:15:00 : No
2020-01-30 20:10:00 .. 2020-01-30 20:35:00 : No
2020-01-30 23:50:00 .. 2020-01-31 00:35:00 : No
2020-01-31 07:50:00 .. 2020-01-31 08:15:00 : No

Edit:

Elaborated version is

private static bool WithinSpan(DateTime startDate, DateTime endDate,
                               TimeSpan from, TimeSpan to) {
  // Empty Period
  if (startDate > endDate)
    return false;

  // [from..to] within single day
  if (to >= from)
    return startDate >= startDate.Date.Add(from) && startDate <= startDate.Date.Add(to) &&
           endDate >= startDate.Date.Add(from) && endDate <= startDate.Date.Add(to);

  // [from..midnight..to]
  if (startDate.Day == endDate.Day)
    return startDate >= startDate.Date.Add(from) || endDate <= endDate.Date.Add(to);
  else {
    to = to.Add(TimeSpan.FromDays(1));

    return startDate >= startDate.Date.Add(from) && startDate <= startDate.Date.Add(to) &&
           endDate >= startDate.Date.Add(from) && endDate <= startDate.Date.Add(to);
  }
}

which removes empty periods, and treats from > to TimeSpan as containing midnight.

Demo:

  // from 22:00 to midnight and then up to 06:00
  TimeSpan allowedStart = new TimeSpan(22, 00, 00);
  TimeSpan allowedEnd   = new TimeSpan(06, 00, 00);

  (DateTime, DateTime)[] tests = new (DateTime, DateTime)[] {
    (new DateTime(2020, 1, 30, 19, 10, 0), new DateTime(2020, 1, 30, 19, 20, 0)),
    (new DateTime(2020, 1, 30, 19, 50, 0), new DateTime(2020, 1, 30, 20, 15, 0)),
    (new DateTime(2020, 1, 30, 20, 10, 0), new DateTime(2020, 1, 30, 20, 35, 0)),
    (new DateTime(2020, 1, 30, 23, 50, 0), new DateTime(2020, 1, 31,  0, 35, 0)),
    (new DateTime(2020, 1, 30, 23, 00, 0), new DateTime(2020, 1, 30, 23, 35, 0)),
    (new DateTime(2020, 1, 30,  3, 00, 0), new DateTime(2020, 1, 30,  4, 00, 0)),
    (new DateTime(2020, 1, 31,  4, 50, 0), new DateTime(2020, 1, 31,  8, 15, 0)),
  };

  Func<DateTime, DateTime, string> within =
    (t1, t2) => $"{(WithinSpan(t1, t2, allowedStart, allowedEnd) ? "Yes" : "No")}";

  string report = string.Join(Environment.NewLine, tests
    .Select(test => $"{test.Item1:yyyy-MM-dd HH:mm:ss} .. {test.Item2:yyyy-MM-dd HH:mm:ss} : {within(test.Item1, test.Item2)}"));

  Console.Write(report);

Outcome:

2020-01-30 19:10:00 .. 2020-01-30 19:20:00 : No
2020-01-30 19:50:00 .. 2020-01-30 20:15:00 : No
2020-01-30 20:10:00 .. 2020-01-30 20:35:00 : No
2020-01-30 23:50:00 .. 2020-01-31 00:35:00 : Yes
2020-01-30 23:00:00 .. 2020-01-30 23:35:00 : Yes
2020-01-30 03:00:00 .. 2020-01-30 04:00:00 : Yes
2020-01-31 04:50:00 .. 2020-01-31 08:15:00 : No

Next Edit:
Shortened verison that magically works:

if (startDate > endDate) {
  return false;
}
if (startDate.Day == endDate.Day && to < from) {
  return startDate >= startDate.Date.Add(from) || endDate <= endDate.Date.Add(to);
}
if (to < from) {
  to = to.Add(TimeSpan.FromDays(1));
}
return startDate >= startDate.Date.Add(from) && endDate <= startDate.Date.Add(to);

Upvotes: 2

MaxB
MaxB

Reputation: 438

Following, is the exact piece of code I use, for this not exact goal, in my program:

public bool IsInsideTimeframe(DateTime firstStart, DateTime firstEnd, DateTime secondStart, DateTime secondEnd)
{
    bool isInside;

    if (firstStart.Ticks >= secondStart.Ticks && firstEnd.Ticks <= secondEnd.Ticks)
        isInside = true;
    else
        isInside = false;

    return isInside;
}

Let's say you have two time frames, one between 11:00-14:00, the second is 10:00-15:00. In order to check whether the first timespan is inside the second, you use the function in following way: IsInsideTimeframe(11:00, 14:00, 10:00, 15:00)

You can simply run this function on both of the dates you wish to validate, with the "allowed time span" as the "second" date given.

Upvotes: 0

Related Questions