Calculate relative time in C# 技术背景 在软件开发中,计算相对时间是一个常见需求,它可以让用户更直观地了解某个事件发生的时间间隔,例如显示“几分钟前”“昨天”“几个月前”等信息。在C#中,有多种方法可以实现相对时间的计算。
实现步骤 1. 基本时间间隔计算 可以通过DateTime
和TimeSpan
类来计算两个时间点之间的时间间隔。示例代码如下:
1 2 3 DateTime dt = DateTime.Now.AddHours(-2 ); TimeSpan ts = DateTime.Now - dt;double totalSeconds = ts.TotalSeconds;
2. 根据时间间隔生成相对时间字符串 根据计算得到的时间间隔,使用条件判断来生成相应的相对时间字符串。示例代码如下:
1 2 3 4 5 6 7 8 9 if (totalSeconds < 60 ) { return ts.Seconds == 1 ? "one second ago" : ts.Seconds + " seconds ago" ; }else if (totalSeconds < 120 ) { return "a minute ago" ; }
3. 封装为方法 为了方便复用,可以将上述逻辑封装为一个方法。示例代码如下:
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 public static string ToRelativeDate (DateTime input ) { TimeSpan oSpan = DateTime.Now.Subtract(input); double TotalMinutes = oSpan.TotalMinutes; string Suffix = " ago" ; if (TotalMinutes < 0.0 ) { TotalMinutes = Math.Abs(TotalMinutes); Suffix = " from now" ; } var aValue = new SortedList<double , Func<string >>(); aValue.Add(0.75 , () => "less than a minute" ); aValue.Add(1.5 , () => "about a minute" ); aValue.Add(45 , () => string .Format("{0} minutes" , Math.Round(TotalMinutes))); aValue.Add(90 , () => "about an hour" ); aValue.Add(1440 , () => string .Format("about {0} hours" , Math.Round(Math.Abs(oSpan.TotalHours)))); aValue.Add(2880 , () => "a day" ); aValue.Add(43200 , () => string .Format("{0} days" , Math.Floor(Math.Abs(oSpan.TotalDays)))); aValue.Add(86400 , () => "about a month" ); aValue.Add(525600 , () => string .Format("{0} months" , Math.Floor(Math.Abs(oSpan.TotalDays / 30 )))); aValue.Add(1051200 , () => "about a year" ); aValue.Add(double .MaxValue, () => string .Format("{0} years" , Math.Floor(Math.Abs(oSpan.TotalDays / 365 )))); return aValue.First(n => TotalMinutes < n.Key).Value.Invoke() + Suffix; }
核心代码 以下是几种不同实现方式的核心代码:
方式一:使用条件判断 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public static string ToRelativeDate (DateTime input ) { TimeSpan oSpan = DateTime.Now.Subtract(input); double TotalMinutes = oSpan.TotalMinutes; string Suffix = " ago" ; if (TotalMinutes < 0.0 ) { TotalMinutes = Math.Abs(TotalMinutes); Suffix = " from now" ; } if (TotalMinutes < 0.75 ) { return "less than a minute" + Suffix; } else if (TotalMinutes < 1.5 ) { return "about a minute" + Suffix; } return "" ; }
方式二:使用字典和委托 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public static string ToRelativeDate (DateTime input ) { TimeSpan oSpan = DateTime.Now.Subtract(input); double TotalMinutes = oSpan.TotalMinutes; string Suffix = " ago" ; if (TotalMinutes < 0.0 ) { TotalMinutes = Math.Abs(TotalMinutes); Suffix = " from now" ; } var dict = new Dictionary<double , Func<double , string >>(); dict.Add(0.75 , (mins) => "less than a minute" ); dict.Add(1.5 , (mins) => "about a minute" ); return dict.First(n => TotalMinutes < n.Key).Value.Invoke(TotalMinutes) + Suffix; }
方式三:使用switch表达式和关系模式 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 public static string ToNaturalLanguage (this TimeSpan @this ) { const int daysInWeek = 7 ; const int daysInMonth = 30 ; const int daysInYear = 365 ; const long threshold = 100 * TimeSpan.TicksPerMillisecond; @this = @this.TotalSeconds < 0 ? TimeSpan.FromSeconds(@this.TotalSeconds * -1 ) : @this; return (@this.Ticks + threshold) switch { < 2 * TimeSpan.TicksPerSecond => "a second" , < 1 * TimeSpan.TicksPerMinute => @this.Seconds + " seconds" , _ => (@this.Days / daysInYear).ToString("F0" ) + " years" }; }public static string ToNaturalLanguage (this DateTime @this ) { TimeSpan timeSpan = @this - DateTime.Now; return timeSpan.TotalSeconds switch { >= 1 => timeSpan.ToNaturalLanguage() + " until" , <= -1 => timeSpan.ToNaturalLanguage() + " ago" , _ => "now" }; }
最佳实践 1. 考虑本地化 如果应用程序需要支持多语言,应该将相对时间的文本信息进行本地化处理。可以创建一个Grammar
类来存储本地化的术语,示例代码如下:
1 2 3 4 5 6 public class Grammar { public string JustNow { get ; set ; } public string MinutesAgo { get ; set ; } }
2. 使用扩展方法 将相对时间计算封装为扩展方法,可以提高代码的可读性和复用性。示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static string ToRelativeDateString (this DateTime value , bool approximate ) { StringBuilder sb = new StringBuilder(); string suffix = (value > DateTime.Now) ? " from now" : " ago" ; TimeSpan timeSpan = new TimeSpan(Math.Abs(DateTime.Now.Subtract(value ).Ticks)); if (timeSpan.Days > 0 ) { sb.AppendFormat("{0} {1}" , timeSpan.Days, (timeSpan.Days > 1 ) ? "days" : "day" ); if (approximate) return sb.ToString() + suffix; } return sb.ToString() + suffix; }
3. 前端与后端结合 可以在前端使用JavaScript库(如jquery.timeago
插件)来实现相对时间的动态更新,减少后端的计算负担。示例代码如下:
1 2 3 jQuery (document ).ready (function ( ) { jQuery ('abbr.timeago' ).timeago (); });
常见问题 1. “1 hours ago”问题 在某些情况下,可能会出现“1 hours ago”这样的错误表述。可以通过调整判断条件来解决,例如将“an hour ago”的判断范围扩大:
1 2 3 4 if (delta < 7200 ) { return "an hour ago" ; }
2. 性能问题 如果需要处理大量的时间计算,可能会影响性能。可以考虑使用缓存机制来减少重复计算。
3. 跨时区问题 在处理相对时间时,需要考虑用户所在的时区。可以在前端根据用户的时区进行时间调整,或者在后端使用DateTimeOffset
类来处理带有时区信息的时间。