【问题标题】:How can one calculate the Nth non-holiday from a given date?如何计算给定日期的第 N 个非节假日?
【发布时间】:2017-10-20 02:37:01
【问题描述】:

假设我有一个List<DateTime> L,其中包含美国的假期日期。让DateTime d 成为今天 2017/10/19。我想计算与d 连续的下一个第n 个非假日日期(假设n 是一个正整数)。

为此,涉及“迭代所有日期>= d,但属于L 的日期除外”的概念。处理这个问题的最佳 C# 设计是什么? (性能很重要。)

备注

  1. 在“迭代”过程中的每个非节假日,我都会使用当前的非节假日来进行计算
  2. n 可能是“365”的 40-50 倍
  3. 假期日期的集合确实是有限的,我不使用由生成规则定义的假期日历
  4. 我也有兴趣创建直到“d+n”的“迭代”非节假日列表,或两个日期之间的非节假日列表

【问题讨论】:

  • 这与您的标题“迭代值优于某些值”有何联系?
  • 除了;马上改正
  • “性能很重要”毫无意义;正确性也很重要,可维护性、健壮性、易读性、可本地化和其他一千件事也很重要。如果您有理由相信计算日期的成本会影响项目的成败,那么请说出您的绩效预算是多少,您已经尝试过什么,以及如何它不符合您的经验表现标准。
  • @EricLippert 你看,“性能非常重要”不仅仅是一句话。它是:“处理这个问题的最佳 c# 设计是什么?(性能非常重要。)”这意味着:“什么是最好的 c# 设计也可以处理性能。”而“最佳 C# 设计”包括正确性、可维护性、稳健性、易读性、可本地化和其他一千种东西。谢谢。
  • @ujsgeyrr1f0d0d0r0h1h0j0j_juj:“这也可以照顾性能”虽然仍然模棱两可。通常,代码的简单性和优化是紧张的。您需要这样如何高效?例如,如果您需要一种根本不执行任何堆分配的方法,那么这可能会更加复杂 - 损害易读性等。我提供了我认为“合理有效”的方法,但无论他们'再足够有效是另一回事。

标签: c# list


【解决方案1】:

如果假期的集合是有限的(并且合理地小)

创建无限的日期序列真的很容易:

static IEnumerable<DateTime> GetDates(DateTime start)
{
    DateTime current = start;
    while (true)
    {
        yield return current;
        current = current.AddDays(1);
    }
}

然后,您可以将Except 与您的假期一起使用,以获得除您的假期之外的无限日期流:

var nonHolidays = GetDates(DateTime.Today).Except(holidays);

要获取第 n 个值(从 0 开始作为第一个),您可以使用ElementAt

var specificWorkDay = nonHolidays.ElementAt(n);

当然,如果您经常这样做,您可能希望生成“所有前 X 个非假日日期”,这也很容易:

var multipleWorkDays = nonHolidays.Take(x).ToList();

请注意,即使GetDates 看起来像是处于无限循环中,这只是意味着它会一直迭代如果你一直向返回的迭代器询问数据(直到你点击DateTime.MaxValue当然)。它只会根据您的要求进行迭代。只是不要打电话给GetDates().ToList()nonHolidays.ToList()

具有可能无限的假期序列

假设您有一个IEnumerable&lt;DateTime&gt; GetHolidays(DateTime start) 方法,该方法从给定的开始日期按顺序返回一系列假期,可能是无限的。 (顺序的顺序非常重要,否则我们无法可靠地判断日期是否为假期。)

此时,您需要稍微聪明一点,因为Except 是行不通的。尽管如此,它仍然可以有效地完成:

static IEnumerable<DateTime> GetNonHolidays(DateTime start)
{
    var holidays = GetHolidays(start);
    var current = start;
    foreach (var holiday in holidays)
    {
        // Yield everything until the next holiday
        while (current < holiday)
        {
            yield return current;
            current = current.AddDays(1);
        }
        // Skip this holiday, then look for the next one
        current = current.AddDays(1);
    }
    // No more holidays? Now we can just yield infinitely...
    while (true)
    {
        yield return current;
        current = current.AddDays(1);
    }
}

【讨论】:

  • 如果假期的集合也是无限的呢?每年只有一个圣诞节。现在,OP 说假期集在一个列表中,因此它是有限的。但是,如果有人采纳了你的建议,并且还列出了无限懒惰的假期清单呢?
  • @EricLippert:同意,对于一组非列表假期,需要采用不同的方法。将进行编辑以表明这一点。
  • @EricLippert:添加了一个迭代无限方法的解决方案。未经测试,但我认为没关系 - 会一如既往地重视您的反馈。
【解决方案2】:

我们可以使用一些辅助函数来实现您的目标(如果已经有假期列表可供提取,则不需要)

static DateTime NthWeekDay(DateTime value, int n, DayOfWeek weekday) {
    return value.AddDays(-(((int)value.DayOfWeek) % 7) + (((int)weekday) % 7) + (7 * n));
}
static IEnumerable<DateTime> GenerateHolidays(DateTime value) {
    yield return new DateTime(value.Year, 1, 1); // New Year's Day
    yield return NthWeekDay(new DateTime(value.Year, 1, 6), 2, DayOfWeek.Monday); // Martin Luther King Jr. Day
    yield return NthWeekDay(new DateTime(value.Year, 2, 6), 2, DayOfWeek.Monday); // Presidents' Day
    yield return NthWeekDay(new DateTime(value.Year, 5, 31), 0, DayOfWeek.Monday); // Memorial Day
    yield return new DateTime(value.Year, 7, 4); // Independence Day
    yield return NthWeekDay(new DateTime(value.Year, 9, 6), 0, DayOfWeek.Monday); // Labor Day
    yield return NthWeekDay(new DateTime(value.Year, 10, 6), 1, DayOfWeek.Monday); // Columbus Day
    yield return new DateTime(value.Year, 11, 11); // Veterans' Day
    yield return NthWeekDay(new DateTime(value.Year, 11, 3), 3, DayOfWeek.Thursday); // Thanksgiving Day
    yield return new DateTime(value.Year, 12, 25); // Christmas Day
}
static IEnumerable<DateTime> GenerateHolidays(DateTime x, DateTime y) {
    var anchor = ((x < y) ? x : y);
    var diff = Math.Abs((x - y).Days);

    foreach (var year in Enumerable.Range(0, (diff + 1)).Select(i => (new DateTime((anchor.Year + i), 1, 1)))) {
        foreach (var holiday in GenerateHolidays(year)) {
            yield return holiday;
        }
    }
}

然后执行以下操作:

var n = 137;
var begin = DateTime.Today;
var end = begin.AddDays(n);
var dates = Enumerable
    .Range(0, (end - begin).Days)
    .Select(a => begin.AddDays(a))
    .Except(GenerateHolidays(begin, end)); // or use your own IEnumerable<DateTime> of holidays here

dates 将是一个可枚举的对象,其中应包含beginend 之间的所有非假日日期。

【讨论】:

  • 呵呵,我内心的完美主义者想远远做更多,但认为the US Federal Holiday list会更实用。你现在让我研究这是否可能......
猜你喜欢
  • 2023-03-08
  • 2017-09-14
  • 1970-01-01
  • 2020-08-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多