Find Closest Week Day in DayOfWeek List

This is probably a newbie question, but here goes.

I have a method where a type DayOfWeek List that gets appended with various days of the week (Could be Wednesday & Saturday, Sunday, Monday, & Friday, etc).

Given that list, I need to compare it to a Datetime parameter, find the Week Day the DateTime parameter is closest to in the DayOfWeek list, and add days to the DateTime parameter based on what Week Day it is in the list.

For example, if the DateTime parameter being passed in is a Sunday, and my DayOfWeek list contains a Wednesday and Saturday, the parameter needs to be moved back to Saturday since it is closest in the list.

Similarly, if my list contains Sunday, Monday, and Saturday, and the parameter passed in is Thursday, then the parameter would have to be moved to Saturday.

Finally, if the parameter is equidistant from two week days in the list (Wednesday is passed in and Monday and Friday are in the list... or Sunday is passed in and Tuesday and Friday are in the list), then the parameter needs to be moved forward to the next closest week day (which, in the first case, would be Friday, and Tuesday in the second case).

It would be ideal (at least for me), to convert the distance of the next closest week day from the passed in date to an int, that way I can do something like:

passedInDate = passedInDate.AddDays(dayOfWeekDistance);
return passedInDate;

But I am open to suggestions.

I have tried LINQ statements such as:

int dayOfWeekDistance = targetDayOfWeekList.Min(x => (x - passedInDate));

But to no avail. There has to be some fancy LINQ statements that I'm missing.

Just a heads up, the main item I can't get to work is for the date to backtrack from Sunday back to Saturday if the passed in date is Sunday and the closest week day in the list is Saturday (similarly, if the passed in date is Monday and the closest week day is Friday, the date would need to traverse all the way back to Friday).

Please let me know if I missed anything or I'm just plain not making sense.

All help is welcome! Thanks.


With a helper function, LINQ can be used.

The helper function computes the closest day of week using a utility function to compute the number of forward days between the two DOWs:

public int MinDOWDistance(DayOfWeek dow1, DayOfWeek dow2) {
    int FwdDaysDiff(int idow1, int idow2) => idow2 - idow1 + ((idow1 > idow2) ? 7 : 0);
    int fwd12 = FwdDaysDiff((int)dow1, (int)dow2);
    int fwd21 = FwdDaysDiff((int)dow2, (int)dow1);
    return fwd12 < fwd21 ? fwd12 : -fwd21;
}

Then you can find the nearest DOW in the list and return the right number of days to move (and direction) using Aggregate with LINQ:

public int DaysToClosestDOW(DayOfWeek dow1, List<DayOfWeek> dowList) {
    return dowList.Select(dow => {
                                    var cdow = MinDOWDistance(dow1, dow);
                                    return new { dow, dist = cdow, absdist = Math.Abs(cdow) };
                                 })
                  .Aggregate((g1, g2) => (g1.absdist < g2.absdist) ? g1 : ((g1.absdist == g2.absdist) ? ((g1.dist > 0) ? g1 : g2) : g2)).dist;
}

It occurred to me I could use a tuple to return the absdist from the helper function since it already knows it. Then I can just use the Tuple in the LINQ:

public (int dist, int absdist) MinDOWDistance(DayOfWeek dow1, DayOfWeek dow2) {
    int FwdDaysDiff(int idow1, int idow2) => idow2 - idow1 + ((idow1 > idow2) ? 7 : 0);
    int fwd12 = FwdDaysDiff((int)dow1, (int)dow2);
    int fwd21 = FwdDaysDiff((int)dow2, (int)dow1);
    if (fwd12 < fwd21)
        return (fwd12, fwd12);
    else
        return (-fwd21, fwd21);
}

public int DaysToClosestDOW(DayOfWeek dow1, List<DayOfWeek> dowList) {
    return dowList.Select(dow => MinDOWDistance(dow1, dow))
                  .Aggregate((g1, g2) => (g1.absdist < g2.absdist) ? g1 : ((g1.absdist == g2.absdist) ? ((g1.dist > 0) ? g1 : g2) : g2)).dist;
}

Let split the problem to several small parts.

NOTE: All the following methods are supposed to be put inside a class like this

public static class DayOfWeekExtensions
{
}

First, you want Sunday to be the last day of the week, while in the DayOfWeek enum it's defined first. So let make a function accounting for that:

public static int GetIndex(this DayOfWeek source)
{
    return source == DayOfWeek.Sunday ? 6 : (int)source - 1;
}

Then we need a function which calculates the distance (offset) between two DayOfWeek values:

public static int OffsetTo(this DayOfWeek source, DayOfWeek target)
{
    return source.GetIndex() - target.GetIndex();
}

Let also add a function which given a pivot and two DayOfWeek values selects the closest value of the two (applying your forward priority rule):

public static DayOfWeek Closest(this DayOfWeek pivot, DayOfWeek first, DayOfWeek second)
{
    int comp = Math.Abs(first.OffsetTo(pivot)).CompareTo(Math.Abs(second.OffsetTo(pivot)));
    return comp < 0 || (comp == 0 && first.GetIndex() > pivot.GetIndex()) ? first : second;
}

Now we are ready to implement the method which finds the closest day from a sequence. It can be implemented in many ways, here is the implementation using (finally! :) LINQ Aggregate method:

public static DayOfWeek? Closest(this IEnumerable<DayOfWeek> source, DayOfWeek target)
{
    if (!source.Any()) return null;
    return source.Aggregate((first, second) => target.Closest(first, second));
}

Finally, let add a function which calculates the closest distance:

public static int ClosestDistance(this IEnumerable<DayOfWeek> source, DayOfWeek target)
{
    return source.Closest(target)?.OffsetTo(target) ?? 0;
}

And we are done. We just created a small simple reusable utility class.

The usage in your case would be:

int dayOfWeekDistance = targetDayOfWeekList.ClosestDistance(passedInDate.DayOfWeek);

UPDATE: It turns out that your requirement is different.

Applying the same principle, first we need a function which calculates the minimum of the forward and backward distance between two days of the week, applying the forward precedence rule.

public static int MinDistanceTo(this DayOfWeek from, DayOfWeek to)
{
    int dist = to - from;
    return dist >= 4 ? dist - 7 : dist <= -4 ? dist + 7 : dist;
}

What it does basically is to convert the value from the possible -6..6 inclusive range to the value in the -3..3 inclusive range.

Then we'll need just one more function, which will implement the method in question by using Select + Aggregate (it can also be implemented with Min and custom comparer). It basically compares two absolute distances and again applies the forward priority rule:

public static int MinDistanceTo(this DayOfWeek from, IEnumerable<DayOfWeek> to)
{
    if (!to.Any()) return 0;
    return to.Select(x => from.MinDistanceTo(x)).Aggregate((dist1, dist2) =>
    {
        if (dist1 == dist2) return dist1;
        int comp = Math.Abs(dist1).CompareTo(Math.Abs(dist2));
        return comp < 0 || (comp == 0 && dist1 > 0) ? dist1 : dist2;
    });
}

And the usage will be:

int dayOfWeekDistance = passedInDate.DayOfWeek.MinDistanceTo(targetDayOfWeekList);
链接地址: http://www.djcxy.com/p/54894.html

上一篇: C#参数的日期基于星期几

下一篇: 在DayOfWeek列表中查找最近一周的日期