C# 时间日期

C#提供了类型处理时间、日期、时区、夏时令和农历。

基本时间日期

DateTime表示时间点,并可以选项表示这是Utc时间还是本地时间或未指定。

DateTime的精度是100纳秒。

DateTime可以通过年月日时分秒来进行创建,不指定类型时,默认是未指定类型。

使用TimeSpan表示时间段。

使用DateTime.Now,DateTime.UtcNow可以获得当前机器的时间。

DateTime date = new DateTime(2011,1,1);
DateTime now = DateTime.UtcNow;

获取日期的部分

通过Year,Month,Day,Hour,Second,Minute,Millisecond可以获得对于的年、月、日、时、分、秒、毫秒。

通的Date,TimeOfDay可以获得时间、日期部分。

通过DayOfWeek可以获得星期。

日期的运算

日期支持比较大小,相等性,并支持和TimeSpan之间进行加减法运算。

DateTime之间也支持进行减法运算,结果是TimeSpan

DateTime提供了多个AddXX方法,支持按照不同的单位计算得到新的时间。

DateTime的问题

本地时间是一个动态的概念,对于不同的机器,结果也是不同的,未指定的类型的时间更让人难以处理。

不同类型时间直接的比较,结果是错误的。

DateTime d2 = new DateTime(2011,1,1,8,0,0,DateTimeKind.Local);
DateTime d1 = new DateTime(2011,1,1,0,0,0,DateTimeKind.Utf);
bool flag = d1==d2;//false

使用不同时区表示的同一时间,比较相等的结果是false

带时差信息的时间日期

DateTimeOffset表示一个时间点,保存了一个Utc时间以及时差。

在使用上,和DateTime是相似的。

通过DateTimeOffset比较不同时区的时间,结果是正确的。

时区

System.TimeZoneInfo表示一个时区。

GetSystemTimeZones静态方法。获得系统的所有时区。

Utc静态属性。获取utc时区。

Local静态属性。获取本机时区。

DisplayName属性。

BaseUtcOffset属性。获得时区和Utc的时差。

打印所有时区

public static void TimeZone()
{
    var zones = TimeZoneInfo.GetSystemTimeZones();
    foreach (var item in zones)
    {
        Console.WriteLine(item.DisplayName);
        Console.WriteLine(item.BaseUtcOffset);
    }
}

夏令时

夏令时(Daylight Saving Time,DST)是指在夏天一个时间点将时间调前,并在之后的调回来。

比如在4月12日2点将时间调前到3点,在9月13日再3点将时间调回2点。

夏令时的开始时间可能是根据日期,或者根据最后一个星期几。

夏令时会导致某个时间段不存在,比如4/12 2:00~2:59这个时间段的时间是不存在的。

也会导致一个时间过两次,比如9/12 2:00~3:00,这个时间段会经过两次,一次是在夏令时中,一次是正常时间。

TimeZoneInfo类和夏令时相关的成员

SupportsDaylightSavingTime属性。获得时区是否实行了夏令时。

DaylightName属性。获取时区在实现夏令时的名称。

IsDaylightSavingTime方法。可以判断当前日期是否在夏令时范围内。

IsInvalidTime方法。判断是否是非法时间,即时间在不存在的时间段内。

IsAmbiguousTime方法。判断是否是歧义的时间,即时间在经过两次的时间段内。如果用户输入了歧义时间,需要让用户进行选择当前是否在夏令时中。

获取详细的夏令时规则

TimeZoneInfoGetAdjustmentRules方法,可以获得夏令时的时间调整信息。

TimeZoneInfo.AdjustmentRule类表示一个夏令时调整规则。

DateStart,DateEnd属性。该类型包含规则的时间范围

DaylightTransitionStartDaylightTransitionEnd属性。夏令时调整开始及结束时间。

DaylightDelta属性。夏令时中和当前时区的偏移量,一般是1:00:00,提前一个小时。

TimeZoneInfo.TransitionTime类表示一个调整时间。

    public struct TransitionTime : IEquatable<TransitionTime>
    {
        //获取发生时间更改的小时、分钟和秒。
        public DateTime TimeOfDay { get; }

        //获取发生时间更改的月份。
        public Int32 Month { get; }

        //获取时间更改发生在月中的第几个星期。用于浮动日期。
        public Int32 Week { get; }

        // 获取发生时间更改的日期。用于固定日期。
        public Int32 Day { get; }

        //获取时间更改发生在星期几。用于浮动日期。
        public DayOfWeek DayOfWeek { get; }

        //获取一个值,该值指示是在固定日期和时间(如 11 月 1 日)还是在浮动日期和时间(如 10 月的最后一个星期日)。
        public Boolean IsFixedDateRule { get; }
    }

