每个日期范围内的拆分金额成每月/每年的金额

问题描述 投票:1回答:2

我基本上只是想与here做相同的事情,只是我想用C#而不是SQL来做。

我有课:

public class AmountPerPeriod
{
    public int Id { get; set; }
    public DateTime Startdate { get; set; }
    public DateTime Enddate { get; set; }
    public decimal Ammount { get; set; }
}

对于此示例,我用以下填充AmountPerPeriod个项目的列表:

var lstAmmountPerPeriod = new List<AmountPerPeriod>()
            {
                new AmountPerPeriod
                {
                    Id = 1,
                    Startdate = new DateTime(2019, 03, 21),
                    Enddate = new DateTime(2019, 05, 09),
                    Ammount = 10000
                },
                new AmountPerPeriod
                {
                    Id = 2,
                    Startdate = new DateTime(2019, 04, 02),
                    Enddate = new DateTime(2019, 04, 10),
                    Ammount = 30000
                },
                new AmountPerPeriod
                {
                    Id = 3,
                    Startdate = new DateTime(2018, 11, 01),
                    Enddate = new DateTime(2019, 01, 08),
                    Ammount = 20000
                }
            };

我希望输出一个如下所示的AmountPerMonth类的列表:

public class AmountPerMonth
{
    public int Id { get; set; }
    public int Year { get; set; }
    public int Month { get; set; }
    public decimal Ammount { get; set; }
}

就像我应该尝试的那样,我得到了一种有效的工作方法,我觉得它很复杂。提供正确结果的此方法如下所示:

var result = new List<AmountPerMonth>();

    foreach (var item in lstAmmountPerPeriod)
    {
        if (item.Startdate.Year == item.Enddate.Year && item.Startdate.Month == item.Enddate.Month)
        {
            result.Add(new AmountPerMonth
            {
                Ammount = item.Ammount,
                Id = item.Id,
                Month = item.Startdate.Month,
                Year = item.Startdate.Year
            });
        }
        else
        {
            var numberOfDaysInPeriod = (item.Enddate - item.Startdate).Days+1;
            var amountPerDay = item.Ammount / numberOfDaysInPeriod;
            var periodStartDate = item.Startdate;

            bool firstPeriod = true;

            while (periodStartDate.ToFirstDateOfMonth() <= item.Enddate.ToFirstDateOfMonth())
            {
                if (firstPeriod)
                {
                    result.Add(new AmountPerMonth
                    {
                        Ammount = ((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
                    {
                        Ammount = ((periodStartDate.ToLastDateOfMonth()-periodStartDate.ToFirstDateOfMonth()).Days+1) * amountPerDay,
                        Id = item.Id,
                        Month = periodStartDate.Month,
                        Year = periodStartDate.Year
                    });
                }
                else
                {
                    result.Add(new AmountPerMonth
                    {
                        Ammount = ((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().Ammount.Should().Be(2200);
result.Last().Ammount.Should().BeApproximately(2318.84M, 2);

// list with result basicly should contain:
// ID |month |year   |ammount
// ---|------|-------|--------
// 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。有人有建议吗?

提前感谢

c# linq date-range
2个回答
1
投票

这是另一种方式,这里的基本区别是我正在使用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 lstAmmountPerPeriod = 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 = lstAmmountPerPeriod.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...");
}

输出

enter image description here

注意:上面的代码中使用了这些扩展方法:

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

2
投票

使用LINQ更容易。

var output =
    from app in lstAmmountPerPeriod
    let days = (int)app.Enddate.Date.Subtract(app.Startdate.Date).TotalDays + 1
    from day in Enumerable.Range(0, days)
    let daily = new AmountPerPeriod()
    {
        Id = app.Id,
        Startdate = app.Startdate.AddDays(day),
        Enddate = app.Startdate.AddDays(day),
        Ammount = app.Ammount / days
    }
    group daily.Ammount by new
    {
        daily.Id,
        daily.Startdate.Year,
        daily.Startdate.Month
    } into gds
    select new AmountPerMonth()
    {
        Id = gds.Key.Id,
        Year = gds.Key.Year,
        Month = gds.Key.Month,
        Ammount = gds.Sum(),
    };

那给了我:

output

© www.soinside.com 2019 - 2024. All rights reserved.