关于datetime:C#将天数添加到给定日期

C# Add days to a given date

我第一眼就要做一些简单的锻炼,但结果却很难做到。我需要一个日期作为输入,还需要给它添加一些天,这些天也是从用户输入中获取的。我已经做了一些函数和一些简单的计算,现在我有了从日期(01,010001是零)开始的所有天数,例如:

第一年1月第二年(01.01.0002)+0天等于365天。如果我在其中添加一些天,它也会正确计算:01.01.0002+12天=387。它也计算闰年。现在我有了totaldays,我只需要将其转换为正常的日/月/年格式。

我不允许使用日期时间

这是我的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
private static int[] daysPerMonth = new int[12];
    private static int days;
    private static int months;
    private static int years;
    private static int add;

    private static void Main()
    {
        Console.Write("Enter day :");
        int.TryParse(Console.ReadLine(), out days);
        Console.Write("Enter Month :");
        int.TryParse(Console.ReadLine(), out months);
        Console.Write("Enter Year :");
        int.TryParse(Console.ReadLine(), out years);
        Console.Write("Enter days to add :");
        int.TryParse(Console.ReadLine(), out add);
        int totalDays = GetTotalDays(new[] {days, months, years});
        totalDays += add;
        TransformIntoDate(totalDays);

        Console.ReadKey();
    }

    private static void TransformIntoDate(int inputDays)
    {

    }
    private static int GetTotalDays(IReadOnlyList<int> date)
    {
        int totalDays = 0;
        for (int i = date[2]; i > 1; i--)
        {
            if (IsLeap(i))
            {
                daysPerMonth = new[] {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
                totalDays += 366;
            }
            else
            {
                daysPerMonth = new[] {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
                totalDays += 365;
            }
        }
        for (int i = 1; i <= date[1]; i++)
        {
            if (i == date[1])
            {
                totalDays += date[0] - 1;
            }
            else
            {
                totalDays += daysPerMonth[i];
            }
        }
        return totalDays;
    }

    private static bool IsLeap(int year)
    {
        if (year%400 == 0) return true;
        return (year%4 == 0) && (year%100 != 0);
    }


有两种方法可以"保存"日期:分别保存年、月、日或保存总天数(或小时、分钟、秒或毫秒…)。从"0点"中选择测量单位)。例如.NET的DateTime使用100纳秒作为Tick和1月1日作为"0"点。Unix使用从1970年1月1日开始的秒数。显然.NET和Unix的方法在内存中更紧凑(只需保存一个值),如果您想加/减一个数量(只需加/减它),它非常有用。问题是,将此内部编号转换为年/月/日或将年/月/日转换为此编号更为复杂。

关于如何完成从年/月/日到内部编号的简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
public class MyDate
{
    public int TotalDaysFrom00010101 { get; private set; }

    private const int DaysIn400YearCycle = 365 * 400 + 97;
    private const int DaysIn100YearCycleNotDivisibleBy400 = 365 * 100 + 24;
    private const int DaysIn4YearCycle = 365 * 4 + 1;

    private static readonly int[] DaysPerMonthNonLeap = new[]
    {
        31,
        31 + 28,
        31 + 28 + 31,
        31 + 28 + 31 + 30,
        31 + 28 + 31 + 30 + 31,
        31 + 28 + 31 + 30 + 31 + 30,
        31 + 28 + 31 + 30 + 31 + 30 + 31,
        31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
        31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
        31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
        31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
        31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31 // Useless
    };

    private static readonly int[] DaysPerMonthLeap = new[]
    {
        31,
        31 + 29,
        31 + 29 + 31,
        31 + 29 + 31 + 30,
        31 + 29 + 31 + 30 + 31,
        31 + 29 + 31 + 30 + 31 + 30,
        31 + 29 + 31 + 30 + 31 + 30 + 31,
        31 + 29 + 31 + 30 + 31 + 30 + 31 + 31,
        31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
        31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
        31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
        31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31 // Useless
    };

    public static bool IsLeap(int year)
    {
        if (year % 400 == 0) return true;
        return (year % 4 == 0) && (year % 100 != 0);
    }

    public void SetDate(int year, int month, int day)
    {
        TotalDaysFrom00010101 = 0;

        {
            int year2 = year - 1;

            // Full 400 year cycles
            TotalDaysFrom00010101 += (year2 / 400) * DaysIn400YearCycle;
            year2 %= 400;

            // Remaining 100 year cycles (0...3)
            if (year2 >= 100)
            {
                year2 -= 100;
                TotalDaysFrom00010101 += DaysIn100YearCycleNotDivisibleBy400;

                if (year2 >= 100)
                {
                    year2 -= 100;
                    TotalDaysFrom00010101 += DaysIn100YearCycleNotDivisibleBy400;

                    if (year2 >= 100)
                    {
                        year2 -= 100;
                        TotalDaysFrom00010101 += DaysIn100YearCycleNotDivisibleBy400;
                    }
                }
            }

            // Full 4 year cycles
            TotalDaysFrom00010101 += (year2 / 4) * DaysIn4YearCycle;
            year2 %= 4;

            // Remaining non-leap years
            TotalDaysFrom00010101 += year2 * 365;
        }

        // Days from the previous month
        if (month > 1)
        {
            // -2 is because -1 is for the 1 January == 0 index, plus -1
            // because we must add only the previous full months here.
            // So if the date is 1 March 2016, we must add the days of
            // January + February, so month 3 becomes index 1.
            TotalDaysFrom00010101 += DaysPerMonthNonLeap[month - 2];

            if (month > 2 && IsLeap(year))
            {
                TotalDaysFrom00010101 += 1;
            }
        }

        // Days (note that the"instant 0" in this class is day 1, so
        // we must add day - 1)
        TotalDaysFrom00010101 += day - 1;
    }

    public void GetDate(out int year, out int month, out int day)
    {
        int days = TotalDaysFrom00010101;

        // year
        {
            year = days / DaysIn400YearCycle * 400;
            days %= DaysIn400YearCycle;

            if (days >= DaysIn100YearCycleNotDivisibleBy400)
            {
                year += 100;
                days -= DaysIn100YearCycleNotDivisibleBy400;

                if (days >= DaysIn100YearCycleNotDivisibleBy400)
                {
                    year += 100;
                    days -= DaysIn100YearCycleNotDivisibleBy400;

                    if (days >= DaysIn100YearCycleNotDivisibleBy400)
                    {
                        year += 100;
                        days -= DaysIn100YearCycleNotDivisibleBy400;
                    }
                }
            }

            year += days / DaysIn4YearCycle * 4;
            days %= DaysIn4YearCycle;

            // Special case: 31 dec of a leap year
            if (days != 1460)
            {
                year += days / 365;
                days %= 365;
            }
            else
            {
                year += 3;
                days = 365;
            }

            year++;
        }

        // month
        {
            bool isLeap = IsLeap(year);

            int[] daysPerMonth = isLeap ? DaysPerMonthLeap : DaysPerMonthNonLeap;

            for (month = 0; month < daysPerMonth.Length; month++)
            {
                if (daysPerMonth[month] > days)
                {
                    if (month > 0)
                    {
                        days -= daysPerMonth[month - 1];
                    }

                    break;
                }
            }

            month++;
        }

        // day
        {
            day = days;
            day++;
        }
    }

    public void AddDays(int days)
    {
        TotalDaysFrom00010101 += days;
    }
}

这里的重点是,我们知道有400年的"周期",每一个"周期"都有1(2)天。减去这些"周期"后,有更小的100年的"周期",每个周期有365 * 100 + 24天。然后我们有4年的"周期",每一个周期有1(4)天,加上剩下的年份(0…3),每一个周期有365天。

在添加了所有"完整前几年"的天数之后,我们可以添加"完整前几个月"的天数。在这里,我们必须考虑今年可能是闰年。

最后,我们添加选定的日期。

如何编写GetDate()作为练习。

现在。。。如何检查结果是否正确?我们可以编写一些基于DateTime实现的单元测试…比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var date = default(DateTime);
var endDate = new DateTime(2017, 1, 1);

while (date < endDate)
{
    int year = date.Year;
    int month = date.Month;
    int day = date.Day;

    int totalDays = (int)(date - default(DateTime)).TotalDays;

    var md = new MyDate();
    md.SetDate(year, month, day);

    if (totalDays != md.TotalDaysFrom00010101)
    {
        Console.WriteLine("{0:d}: {1} vs {2}", date, totalDays, md.TotalDaysFrom00010101);
    }

    int year2, month2, day2;
    md.GetDate(out year2, out month2, out day2);

    if (year != year2 || month != month2 || day != day2)
    {
        Console.WriteLine("{0:d}: {1:D4}-{2:D2}-{3:D2} vs {4:D4}-{5:D2}-{6:D2}", date, year, month, day, year2, month2, day2);
    }

    date = date.AddDays(1);
}

(我知道这不是单元测试…但演示了如何使用DateTime进行比较)

请注意,这是一个关于如何实现它的示例。我不会用这种方式实现它。我要做一个不可变的structDateTime,用一个构造函数而不是SetDate()。但你的似乎是一个练习,我确实认为一个人应该小步前进:首先正确地构建一些东西,然后使它"正式地正确"。因此,第一步是构建一个正确的GetDate()/SetDate(),然后您可以将其正确封装到一个更正式的正确数据结构中。这个小样本甚至缺少一些参数验证:您可以使用SetDate(-1, 13, 32):-)

GetDate()的建造相当复杂。比我想象的要多得多。最后,我终于明白如何写了它。请注意,最终它与微软的实现非常相似(参见GetDatePart(int part))。