开发者

How to chop a continuous date range list into a list of financial year in C#?

Example: given a continuous list of date range

List[0] = from 2001 Jan 01 to 2001 Aug 14

List[1] = from 2001 Aug 15 to 2002 Jul 10

Let’s assume that a financial year is from 1st of July to 30th of June (of next year) so the output should be

AnotherList[0] = from 2000 Jul 01 to 2001 Jun 30

  period: 2001 Jan 01 to 2001 Jun 30

AnotherList[1] = from 2001 July 01 to 2002 Jun 30

  period: 2001 Jul 01 to 2001 Aug开发者_StackOverflow中文版 14
  period: 2001 Aug 15 to 2002 Jun 30

AnotherList[2] = from 2002 July 01 to 2003 Jun 30

  period: 2002 Jul 01 to 2002 Jul 10

Again it's very easy to work out by hand but my method contains close to 100 lines of code with the combination of if else, for each and while loops which I think it's ugly. I am trying to simplify the algorithm so that it's easier to maintain and debug. Thanks in advance.


You can be clever with 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 );

This gives an IEnumerable<IGrouping<int, DateTime>> with each outer enumerable containing all the dates in the 2 lists in a single financial year.


EDIT: Changed to include clearer requirements.

Given a list that contains contiguous date ranges, the code doesn't have to be hard at all. In fact, you don't even have to write an actual loop:

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

           };
}

This assumes some DateRange structures and helper functions, like this:

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

This test code:

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

outputs:

from 2000 Jul 01 to 2001 Jun 30
    period: 2001 Jan 01 to 2001 Jun 30
from 2001 Jul 01 to 2002 Jun 30
    period: 2001 Jul 01 to 2001 Aug 14
    period: 2001 Aug 15 to 2002 Jun 30
from 2002 Jul 01 to 2003 Jun 30
    period: 2002 Jul 01 to 2002 Jul 10


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

Sorry for mix of ruby and java :)


This is my simplest financial year list generate code

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


        }
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