【问题标题】:Find Closest Week Day in DayOfWeek List在 DayOfWeek 列表中查找最近的工作日
【发布时间】:2025-11-21 02:20:10
【问题描述】:

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

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

给定该列表,我需要将其与 Datetime 参数进行比较,在 DayOfWeek 列表中找到 DateTime 参数最接近的星期几,并根据它在列表中的星期几向 DateTime 参数添加天数。

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

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

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

将下一个最近一周的距离从传入日期转换为 int 是理想的(至少对我而言),这样我可以执行以下操作:

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

但我愿意接受建议。

我尝试过 LINQ 语句,例如:

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

但无济于事。一定有一些我缺少的花哨的 LINQ 语句。

提醒一下,如果传入的日期是星期日并且列表中最近的工作日是星期六(类似地,如果传入的日期是星期一,最近的工作日是星期五,日期需要一直遍历到星期五。

如果我遗漏了什么或者我只是说不通,请告诉我。

欢迎所有帮助!谢谢。

【问题讨论】:

  • 我没有时间测试这个但是看看这个question
  • 您正在计算一个 TimeSpan,它是两个日期的差值。 DateTime 的默认时间是午夜,所以第一个问题是您比较的是一天中的什么时间?是午夜还是其他时间?
  • 应该是午夜。

标签: c# linq datetime dayofweek


【解决方案1】:

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

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

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

【讨论】:

  • 这在大多数情况下都有效!我有一个问题。我不确定我是否希望星期天成为一周的最后一天,我只需要它以便如果传入的日期是星期一,并且最近的工作日是星期六(或星期五),则日期可以有效地回溯到周六(或周五)……如果有道理的话。例如,如果列表包含周二和周五,并且传入的日期是周日,那么您能否修改您的答案以将日期撞到周二(以考虑等距向前规则)?这是我唯一的批评。否则它会完美运行!
  • 解释得很好!
  • @wibby35 不客气。我已经根据您的评论重新访问并更新了答案。不幸的是,它需要与最初想法完全不同的方法,所以最后它变得更接近(如果不相等,只是不同的实现/编码风格)同时由 NetMage 发布的另一个答案。我没有测试另一个答案,但如果它有效,我认为为了正确起见你应该接受它,因为它首先到达了正确的方向。
  • @IvanStoev 感谢您进行修改!我没有去测试最终的更改,但我一直在使用 NetMage 提供的答案,它似乎运行良好。不过感谢您的帮助!
  • @IvanStoev 或其他任何人,这完全是在黑暗中拍摄,但是您对 T-SQL 有什么好处吗?你知道如何在 T-SQL 中实现同样的情况吗?基本上,我有一堆存储过程,它们有一个 DateTime 变量,需要根据列表中包含的内容将其设置为最近的工作日。所以它基本上只是相同的情况,但在 SQL 方面。
【解决方案2】:

通过辅助函数,可以使用 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;
}

【讨论】: