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