Calculate relative time in C#

Calculate relative time in C#

技术背景

在软件开发中,计算相对时间是一个常见需求,它可以让用户更直观地了解某个事件发生的时间间隔,例如显示“几分钟前”“昨天”“几个月前”等信息。在C#中,有多种方法可以实现相对时间的计算。

实现步骤

1. 基本时间间隔计算

可以通过DateTimeTimeSpan类来计算两个时间点之间的时间间隔。示例代码如下:

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",
// 其他case...
_ => (@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) // 120 * 60
{
return "an hour ago";
}

2. 性能问题

如果需要处理大量的时间计算,可能会影响性能。可以考虑使用缓存机制来减少重复计算。

3. 跨时区问题

在处理相对时间时,需要考虑用户所在的时区。可以在前端根据用户的时区进行时间调整,或者在后端使用DateTimeOffset类来处理带有时区信息的时间。


Calculate relative time in C#
https://119291.xyz/posts/calculate-relative-time-in-csharp/
作者
ww
发布于
2025年5月29日
许可协议