Как преобразовать список непрерывного диапазона дат в список финансового года в C #?

#c# #.net #datetime

#c# #.net #дата — время #дата и время

Вопрос:

Пример: задан непрерывный список диапазона дат

Список [0] = с 01 января 2001 года по 14 августа 2001 года

Список [1] = с 15 августа 2001 года по 10 июля 2002 года

Давайте предположим, что финансовый год длится с 1 июля по 30 июня (следующего года), поэтому результат должен быть

anotherList[0] = с 01 июля 2000 по 30 июня 2001

   period: 2001 Jan 01 to 2001 Jun 30
  

anotherList[1] = с 01 июля 2001 года по 30 июня 2002 года

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

anotherList[2] = с 01 июля 2002 по 30 июня 2003

   period: 2002 Jul 01 to 2002 Jul 10
  

Опять же, это очень легко решить вручную, но мой метод содержит около 100 строк кода с комбинацией циклов if else, for each и while, которые я считаю уродливыми. Я пытаюсь упростить алгоритм, чтобы его было легче поддерживать и отлаживать. Заранее благодарю.

Комментарии:

1. Что содержит ваш список? Отдельные даты внутри финансовых лет или некоторая структура, представляющая период?

2. @Winston, я имел в виду, что каждый финансовый год должен содержать «исходный диапазон дат», как показано в вопросе выше. Например, anotherList[1] должен содержать 2 внутренних элемента, которые представляют диапазон дат с 01 июля 2001 года по 14 августа 2001 года и с 15 августа 2001 года по 30 июня 2002 года.

Ответ №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>> с каждым внешним перечисляемым, содержащим все даты в двух списках за один финансовый год.

Комментарии:

1. не в каждом году 365 дней. Итак, я полагаю, ваш ответ является приблизительным? Но в целом все просто. Я углублюсь в вашу «концепцию».

2. хм … да. Завтра я сделаю несколько тестовых примеров и отчитаюсь. А теперь мне хорошего ночного сна. Cya.

3. Високосные годы приведут к смещению границ на день — но вы могли бы учесть это в реализации: если d это високосный год, вычтите 1 из .Days . Тем не менее, после обновления вашего вопроса я не уверен, что это то, что вам нужно.

4. Я только что протестировал ваш код, но кажется, что для каждой «группы» он содержит «элементы дня за период», а не элемент диапазона дат.

Ответ №2:

РЕДАКТИРОВАТЬ: Изменено, чтобы включить более четкие требования.

Учитывая список, содержащий непрерывные диапазоны дат, код совсем не обязательно должен быть сложным. На самом деле, вам даже не нужно писать фактический цикл:

 public const int FYBeginMonth = 7, FYBeginDay = 1;

public static int FiscalYearFromDate(DateTime date)
{
    return date.Month > FYBeginMonth ||
           date.Month == FYBeginMonth amp;amp; 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
                             amp;amp; 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
                             amp;amp; 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);
    }
}
  

результаты:

с 01 июля 2000 года по 30 июня 2001 года 
 период: с 01 января 2001 года по 30 июня 2001 года 
с 01 июля 2001 года по 30 июня 2002 года 
 период: с 01 июля 2001 года по 14 августа 2001 года 
 период: с 15 августа 2001 года по 30 июня 2002 года 
с 01 июля 2002 года по 30 июня 2003 года 
 период: с 01 июля 2002 по 10 июля 2002

Комментарии:

1. Я думаю, вы не поняли вопроса. В ваших выходных данных выводится только 3 финансовых года, где в моих выходных данных требовалось добавлять эти «периоды» для каждого финансового года, и я думаю, именно поэтому ваши решения намного проще.

2. @Jeffrey — что вы подразумеваете под «периодами»?

3. Джеффри: Да, по-видимому, я неправильно понял. Думаю, теперь у меня это есть. Это не добавляет особой сложности — по-прежнему нет if s, for s или while s.

4. спасибо, но я действительно думаю, что if for или while было заменено на linq. L0L

5. Да, но без использования потока управления ( if , while ), это просто простая логика: «Для каждого финансового года в диапазоне выберите начало и конец финансового года плюс все периоды, которые начинаются до финансового года и заканчиваются в финансовом году, все периоды, которые начинаются в финансовом году, и все периоды, которые начинаются до финансового года и заканчиваются после финансового года».

Ответ №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 🙂

Комментарии:

1. Ваш подход очень похож на мой, возможно, это единственный способ выполнить разделение. На самом деле я надеялся на более концентрированное / алгоритмическое решение. Возможно, такого нет.

2. Выглядит не страшно: два цикла и одно условие. Но этот код, безусловно, может стать пугающим, если вы добавите в него много низкоуровневой логики. (в частности, если какая-либо функция обработки дат не представлена в .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);
            }


        }