我基本上是想做与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...");
}


输出量

c# - 将每个日期范围的金额分成每月/每年的金额-LMLPHP

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

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/

10-11 02:01