打印太平洋时间的夏令时信息


        public static string FormatTime(TimeZoneInfo.TransitionTime time)
        {
            if (time.IsFixedDateRule)
            {
                return $"{time.Month}/{time.Day} {time.TimeOfDay:HH:mm}";
            }
            else
            {
                return $"{time.Month}/({time.Week} {time.DayOfWeek}){time.TimeOfDay:HH:mm}";
            }
        }

        public static void TimeZoneAdjust()
        {
            var zones = TimeZoneInfo.GetSystemTimeZones();
            foreach (var item in zones)
            {
                if (item.DisplayName.Contains("太平洋"))
                {
                    Console.WriteLine(item.DisplayName);
                    Console.WriteLine(item.BaseUtcOffset);
                    var rules = item.GetAdjustmentRules();
                    foreach (var rule in rules)
                    {
                        Console.WriteLine(rule.DaylightDelta);
                        Console.WriteLine($"{rule.DateStart:yyyy/mm/dd}~{rule.DateEnd:yyyy/mm/dd}");
                        var start = rule.DaylightTransitionStart;
                        var end = rule.DaylightTransitionEnd;
                        Console.WriteLine($"{FormatTime(start)}~{FormatTime(end)}");
                    }
                }
            }
        }

时区转换

TimeZoneInfo类包含多个静态方法,用来进行时区之间的转换。

ConvertTime将本地或特定时区时间转换成另一个时区的时间。

ConvertTimeToUtc将本地时间或特定时区时间转换成Utc时间。

ConvertTimeFromUtc将Utc时间转换成本地时间或特定时区。

对于没有夏令时的时区,转换是加上和减去时差;对于有夏令时的时区,还需要计算夏时令。

夏时令可能导致时间不存在,或者时间歧义。对于不存在时间,会抛出异常,对于歧义时间,会作为夏时令结束后的时间处理。

时间日期使用建议

无论使用DateTime,还是DateTimeOffset保存时间,始终保存为Utc时间,并将读取的时间作为Utc处理。

仅仅在展示时,将时间显示为本地时间。

农历日期

System.Globalization命名空间包含各种日历。

ChineseLunisolarCalendar类型表示中国的农历。

获取农历日期

    public static void CnDate()
    {
        var now = DateTime.Now;

        var calendar = new ChineseLunisolarCalendar();

        var y = calendar.GetYear(now);
        var m = calendar.GetMonth(now);
        var d = calendar.GetDayOfMonth(now);
        var leapMonth = calendar.GetLeapMonth(y);

        //判断当前农历月份是否是闰月
        bool isLeapMonth = m == leapMonth;

        //闰月后续的月份需要-1
        if (isLeapMonth && m >= leapMonth)
        {
            m--;
        }
    }

农历闰年的月份m对应1~13,其中包括闰月,leapMonth是对应的闰月。 要获得可读月份,闰月以及之后的月份需要-1.

农历干支纪年和生肖

    public static void CnDateGanzhi()
    {
        var now = DateTime.Now;

        var calendar = new ChineseLunisolarCalendar();

        //干支纪年的索引1~60
        var index = calendar.GetSexagenaryYear(now);
        //天干索引1~10
        var tiangan = calendar.GetCelestialStem(index);
        //地支索引1~12
        var dizhi = calendar.GetTerrestrialBranch(index);

        var tianganlist = "甲乙丙丁戊己庚辛壬癸";
        var dizhilist = "子丑寅卯辰巳午未申酉戌亥";
        var shenxiaolist = "鼠牛虎兔龙蛇马羊猴鸡狗猪";

        Console.WriteLine($"{tianganlist[tiangan-1]}{dizhilist[dizhi-1]}");
        //生肖和地支对应
        Console.WriteLine($"{shenxiaolist[dizhi-1]}年");
    }

农历日期反算公历日期

public static void CnDateBack()
{
    var calendar = new ChineseLunisolarCalendar();
    DateTime date = calendar.ToDateTime(2018, 1, 1, 0, 0, 0, 0);
    Console.WriteLine(date);//2018/2/16
}

在传递月份的时候,如果是闰年,闰月以及后边的月份要+1。比如闰六月传7,当年是闰六月,八月要传9。