簡體   English   中英

將每個日期范圍的金額拆分為每月/每年的金額

[英]Split amount per date-range into amount per month/year

我基本上是在嘗試做與這里相同的事情,只是我想在 C# 中而不是在 SQL 中執行此操作。

我有一個 class:

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 class 的列表,如下所示:

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。 有人有什么建議嗎?

提前致謝

LINQ 更容易。

var output =
    from app in lstAmountPerPeriod
    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),
        Amount = app.Amount / days
    }
    group daily.Amount 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,
        Amount = gds.Sum(),
    };

此查詢返回:

輸出

這是另一種方法,這里的基本區別是我使用for循環將“第一天”不斷更新為該期間的第一天或當月的第一天,以及“最后一天” " 到當月的最后一天或期間的最后一天,以較小者為准。

我還將它添加為 AmountPerMonth class 上的AmountPerMonth方法,該方法接受AmountPerPeriod並返回List<AmountPerMonth> 此外,我將ToString方法覆蓋為 output 一個類似於您問題中的字符串,因此 output 看起來相同:

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的參數從您的示例數據中生成我們的列表和 output 結果:

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

Output

在此處輸入圖像描述

注意:以上代碼中使用了這些擴展方法:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM