【问题标题】:How to chop a continuous date range list into a list of financial year in C#?如何在 C# 中将连续日期范围列表切割成财政年度列表?
【发布时间】:2010-07-20 02:16:06
【问题描述】:

示例:给定日期范围的连续列表

列表[0] = 从 2001 年 1 月 1 日到 2001 年 8 月 14 日

列表[1] = 从 2001 年 8 月 15 日到 2002 年 7 月 10 日

假设一个财政年度是从 7 月 1 日到 6 月 30 日(明年),那么输出应该是

AnotherList[0] = 从 2000 年 7 月 1 日到 2001 年 6 月 30 日

  period: 2001 Jan 01 to 2001 Jun 30

AnotherList[1] = 从 2001 年 7 月 1 日到 2002 年 6 月 30 日

  period: 2001 Jul 01 to 2001 Aug 14
  period: 2001 Aug 15 to 2002 Jun 30

AnotherList[2] = 从 2002 年 7 月 1 日到 2003 年 6 月 30 日

  period: 2002 Jul 01 to 2002 Jul 10

同样,手动计算非常容易,但我的方法包含近 100 行代码,其中结合了 if else、for each 和 while 循环,我认为这很难看。我正在尝试简化算法,以便更容易维护和调试。提前致谢。

【问题讨论】:

  • 您的清单包含什么?财政年度内的个别日期,还是代表一个时期的某种结构?
  • @Winston,我的意思是每个财政年度都需要携带“原始日期范围”,如上面的问题所示。例如,AnotherList[1] 应该包含 2 个内部项目,它们代表从 2001 年 7 月 1 日到 2001 年 8 月 14 日和 2001 年 8 月 15 日到 2002 年 6 月 30 日的日期范围。

标签: c# .net datetime


【解决方案1】:

GroupBy 可以让你变得聪明

// Beginning of earliest financial year
var start = new DateTime(2000,7,1); 
var range = Enumerable.Range(0,365*2);

// Some random test data
var dates1 = range.Select(i => new DateTime(2001,1,1).AddDays(i) );
var dates2 = range.Select(i => new DateTime(2003,1,1).AddDays(i) );

// Group by distance in years from beginning of earliest financial year
var finYears =
    dates1
    .Concat(dates2)
    .GroupBy(d => d.Subtract(start).Days / 365 );

这给出了一个IEnumerable<IGrouping<int, DateTime>>,每个外部可枚举包含单个财政年度中两个列表中的所有日期。

【讨论】:

  • 并非每年都有 365 天。所以你的答案是我猜的近似值?但总体来说是更锋利的。我将深入研究您的“概念”。
  • 嗯...是的。我明天会做一些测试用例并报告。现在为我睡个好觉。赛亚。
  • 闰年将导致边界移动一天 - 但您可以在实现中考虑这一点:如果 d 是闰年,则从 .Days 中减去 1。不过,在更新您的问题之后,我不确定这就是您所追求的。
  • 我刚刚测试了您的代码,但似乎对于每个“组”,它都包含“期间的日期项”而不是日期范围项。
【解决方案2】:

编辑:更改为包含更明确的要求。

给定一个包含连续日期范围的列表,代码一点也不难。实际上,您甚至不必编写实际的循环:

public const int FYBeginMonth = 7, FYBeginDay = 1;

public static int FiscalYearFromDate(DateTime date)
{
    return date.Month > FYBeginMonth ||
           date.Month == FYBeginMonth && date.Day >= FYBeginDay ?
        date.Year : date.Year - 1;
}

public static IEnumerable<DateRangeWithPeriods>
              FiscalYears(IEnumerable<DateRange> continuousDates)
{
    int startYear = FiscalYearFromDate(continuousDates.First().Begin),
        endYear = FiscalYearFromDate(continuousDates.Last().End);
    return from year in Enumerable.Range(startYear, endYear - startYear + 1)
           select new DateRangeWithPeriods {
               Range = new DateRange { Begin = FiscalYearBegin(year),
                                       End = FiscalYearEnd(year) },
      // start with the periods that began the previous FY and end in this FY
               Periods = (from range in continuousDates
                          where FiscalYearFromDate(range.Begin) < year
                             && FiscalYearFromDate(range.End) == year
                          select new DateRange { Begin = FiscalYearBegin(year),
                                                 End = range.End })
                          // add the periods that begin this FY
                  .Concat(from range in continuousDates
                          where FiscalYearFromDate(range.Begin) == year
                          select new DateRange { Begin = range.Begin,
                                 End = Min(range.End, FiscalYearEnd(year)) })
                          // add the periods that completely span this FY
                  .Concat(from range in continuousDates
                          where FiscalYearFromDate(range.Begin) < year
                             && FiscalYearFromDate(range.End) > year
                          select new DateRange { Begin = FiscalYearBegin(year),
                                                 End = FiscalYearEnd(year) })

           };
}

