给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时间类型已经满足小编的开发需要,当然也可以根据需求继续扩展其它功能。

posted @ 2023-07-25 11:37  天行健君子以自强  阅读(2625)  评论(21编辑  收藏  举报