我基本上是想做与here相同的事情,只是我想用C#而不是SQL。
我有一堂课:
public class AmountPerPeriod
{
public int Id { get; set; }
public DateTime Startdate { get; set; }
public DateTime Enddate { get; set; }
public decimal Amount { get; set; }
}
对于此示例,我用以下命令填充了
AmountPerPeriod
个项目的列表:var lstAmountPerPeriod = new List<AmountPerPeriod>()
{
new AmountPerPeriod
{
Id = 1,
Startdate = new DateTime(2019, 03, 21),
Enddate = new DateTime(2019, 05, 09),
Amount = 10000
},
new AmountPerPeriod
{
Id = 2,
Startdate = new DateTime(2019, 04, 02),
Enddate = new DateTime(2019, 04, 10),
Amount = 30000
},
new AmountPerPeriod
{
Id = 3,
Startdate = new DateTime(2018, 11, 01),
Enddate = new DateTime(2019, 01, 08),
Amount = 20000
}
};
我希望我的输出是如下所示的AmountPerMonth类的列表:
public class AmountPerMonth
{
public int Id { get; set; }
public int Year { get; set; }
public int Month { get; set; }
public decimal Amount { get; set; }
}
就像我应该尝试的那样,我得到了一种有效的方法,感觉到它变得复杂了。提供正确结果的此方法如下所示:
var result = new List<AmountPerMonth>();
foreach (var item in lstAmountPerPeriod)
{
if (item.Startdate.Year == item.Enddate.Year && item.Startdate.Month == item.Enddate.Month)
{
result.Add(new AmountPerMonth
{
Amount = item.Amount,
Id = item.Id,
Month = item.Startdate.Month,
Year = item.Startdate.Year
});
}
else
{
var numberOfDaysInPeriod = (item.Enddate - item.Startdate).Days+1;
var amountPerDay = item.Amount / numberOfDaysInPeriod;
var periodStartDate = item.Startdate;
bool firstPeriod = true;
while (periodStartDate.ToFirstDateOfMonth() <= item.Enddate.ToFirstDateOfMonth())
{
if (firstPeriod)
{
result.Add(new AmountPerMonth
{
Amount = ((periodStartDate.ToLastDateOfMonth()-periodStartDate).Days+1)*amountPerDay,
Id = item.Id,
Month = periodStartDate.Month,
Year = periodStartDate.Year
});
}
else if (periodStartDate.Month != item.Enddate.Month)
{
result.Add(new AmountPerMonth
{
Amount = ((periodStartDate.ToLastDateOfMonth()-periodStartDate.ToFirstDateOfMonth()).Days+1) * amountPerDay,
Id = item.Id,
Month = periodStartDate.Month,
Year = periodStartDate.Year
});
}
else
{
result.Add(new AmountPerMonth
{
Amount = ((item.Enddate - periodStartDate.ToFirstDateOfMonth()).Days+1) * amountPerDay,
Id = item.Id,
Month = periodStartDate.Month,
Year = periodStartDate.Year
});
}
periodStartDate = periodStartDate.AddMonths(1);
firstPeriod = false;
}
}
}
// assert using fluentassertions
result.Count.Should().Be(7);
result.First().Amount.Should().Be(2200);
result.Last().Amount.Should().BeApproximately(2318.84M, 2);
// list with result basically should contain:
// ID |month |year |amount
// ---|------|-------|--------
// 1 |3 | 2019 | 2200.00
// 1 |4 | 2019 | 6000.00
// 1 |5 | 2019 | 1800.00
// 2 |4 | 2019 |30000.00
// 3 |11 | 2018 | 8695.65
// 3 |12 | 2018 | 8985.51
// 3 |1 | 2019 | 2318.84
就像我说的那样,应该有更简单的方法,甚至可以使用LINQ。有人有建议吗?
提前致谢
最佳答案
这是另一种方式,基本区别在于,我使用for
循环将“第一天”持续更新为当月的第一天或当月第一天,而“最后一天”到该月的最后一天或该期间的最后一天(以较小者为准)。
我还将此方法添加为AmountPerMonth
类的静态方法,该方法接受AmountPerPeriod
并返回List<AmountPerMonth>
。另外,我覆盖了ToString
方法,以输出与您的问题类似的字符串,因此输出看起来相同:
public class AmountPerMonth
{
public int Id { get; set; }
public int Year { get; set; }
public int Month { get; set; }
public decimal Amount { get; set; }
public static List<AmountPerMonth> FromPeriod(AmountPerPeriod period)
{
if (period == null) return null;
var amtPerDay = period.Amount / ((period.EndDate - period.StartDate).Days + 1);
var result = new List<AmountPerMonth>();
for (var date = period.StartDate; date <= period.EndDate;
date = date.AddMonths(1).ToFirstDateOfMonth())
{
var lastDayOfMonth = date.ToLastDateOfMonth();
var lastDay = period.EndDate < lastDayOfMonth
? period.EndDate
: lastDayOfMonth;
var amount = ((lastDay - date).Days + 1) * amtPerDay;
result.Add(new AmountPerMonth
{
Id = period.Id,
Year = date.Year,
Month = date.Month,
Amount = amount
});
}
return result;
}
public override string ToString()
{
return $"{Id,-3} |{Month,-6}| {Year,-6}| {Amount:0.00}";
}
}
我们可以使用此方法作为示例数据中
SelectMany
的参数,以生成列表并输出结果:static void Main(string[] args)
{
var lstAmountPerPeriod = new List<AmountPerPeriod>()
{
new AmountPerPeriod
{
Id = 1,
StartDate = new DateTime(2019, 03, 21),
EndDate = new DateTime(2019, 05, 09),
Amount = 10000
},
new AmountPerPeriod
{
Id = 2,
StartDate = new DateTime(2019, 04, 02),
EndDate = new DateTime(2019, 04, 10),
Amount = 30000
},
new AmountPerPeriod
{
Id = 3,
StartDate = new DateTime(2018, 11, 01),
EndDate = new DateTime(2019, 01, 08),
Amount = 20000
}
};
var amountsPerMonth = lstAmountPerPeriod.SelectMany(AmountPerMonth.FromPeriod);
Console.WriteLine("ID |month |year |amount");
Console.WriteLine("---|------|-------|--------");
Console.WriteLine(string.Join(Environment.NewLine, amountsPerMonth));
GetKeyFromUser("\n\nDone! Press any key to exit...");
}
输出量
注意:上面的代码中使用了这些扩展方法:
public static class Extensions
{
public static DateTime ToFirstDateOfMonth(this DateTime input)
{
return new DateTime(input.Year, input.Month, 1, input.Hour,
input.Minute, input.Second, input.Millisecond, input.Kind);
}
public static DateTime ToLastDateOfMonth(this DateTime input)
{
return new DateTime(input.Year, input.Month,
DateTime.DaysInMonth(input.Year, input.Month), input.Hour,
input.Minute, input.Second, input.Millisecond, input.Kind);
}
}
关于c# - 将每个日期范围的金额分成每月/每年的金额,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/58106660/