这假设了一些 DateRange 结构和辅助函数,如下所示:

public struct DateRange
{
    public DateTime Begin { get; set; }
    public DateTime End { get; set; }
}

public class DateRangeWithPeriods
{
    public DateRange Range { get; set; }
    public IEnumerable<DateRange> Periods { get; set; }
}
private static DateTime Min(DateTime a, DateTime b)
{
    return a < b ? a : b;
}

public static DateTime FiscalYearBegin(int year)
{
    return new DateTime(year, FYBeginMonth, FYBeginDay);
}

public static DateTime FiscalYearEnd(int year)
{
    return new DateTime(year + 1, FYBeginMonth, FYBeginDay).AddDays(-1);
}

这个测试代码:

static void Main()
{
    foreach (var x in FiscalYears(new DateRange[] { 
        new DateRange { Begin = new DateTime(2001, 1, 1),
                        End = new DateTime(2001, 8, 14) },
        new DateRange { Begin = new DateTime(2001, 8, 15),
                        End = new DateTime(2002, 7, 10) } }))
    {
        Console.WriteLine("from {0:yyyy MMM dd} to {1:yyyy MMM dd}",
                          x.Range.Begin, x.Range.End);
        foreach (var p in x.Periods)
            Console.WriteLine(
            "    period: {0:yyyy MMM dd} to {1:yyyy MMM dd}", p.Begin, p.End);
    }
}

输出:

从 2000 年 7 月 1 日到 2001 年 6 月 30 日 期间:2001年1月1日至2001年6月30日 从 2001 年 7 月 1 日到 2002 年 6 月 30 日 期间:2001 年 7 月 1 日至 2001 年 8 月 14 日 期间:2001年8月15日至2002年6月30日 从 2002 年 7 月 1 日到 2003 年 6 月 30 日 期间:2002年7月1日至2002年7月10日

【讨论】:

  • 我想你错过了理解这个问题。您的输出仅打印 3 个财政年度,而我的输出要求为每个财政年度添加这些“期间”,我认为这就是您的解决方案要简单得多的原因。
  • @Jeffrey - 你所说的“期间”是什么意思?
  • 杰弗里:是的,显然我误解了。我想我现在有了。它不会增加太多复杂性——仍然没有ifs、fors 或whiles。
  • 谢谢,但我确实认为 if 或 while 已被 linq 取代。 L0L
  • 是的,但是没有控制流(ifwhile),只是简单的逻辑:“对于范围内的每个会计年度,选择会计年度的开始和结束,加上从 FY 开始到 FY 结束的所有期间,在 FY 开始的所有期间,以及在 FY 之前开始和在 FY 之后结束的所有期间。”
【解决方案3】:
for each range in list
  // determine end of this fiscal year
  cut = new Date(range.start.year, 06, 31)
  if cut < range.start
    cut += year
  end

  if (range.end <= cut)
    // one fiscal year
    result.add range
    continue
  end

  result.add new Range(range.start, cut)

  // chop off whole fiscal years
  start = cut + day
  while (start + year <= range.end)
    result.add new Range(start, start + year - day)
    start += year
  end

  result.add new Range(start, range.end)
end

抱歉混用了 ruby​​ 和 java :)

【讨论】:

  • 您的方法与我的非常相似,也许这是进行砍伐的唯一方法。我实际上希望有一个更集中/算法的解决方案。也许没有。
  • 看起来并不可怕:两个循环和一个条件。但是,如果您在其中放入大量低级逻辑,这段代码肯定会变得可怕。 (特别是,如果 .net 中没有提供某些日期操作功能,我会为它定义一个子例程,而不是将该功能添加到高级代码中)
【解决方案4】:

这是我最简单的财政年度列表生成代码

public void financialYearList()
        {
            List<Dictionary<string, DateTime>> diclist = new List<Dictionary<string, DateTime>>();
//financial year start from july and end june
            int year = DateTime.Now.Month >= 7 ? DateTime.Now.Year + 1 : DateTime.Now.Year;

            for (int i = 7; i <= 12; i++)
            {
                Dictionary<string, DateTime> dic = new Dictionary<string, DateTime>();        
                var first = new DateTime(year-1, i,1);
                var last = first.AddMonths(1).AddDays(-1);
                dic.Add("first", first);
                dic.Add("lst", last);
                diclist.Add(dic);
            }

            for (int i = 1; i <= 6; i++)
            {
                Dictionary<string, DateTime> dic = new Dictionary<string, DateTime>();
                var first = new DateTime(year, i, 1);
                var last = first.AddMonths(1).AddDays(-1);
                dic.Add("first", first);
                dic.Add("lst", last);
                diclist.Add(dic);
            }


        }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-10
    • 1970-01-01
    相关资源
    最近更新 更多