给C#新增一个时间类型: YearMonth
在.Net Framework中,我们常用的时间类型是DateTime。直到.Net6微软加入了两个新的时间类型:DateOnly和TimeOnly,才弥补了之前的不足。
DateOnly:表示仅日期。比如:某人的生日,我只关心日期,就适合用DateOnly。
TimeOnly:表示仅时间。比如:每天定时执行某个任务,我只关心时间,就适合用TimeOnly。
由此可见,DateOnly和TimeOnly都有相应的应用场景。可笔者在实际项目中遇到了这样的业务场景:需要每月给客户生成月账单。这里我所关心的是某个月份,于是我首先想到用DateOnly表示(不考虑字符串)。
var date = new DateOnly(2023, 2, 1); // 代表2023年2月1日
虽然DateOnly可用,但从字面理解和表现形式上还是略显尴尬。 DateOnly真正表达的是某一天并不是某个月, 在代码层面也容易混淆,所以并不符合博主的心理期望。经过一番纠结和思考,博主决定自己动手创建一个表示年/月的时间类型:YearMonth。
var ym = new YearMonth(2023, 2); // 代表2023年2月
YearMonth的源码如下:
1 /// <summary> 2 /// 表示年/月的时间类型 3 /// </summary> 4 [JsonConverter(typeof(YearMonthJsonConverter))] 5 public readonly struct YearMonth 6 { 7 public int Year { get; } 8 9 public int Month { get; } 10 11 public YearMonth(int year, int month) 12 { 13 Year = year; 14 Month = month; 15 } 16 17 public YearMonth AddMonths(int value) 18 { 19 var date = new DateOnly(Year, Month, 1); 20 return FromDateOnly(date.AddMonths(value)); 21 } 22 24 public YearMonth AddYears(int value) 25 { 26 var date = new DateOnly(Year, Month, 1); 27 return FromDateOnly(date.AddYears(value)); 28 } 29 30 public DateOnly FirstDay() 31 { 32 return new DateOnly(Year, Month, 1); 33 } 34 35 public DateOnly LastDay() 36 { 37 var nextMonth = AddMonths(1); 38 var date = new DateOnly(nextMonth.Year, nextMonth.Month, 1); 39 return date.AddDays(-1); 40 } 41 42 public int DaysInMonth() 43 { 44 return DateTime.DaysInMonth(Year, Month); 45 } 46 47 public static YearMonth Current 48 { 49 get { return FromDateTime(DateTime.Now); } 50 } 51 52 public static YearMonth UtcCurrent 53 { 54 get { return FromDateTime(DateTime.UtcNow); } 55 } 56 57 public static YearMonth FromDateOnly(DateOnly dateOnly) 58 { 59 return new YearMonth(dateOnly.Year, dateOnly.Month); 60 } 61 62 public static YearMonth FromDateTime(DateTime dateTime) 63 { 64 return new YearMonth(dateTime.Year, dateTime.Month); 65 } 66 67 public static YearMonth FromString(string s) 68 { 69 if (DateTime.TryParse(s, out var date)) 70 { 71 return FromDateTime(date); 72 } 73 throw new ArgumentException("format is error", nameof(s)); 74 } 75 76 public override string ToString() 77 { 78 return $"{Year.ToString().PadLeft(4, '0')}-{Month.ToString().PadLeft(2, '0')}"; 79 } 80 81 public static bool operator ==(YearMonth left, YearMonth right) 82 { 83 return left.Year == right.Year && left.Month == right.Month; 84 } 85 86 public static bool operator !=(YearMonth left, YearMonth right) 87 { 88 return !(left.Year == right.Year && left.Month == right.Month); 89 } 90 91 public static bool operator >=(YearMonth left, YearMonth right) 92 { 93 return (left.Year > right.Year) || (left.Year == right.Year && left.Month >= right.Month); 94 } 95 96 public static bool operator <=(YearMonth left, YearMonth right) 97 { 98 return (left.Year < right.Year) || (left.Year == right.Year && left.Month <= right.Month); 99 } 100 101 public static bool operator >(YearMonth left, YearMonth right) 102 { 103 return (left.Year > right.Year) || (left.Year == right.Year && left.Month > right.Month); 104 } 105 106 public static bool operator <(YearMonth left, YearMonth right) 107 { 108 return (left.Year < right.Year) || (left.Year == right.Year && left.Month < right.Month); 109 } 110 111 public override bool Equals(object obj) 112 { 113 return base.Equals(obj); 114 } 115 116 public override int GetHashCode() 117 { 118 return base.GetHashCode(); 119 } 120 }
其中特性 [JsonConverter(typeof(YearMonthJsonConverter))]用于Json序列化和反序列化。
1 public class YearMonthJsonConverter : JsonConverter<YearMonth> 2 { 3 public override YearMonth Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 4 { 5 return YearMonth.FromString(reader.GetString()); 6 } 7 8 public override void Write(Utf8JsonWriter writer, YearMonth value, JsonSerializerOptions options) 9 { 10 writer.WriteStringValue(value.ToString()); 11 } 12 }
YearMonth的一些用法示例:
1 var ym = new YearMonth(2023, 2); 2 int n = ym.DaysInMonth(); //n:28 3 DateOnly d1 = ym.FirstDay(); //d1:2023/2/1 4 DateOnly d2 = ym.LastDay(); //d2:2023/2/28 5 string str = ym.ToString(); //str:2023-02 6 YearMonth ym2 = ym.AddMonths(1); //ym2: 2023-03 7 YearMonth ym3 = YearMonth.FromDateOnly(new DateOnly(2023, 2, 8)); //ym3: 2023-02 8 YearMonth ym4 = YearMonth.FromDateTime(new DateTime(2023, 2, 8, 12, 23, 45)); //ym4: 2023-02 9 bool b = new YearMonth(2023, 3) > new YearMonth(2023, 2); //b: true
至此,上面的YearMonth时间类型已经满足小编的开发需要,当然也可以根据需求继续扩展其它功能。
作者:天行健君子以自强
如果此文对你有帮助的话,请点一下右下角的【推荐】,欢迎评论区留言。本文已同步至作者微信公众号:玩转DotNet,感谢关注!