给定特定的DateTime值,如何显示相对时间,例如:
2小时前3天前一个月前
给定特定的DateTime值,如何显示相对时间,例如:
2小时前3天前一个月前
当前回答
文森特接受的答案做出了许多武断的决定。为什么45分钟舍入为一小时,而45秒不舍入为一分钟?在年和月的计算中,它的圈复杂度增加了,这使得遵循逻辑变得更加复杂。它假设TimeSpan是相对于过去(2天前)的,而它很可能是在未来(2天后)。它定义了不必要的常量,而不是使用TimeSpan.TicksPerSecond等。
此实现解决了上述问题,并更新了语法以使用开关表达式和关系模式
/// <summary>
/// Convert a <see cref="TimeSpan"/> to a natural language representation.
/// </summary>
/// <example>
/// <code>
/// TimeSpan.FromSeconds(10).ToNaturalLanguage();
/// // 10 seconds
/// </code>
/// </example>
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",
< 2 * TimeSpan.TicksPerMinute => "a minute",
< 1 * TimeSpan.TicksPerHour => @this.Minutes + " minutes",
< 2 * TimeSpan.TicksPerHour => "an hour",
< 1 * TimeSpan.TicksPerDay => @this.Hours + " hours",
< 2 * TimeSpan.TicksPerDay => "a day",
< 1 * daysInWeek * TimeSpan.TicksPerDay => @this.Days + " days",
< 2 * daysInWeek * TimeSpan.TicksPerDay => "a week",
< 1 * daysInMonth * TimeSpan.TicksPerDay => (@this.Days / daysInWeek).ToString("F0") + " weeks",
< 2 * daysInMonth * TimeSpan.TicksPerDay => "a month",
< 1 * daysInYear * TimeSpan.TicksPerDay => (@this.Days / daysInMonth).ToString("F0") + " months",
< 2 * daysInYear * TimeSpan.TicksPerDay => "a year",
_ => (@this.Days / daysInYear).ToString("F0") + " years"
};
}
/// <summary>
/// Convert a <see cref="DateTime"/> to a natural language representation.
/// </summary>
/// <example>
/// <code>
/// (DateTime.Now - TimeSpan.FromSeconds(10)).ToNaturalLanguage()
/// // 10 seconds ago
/// </code>
/// </example>
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",
};
}
可以使用NUnit对其进行如下测试:
[TestCase("a second", 0)]
[TestCase("a second", 1)]
[TestCase("2 seconds", 2)]
[TestCase("a minute", 0, 1)]
[TestCase("5 minutes", 0, 5)]
[TestCase("an hour", 0, 0, 1)]
[TestCase("2 hours", 0, 0, 2)]
[TestCase("a day", 0, 0, 24)]
[TestCase("a day", 0, 0, 0, 1)]
[TestCase("6 days", 0, 0, 0, 6)]
[TestCase("a week", 0, 0, 0, 7)]
[TestCase("4 weeks", 0, 0, 0, 29)]
[TestCase("a month", 0, 0, 0, 30)]
[TestCase("6 months", 0, 0, 0, 6 * 30)]
[TestCase("a year", 0, 0, 0, 365)]
[TestCase("68 years", int.MaxValue)]
public void NaturalLanguageHelpers_TimeSpan(
string expected,
int seconds,
int minutes = 0,
int hours = 0,
int days = 0
)
{
// Arrange
TimeSpan timeSpan = new(days, hours, minutes, seconds);
// Act
string result = timeSpan.ToNaturalLanguage();
// Assert
Assert.That(result, Is.EqualTo(expected));
}
[TestCase("now", 0)]
[TestCase("10 minutes ago", 0, -10)]
[TestCase("10 minutes until", 10, 10)]
[TestCase("68 years until", int.MaxValue)]
[TestCase("68 years ago", int.MinValue)]
public void NaturalLanguageHelpers_DateTime(
string expected,
int seconds,
int minutes = 0,
int hours = 0,
int days = 0
)
{
// Arrange
TimeSpan timeSpan = new(days, hours, minutes, seconds);
DateTime now = DateTime.Now;
DateTime dateTime = now + timeSpan;
// Act
string result = dateTime.ToNaturalLanguage();
// Assert
Assert.That(result, Is.EqualTo(expected));
}
或者作为要点:https://gist.github.com/StudioLE/2dd394e3f792e79adc927ede274df56e
其他回答
我是这样做的
var ts = new TimeSpan(DateTime.UtcNow.Ticks - dt.Ticks);
double delta = Math.Abs(ts.TotalSeconds);
if (delta < 60)
{
return ts.Seconds == 1 ? "one second ago" : ts.Seconds + " seconds ago";
}
if (delta < 60 * 2)
{
return "a minute ago";
}
if (delta < 45 * 60)
{
return ts.Minutes + " minutes ago";
}
if (delta < 90 * 60)
{
return "an hour ago";
}
if (delta < 24 * 60 * 60)
{
return ts.Hours + " hours ago";
}
if (delta < 48 * 60 * 60)
{
return "yesterday";
}
if (delta < 30 * 24 * 60 * 60)
{
return ts.Days + " days ago";
}
if (delta < 12 * 30 * 24 * 60 * 60)
{
int months = Convert.ToInt32(Math.Floor((double)ts.Days / 30));
return months <= 1 ? "one month ago" : months + " months ago";
}
int years = Convert.ToInt32(Math.Floor((double)ts.Days / 365));
return years <= 1 ? "one year ago" : years + " years ago";
建议?评论?如何改进此算法?
public string getRelativeDateTime(DateTime date)
{
TimeSpan ts = DateTime.Now - date;
if (ts.TotalMinutes < 1)//seconds ago
return "just now";
if (ts.TotalHours < 1)//min ago
return (int)ts.TotalMinutes == 1 ? "1 Minute ago" : (int)ts.TotalMinutes + " Minutes ago";
if (ts.TotalDays < 1)//hours ago
return (int)ts.TotalHours == 1 ? "1 Hour ago" : (int)ts.TotalHours + " Hours ago";
if (ts.TotalDays < 7)//days ago
return (int)ts.TotalDays == 1 ? "1 Day ago" : (int)ts.TotalDays + " Days ago";
if (ts.TotalDays < 30.4368)//weeks ago
return (int)(ts.TotalDays / 7) == 1 ? "1 Week ago" : (int)(ts.TotalDays / 7) + " Weeks ago";
if (ts.TotalDays < 365.242)//months ago
return (int)(ts.TotalDays / 30.4368) == 1 ? "1 Month ago" : (int)(ts.TotalDays / 30.4368) + " Months ago";
//years ago
return (int)(ts.TotalDays / 365.242) == 1 ? "1 Year ago" : (int)(ts.TotalDays / 365.242) + " Years ago";
}
一个月和一年中的天数的转换值取自谷歌。
如果您想获得类似“2天4小时12分钟前”的输出,则需要一个时间跨度:
TimeSpan timeDiff = DateTime.Now-CreatedDate;
然后您可以访问您喜欢的值:
timeDiff.Days
timeDiff.Hours
等
@杰夫
我知道你的有点长。然而,随着对“昨天”和“几年”的支持,它似乎确实更为有力。但根据我的经验,当使用此选项时,用户最有可能在前30天内查看内容。只有真正的铁杆人才会在这之后出现。所以,我通常选择保持简短。
这是我目前在我的一个网站上使用的方法。这只返回相对的日期、小时和时间。然后用户必须在输出中加上“ago”。
public static string ToLongString(this TimeSpan time)
{
string output = String.Empty;
if (time.Days > 0)
output += time.Days + " days ";
if ((time.Days == 0 || time.Days == 1) && time.Hours > 0)
output += time.Hours + " hr ";
if (time.Days == 0 && time.Minutes > 0)
output += time.Minutes + " min ";
if (output.Length == 0)
output += time.Seconds + " sec";
return output.Trim();
}
我的方法要简单得多。您可以根据需要调整返回字符串
public static string TimeLeft(DateTime utcDate)
{
TimeSpan timeLeft = DateTime.UtcNow - utcDate;
string timeLeftString = "";
if (timeLeft.Days > 0)
{
timeLeftString += timeLeft.Days == 1 ? timeLeft.Days + " day" : timeLeft.Days + " days";
}
else if (timeLeft.Hours > 0)
{
timeLeftString += timeLeft.Hours == 1 ? timeLeft.Hours + " hour" : timeLeft.Hours + " hours";
}
else
{
timeLeftString += timeLeft.Minutes == 1 ? timeLeft.Minutes+" minute" : timeLeft.Minutes + " minutes";
}
return timeLeftString;
}