在DayOfWeek列表中查找最接近的工作日

时间:2017-09-12 17:08:02

标签: c# linq datetime dayofweek

这可能是一个新手问题,但这里有。

我有一种方法,其中一个类型的DayOfWeek列表附加了一周中的各个日子(可能是星期三和星期六,星期日,星期一和星期五等)。

鉴于该列表,我需要将其与Datetime参数进行比较,在DayOfWeek列表中找到DateTime参数最接近的Week Day,并根据列表中的Week Day参数将日期添加到DateTime参数。

例如,如果传入的DateTime参数是星期日,而我的DayOfWeek列表包含星期三和星期六,则该参数需要移回星期六,因为它在列表中最接近。

同样,如果我的列表包含星期日,星期一和星期六,并且传入的参数是星期四,则该参数必须移至星期六。

最后,如果参数与列表中的两个工作日相等(周三传入,周一和周五在列表中......或周日传入,周二和周五在列表中),则参数需要向前移动到下一个最接近的工作日(在第一种情况下,将是星期五,在第二种情况下是星期二)。

将下一个最接近的工作日的距离从传入的日期转换为int是理想的(至少对我而言),这样我可以做类似的事情:

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

但我愿意接受建议。

我尝试过LINQ语句,例如:

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

但无济于事。必须有一些我想要的LINQ语句。

只是抬头,我无法上班的主要项目是,如果传递的日期是星期日,并且列表中最接近的星期日是星期六,那么从星期日回到星期六的日期是回溯的(类似的,如果传入的日期是星期一,而最接近的工作日是星期五,则日期需要一直遍历到星期五。)

如果我遗漏了任何内容,请告诉我,或者我只是没有意义。

欢迎所有帮助!感谢。

2 个答案:

答案 0 :(得分:3)

让我们将问题分成几个小部分。

注意:以下所有方法都应该放在像这样的类

public static class DayOfWeekExtensions
{
}

首先,您希望Sunday成为一周中的最后一天,而DayOfWeek enum首先定义它。因此,让一个函数说明:

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

然后我们需要一个函数来计算两个DayOfWeek值之间的距离(偏移量):

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

还要添加一个给定一个数据透视的函数,并且两个DayOfWeek值选择两者中最接近的值(应用您的前向优先级规则):

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

现在我们已经准备好实现从序列中找到最接近的一天的方法。它可以通过多种方式实现,这里是使用(最终!:) LINQ Aggregate方法的实现:

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

最后,让我们添加一个计算最近距离的函数:

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

我们完成了。我们刚刚创建了一个简单的小型可重用实用程序类。

您案例中的用法是:

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

更新:事实证明您的要求不同。

应用相同的原理,首先我们需要一个函数来计算一周中两天之间的前后距离的最小值,并应用前向优先规则。

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

它的作用基本上是将可能的-6..6包含范围内的值转换为-3..3包含范围内的值。

然后我们还需要一个函数,它将使用Select + Aggregate来实现相关方法(它也可以使用Min和自定义比较器实现)。它基本上比较了两个绝对距离,并再次应用前向优先级规则:

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

用法将是:

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

答案 1 :(得分:1)

使用辅助函数,可以使用LINQ。

辅助函数使用效用函数计算最接近的星期几,以计算两个DOW之间的前进天数:

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

然后你可以在列表中找到最近的DOW,并使用Aggregate和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;
}

我发现我可以使用元组从帮助函数返回absdist,因为它已经知道它。然后我可以使用LINQ中的Tuple

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