在 C# 中计算相对时间
Posted
技术标签:
【中文标题】在 C# 中计算相对时间【英文标题】:Calculate relative time in C# 【发布时间】:2010-09-05 05:27:44 【问题描述】:给定一个特定的DateTime
值,我如何显示相对时间,例如:
【问题讨论】:
如果你想计算从现在到未来的相对时间怎么办? moment.js 是一个非常好的日期解析库。您可以根据需要考虑使用它(服务器端或客户端)。仅供参考,因为这里没有人提到它 这个项目的日期格式非常漂亮github.com/Humanizr/Humanizer#humanize-datetime 【参考方案1】:Jeff,your code 很好,但使用常量可能更清晰(如代码完成中所建议的那样)。
const int SECOND = 1;
const int MINUTE = 60 * SECOND;
const int HOUR = 60 * MINUTE;
const int DAY = 24 * HOUR;
const int MONTH = 30 * DAY;
var ts = new TimeSpan(DateTime.UtcNow.Ticks - yourDate.Ticks);
double delta = Math.Abs(ts.TotalSeconds);
if (delta < 1 * MINUTE)
return ts.Seconds == 1 ? "one second ago" : ts.Seconds + " seconds ago";
if (delta < 2 * MINUTE)
return "a minute ago";
if (delta < 45 * MINUTE)
return ts.Minutes + " minutes ago";
if (delta < 90 * MINUTE)
return "an hour ago";
if (delta < 24 * HOUR)
return ts.Hours + " hours ago";
if (delta < 48 * HOUR)
return "yesterday";
if (delta < 30 * DAY)
return ts.Days + " days ago";
if (delta < 12 * MONTH)
int months = Convert.ToInt32(Math.Floor((double)ts.Days / 30));
return months <= 1 ? "one month ago" : months + " months ago";
else
int years = Convert.ToInt32(Math.Floor((double)ts.Days / 365));
return years <= 1 ? "one year ago" : years + " years ago";
【讨论】:
我非常讨厌这样的常数。这对任何人来说都是错的吗?Thread.Sleep(1 * MINUTE)
?因为它错了 1000 倍。
const int SECOND = 1;
一秒就是一秒。
这种类型的代码几乎不可能本地化。如果您的应用程序只需要保持英文,那很好。但是如果你跳到其他语言,你会讨厌自己做这样的逻辑。只是让你们都知道......
我认为如果将常量重命名以准确描述其中的值,它会更容易理解。所以 SecondsPerMinute = 60;分钟/小时 = 60; SecondsPerHour = MinutesPerHour * SecondsPerHour;等等。仅仅调用它 MINUTE=60 不允许读者确定值是什么。
为什么没有人(除了乔)关心错误的“昨天”或“几天前”值???昨天不是一个小时的计算,而是一天一天的计算。所以是的,至少在两种常见情况下这是一个错误的代码。【参考方案2】:
jquery.timeago plugin
Jeff,因为 Stack Overflow 广泛使用 jQuery,我推荐 jquery.timeago plugin。
好处:
避免使用日期为“1 分钟前”的时间戳,即使该页面是 10 分钟前打开的; timeago 会自动刷新。 您可以充分利用 Web 应用程序中的页面和/或片段缓存,因为时间戳不是在服务器上计算的。 您可以像酷孩子一样使用微格式。只需将其附加到 DOM 上的时间戳即可:
jQuery(document).ready(function()
jQuery('abbr.timeago').timeago();
);
这将把所有 abbr
元素与 timeago 类和标题中的 ISO 8601 时间戳:
<abbr class="timeago" title="2008-07-17T09:24:17Z">July 17, 2008</abbr>
变成这样:
<abbr class="timeago" title="July 17, 2008">4 months ago</abbr>
产生:4 个月前。随着时间的推移,时间戳会自动更新。
免责声明:我写了这个插件,所以我有偏见。
【讨论】:
Seb,如果您禁用了 javascript,则会显示您最初放在 abbr 标记之间的字符串。通常,这只是您希望的任何格式的日期或时间。 Timeago优雅地降级。它并没有变得更简单。 Ryan,我刚才建议 SO 使用 timeago。 Jeff 的回复让我哭了,建议你坐下:***.uservoice.com/pages/1722-general/suggestions/… 嘿,谢谢罗。没关系。它几乎不引人注意,尤其是在转换过程中只有一个数字发生变化时,尽管 SO 页面有很多时间戳。我原以为他至少会欣赏页面缓存的好处,即使他选择避免自动更新。我相信 Jeff 也可以提供反馈来改进插件。知道arstechnica.com 这样的网站使用它,我感到很安慰。 @Rob Fonseca-Ensor - 现在它也让我哭了。如何每分钟更新一次以显示准确的信息,以任何方式与文本每秒闪烁一次有关? 问题是关于 C#,我看不出 jQuery 插件是如何相关的。【参考方案3】:我是这样做的
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";
建议?注释?有什么方法可以改进这个算法?
【讨论】:
" 编译器通常很擅长预先计算常量表达式,比如 24 * 60 * 60,所以你可以直接使用它们,而不用自己计算为 86400 并将原始表达式放在 cmets 中跨度> @bzlm 我想我是为我正在做的一个项目做的。我在这里的动机是提醒其他人此代码示例中省略了几周。至于如何做到这一点,对我来说似乎很简单。 我认为改进算法的好方法是显示 2 个单位,如“2 个月 21 天前”、“1 小时 40 分钟前”以提高准确性。 @Jeffy,你错过了闰年和相关检查的计算【参考方案4】:public static string RelativeDate(DateTime theDate)
Dictionary<long, string> thresholds = new Dictionary<long, string>();
int minute = 60;
int hour = 60 * minute;
int day = 24 * hour;
thresholds.Add(60, "0 seconds ago");
thresholds.Add(minute * 2, "a minute ago");
thresholds.Add(45 * minute, "0 minutes ago");
thresholds.Add(120 * minute, "an hour ago");
thresholds.Add(day, "0 hours ago");
thresholds.Add(day * 2, "yesterday");
thresholds.Add(day * 30, "0 days ago");
thresholds.Add(day * 365, "0 months ago");
thresholds.Add(long.MaxValue, "0 years ago");
long since = (DateTime.Now.Ticks - theDate.Ticks) / 10000000;
foreach (long threshold in thresholds.Keys)
if (since < threshold)
TimeSpan t = new TimeSpan((DateTime.Now.Ticks - theDate.Ticks));
return string.Format(thresholds[threshold], (t.Days > 365 ? t.Days / 365 : (t.Days > 0 ? t.Days : (t.Hours > 0 ? t.Hours : (t.Minutes > 0 ? t.Minutes : (t.Seconds > 0 ? t.Seconds : 0))))).ToString());
return "";
我更喜欢这个版本,因为它简洁,并且能够添加新的刻度点。
这可以用 Timespan 的 Latest()
扩展来封装,而不是那个长的 1 衬里,但为了简洁起见,这样就可以了。
这通过提供一个小时直到 2 小时过去来修复一个小时前,1 小时前
【讨论】:
我在使用这个函数时遇到了各种各样的问题,例如,如果你模拟 'theDate = DateTime.Now.AddMinutes(-40);'我得到了“40 小时前”,但随着迈克尔的 refactormycode 响应,它在“40 分钟前”返回正确? 我认为你错过了一个零,请尝试: long since = (DateTime.Now.Ticks - theDate.Ticks) / 10000000; 嗯,虽然此代码可能有效,但假设字典中键的顺序将按特定顺序是不正确且无效的。 Dictionary 使用 Object.GetHashCode() ,它不返回 long 而是 int!。如果要对这些进行排序,则应使用 SortedListSortedDictionary
而不是普通的Dictionary
:用法相同,但它确保键已排序。但即便如此,该算法仍有缺陷,因为RelativeDate(DateTime.Now.AddMonths(-3).AddDays(-3))
返回“95 个月前”,无论您使用哪种字典类型,这是不正确的(它应该返回“3 个月前”或“ 4 个月前”取决于您使用的阈值)-即使 -3 不会在过去一年中创建日期(我在 12 月对此进行了测试,因此在这种情况下它不应该发生)。【参考方案5】:
这里是 Jeffs Script for php 的重写:
define("SECOND", 1);
define("MINUTE", 60 * SECOND);
define("HOUR", 60 * MINUTE);
define("DAY", 24 * HOUR);
define("MONTH", 30 * DAY);
function relativeTime($time)
$delta = time() - $time;
if ($delta < 1 * MINUTE)
return $delta == 1 ? "one second ago" : $delta . " seconds ago";
if ($delta < 2 * MINUTE)
return "a minute ago";
if ($delta < 45 * MINUTE)
return floor($delta / MINUTE) . " minutes ago";
if ($delta < 90 * MINUTE)
return "an hour ago";
if ($delta < 24 * HOUR)
return floor($delta / HOUR) . " hours ago";
if ($delta < 48 * HOUR)
return "yesterday";
if ($delta < 30 * DAY)
return floor($delta / DAY) . " days ago";
if ($delta < 12 * MONTH)
$months = floor($delta / DAY / 30);
return $months <= 1 ? "one month ago" : $months . " months ago";
else
$years = floor($delta / DAY / 365);
return $years <= 1 ? "one year ago" : $years . " years ago";
【讨论】:
问题是C#标记为什么是PHP代码?【参考方案6】: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)))); // 60 * 24
aValue.Add(2880, () => "a day"); // 60 * 48
aValue.Add(43200, () => string.Format("0 days", Math.Floor(Math.Abs(oSpan.TotalDays)))); // 60 * 24 * 30
aValue.Add(86400, () => "about a month"); // 60 * 24 * 60
aValue.Add(525600, () => string.Format("0 months", Math.Floor(Math.Abs(oSpan.TotalDays / 30)))); // 60 * 24 * 365
aValue.Add(1051200, () => "about a year"); // 60 * 24 * 365 * 2
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;
http://refactormycode.com/codes/493-twitter-esque-relative-dates
C# 6 版本:
static readonly SortedList<double, Func<TimeSpan, string>> offsets =
new SortedList<double, Func<TimeSpan, string>>
0.75, _ => "less than a minute",
1.5, _ => "about a minute",
45, x => $"x.TotalMinutes:F0 minutes",
90, x => "about an hour",
1440, x => $"about x.TotalHours:F0 hours",
2880, x => "a day",
43200, x => $"x.TotalDays:F0 days",
86400, x => "about a month",
525600, x => $"x.TotalDays / 30:F0 months",
1051200, x => "about a year",
double.MaxValue, x => $"x.TotalDays / 365:F0 years"
;
public static string ToRelativeDate(this DateTime input)
TimeSpan x = DateTime.Now - input;
string Suffix = x.TotalMinutes > 0 ? " ago" : " from now";
x = new TimeSpan(Math.Abs(x.Ticks));
return offsets.First(n => x.TotalMinutes < n.Key).Value(x) + Suffix;
【讨论】:
这是非常好的 IMO :) 这也可以重构为扩展方法?字典可以变成静态的,所以它只创建一次并在之后引用吗? Pure.Krome: ***.com/questions/11/how-do-i-calculate-relative-time/… 您可能希望将该字典提取到一个字段中,以便减少实例化和 GC 流失。您必须将Func<string>
更改为 Func<double>
。【参考方案7】:
这是我作为扩展方法添加到 DateTime 类的一个实现,它处理未来和过去的日期,并提供一个近似选项,允许您指定您正在寻找的详细程度(“3 小时前”与“ 3 小时 23 分 12 秒前”):
using System.Text;
/// <summary>
/// Compares a supplied date to the current date and generates a friendly English
/// comparison ("5 days ago", "5 days from now")
/// </summary>
/// <param name="date">The date to convert</param>
/// <param name="approximate">When off, calculate timespan down to the second.
/// When on, approximate to the largest round unit of time.</param>
/// <returns></returns>
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;
if (timeSpan.Hours > 0)
sb.AppendFormat("01 2", (sb.Length > 0) ? ", " : string.Empty,
timeSpan.Hours, (timeSpan.Hours > 1) ? "hours" : "hour");
if (approximate) return sb.ToString() + suffix;
if (timeSpan.Minutes > 0)
sb.AppendFormat("01 2", (sb.Length > 0) ? ", " : string.Empty,
timeSpan.Minutes, (timeSpan.Minutes > 1) ? "minutes" : "minute");
if (approximate) return sb.ToString() + suffix;
if (timeSpan.Seconds > 0)
sb.AppendFormat("01 2", (sb.Length > 0) ? ", " : string.Empty,
timeSpan.Seconds, (timeSpan.Seconds > 1) ? "seconds" : "second");
if (approximate) return sb.ToString() + suffix;
if (sb.Length == 0) return "right now";
sb.Append(suffix);
return sb.ToString();
【讨论】:
【参考方案8】:Nuget 上还有一个名为 Humanizr 的包,它实际上运行良好,并且在 .NET Foundation 中。
DateTime.UtcNow.AddHours(-30).Humanize() => "yesterday"
DateTime.UtcNow.AddHours(-2).Humanize() => "2 hours ago"
DateTime.UtcNow.AddHours(30).Humanize() => "tomorrow"
DateTime.UtcNow.AddHours(2).Humanize() => "2 hours from now"
TimeSpan.FromMilliseconds(1299630020).Humanize() => "2 weeks"
TimeSpan.FromMilliseconds(1299630020).Humanize(3) => "2 weeks, 1 day, 1 hour"
Scott Hanselman 在他的 blog 上有一篇关于它的文章
【讨论】:
友情提示:在.net 4.5或更高版本上不要安装完整的Humanizer...只安装Humanizer.Core的一部分..因为这个版本不支持其他语言包【参考方案9】:我也建议在客户端进行计算。服务器的工作量减少。
以下是我使用的版本(来自 Zach Leatherman)
/*
* Javascript Humane Dates
* Copyright (c) 2008 Dean Landolt (deanlandolt.com)
* Re-write by Zach Leatherman (zachleat.com)
*
* Adopted from the John Resig's pretty.js
* at http://ejohn.org/blog/javascript-pretty-date
* and henrah's proposed modification
* at http://ejohn.org/blog/javascript-pretty-date/#comment-297458
*
* Licensed under the MIT license.
*/
function humane_date(date_str)
var time_formats = [
[60, 'just now'],
[90, '1 minute'], // 60*1.5
[3600, 'minutes', 60], // 60*60, 60
[5400, '1 hour'], // 60*60*1.5
[86400, 'hours', 3600], // 60*60*24, 60*60
[129600, '1 day'], // 60*60*24*1.5
[604800, 'days', 86400], // 60*60*24*7, 60*60*24
[907200, '1 week'], // 60*60*24*7*1.5
[2628000, 'weeks', 604800], // 60*60*24*(365/12), 60*60*24*7
[3942000, '1 month'], // 60*60*24*(365/12)*1.5
[31536000, 'months', 2628000], // 60*60*24*365, 60*60*24*(365/12)
[47304000, '1 year'], // 60*60*24*365*1.5
[3153600000, 'years', 31536000], // 60*60*24*365*100, 60*60*24*365
[4730400000, '1 century'] // 60*60*24*365*100*1.5
];
var time = ('' + date_str).replace(/-/g,"/").replace(/[TZ]/g," "),
dt = new Date,
seconds = ((dt - new Date(time) + (dt.getTimezoneOffset() * 60000)) / 1000),
token = ' ago',
i = 0,
format;
if (seconds < 0)
seconds = Math.abs(seconds);
token = '';
while (format = time_formats[i++])
if (seconds < format[0])
if (format.length == 2)
return format[1] + (i > 1 ? token : ''); // Conditional so we don't return Just Now Ago
else
return Math.round(seconds / format[2]) + ' ' + format[1] + (i > 1 ? token : '');
// overflow for centuries
if(seconds > 4730400000)
return Math.round(seconds / 4730400000) + ' centuries' + token;
return date_str;
;
if(typeof jQuery != 'undefined')
jQuery.fn.humane_dates = function()
return this.each(function()
var date = humane_date(this.title);
if(date && jQuery(this).text() != date) // don't modify the dom if we don't have to
jQuery(this).text(date);
);
;
【讨论】:
问题是C#标记为什么是Javascript代码?【参考方案10】:@杰夫
恕我直言,你的似乎有点长。然而,在支持“昨天”和“岁月”的情况下,它似乎更加强大。但根据我的使用经验,用户最有可能在前 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();
【讨论】:
【参考方案11】:晚会晚了几年,但我需要为过去和未来的日期执行此操作,因此我将Jeff 和Vincent's 合并到此。这是一场三重盛宴! :)
public static class DateTimeHelper
private const int SECOND = 1;
private const int MINUTE = 60 * SECOND;
private const int HOUR = 60 * MINUTE;
private const int DAY = 24 * HOUR;
private const int MONTH = 30 * DAY;
/// <summary>
/// Returns a friendly version of the provided DateTime, relative to now. E.g.: "2 days ago", or "in 6 months".
/// </summary>
/// <param name="dateTime">The DateTime to compare to Now</param>
/// <returns>A friendly string</returns>
public static string GetFriendlyRelativeTime(DateTime dateTime)
if (DateTime.UtcNow.Ticks == dateTime.Ticks)
return "Right now!";
bool isFuture = (DateTime.UtcNow.Ticks < dateTime.Ticks);
var ts = DateTime.UtcNow.Ticks < dateTime.Ticks ? new TimeSpan(dateTime.Ticks - DateTime.UtcNow.Ticks) : new TimeSpan(DateTime.UtcNow.Ticks - dateTime.Ticks);
double delta = ts.TotalSeconds;
if (delta < 1 * MINUTE)
return isFuture ? "in " + (ts.Seconds == 1 ? "one second" : ts.Seconds + " seconds") : ts.Seconds == 1 ? "one second ago" : ts.Seconds + " seconds ago";
if (delta < 2 * MINUTE)
return isFuture ? "in a minute" : "a minute ago";
if (delta < 45 * MINUTE)
return isFuture ? "in " + ts.Minutes + " minutes" : ts.Minutes + " minutes ago";
if (delta < 90 * MINUTE)
return isFuture ? "in an hour" : "an hour ago";
if (delta < 24 * HOUR)
return isFuture ? "in " + ts.Hours + " hours" : ts.Hours + " hours ago";
if (delta < 48 * HOUR)
return isFuture ? "tomorrow" : "yesterday";
if (delta < 30 * DAY)
return isFuture ? "in " + ts.Days + " days" : ts.Days + " days ago";
if (delta < 12 * MONTH)
int months = Convert.ToInt32(Math.Floor((double)ts.Days / 30));
return isFuture ? "in " + (months <= 1 ? "one month" : months + " months") : months <= 1 ? "one month ago" : months + " months ago";
else
int years = Convert.ToInt32(Math.Floor((double)ts.Days / 365));
return isFuture ? "in " + (years <= 1 ? "one year" : years + " years") : years <= 1 ? "one year ago" : years + " years ago";
【讨论】:
【参考方案12】:在 Java 中有没有简单的方法来做到这一点? java.util.Date
类似乎相当有限。
这是我快速而肮脏的 Java 解决方案:
import java.util.Date;
import javax.management.timer.Timer;
String getRelativeDate(Date date)
long delta = new Date().getTime() - date.getTime();
if (delta < 1L * Timer.ONE_MINUTE)
return toSeconds(delta) == 1 ? "one second ago" : toSeconds(delta) + " seconds ago";
if (delta < 2L * Timer.ONE_MINUTE)
return "a minute ago";
if (delta < 45L * Timer.ONE_MINUTE)
return toMinutes(delta) + " minutes ago";
if (delta < 90L * Timer.ONE_MINUTE)
return "an hour ago";
if (delta < 24L * Timer.ONE_HOUR)
return toHours(delta) + " hours ago";
if (delta < 48L * Timer.ONE_HOUR)
return "yesterday";
if (delta < 30L * Timer.ONE_DAY)
return toDays(delta) + " days ago";
if (delta < 12L * 4L * Timer.ONE_WEEK) // a month
long months = toMonths(delta);
return months <= 1 ? "one month ago" : months + " months ago";
else
long years = toYears(delta);
return years <= 1 ? "one year ago" : years + " years ago";
private long toSeconds(long date)
return date / 1000L;
private long toMinutes(long date)
return toSeconds(date) / 60L;
private long toHours(long date)
return toMinutes(date) / 60L;
private long toDays(long date)
return toHours(date) / 24L;
private long toMonths(long date)
return toDays(date) / 30L;
private long toYears(long date)
return toMonths(date) / 365L;
【讨论】:
问题是C#标记为什么是Java代码?【参考方案13】:iPhone Objective-C 版本
+ (NSString *)timeAgoString:(NSDate *)date
int delta = -(int)[date timeIntervalSinceNow];
if (delta < 60)
return delta == 1 ? @"one second ago" : [NSString stringWithFormat:@"%i seconds ago", delta];
if (delta < 120)
return @"a minute ago";
if (delta < 2700)
return [NSString stringWithFormat:@"%i minutes ago", delta/60];
if (delta < 5400)
return @"an hour ago";
if (delta < 24 * 3600)
return [NSString stringWithFormat:@"%i hours ago", delta/3600];
if (delta < 48 * 3600)
return @"yesterday";
if (delta < 30 * 24 * 3600)
return [NSString stringWithFormat:@"%i days ago", delta/(24*3600)];
if (delta < 12 * 30 * 24 * 3600)
int months = delta/(30*24*3600);
return months <= 1 ? @"one month ago" : [NSString stringWithFormat:@"%i months ago", months];
else
int years = delta/(12*30*24*3600);
return years <= 1 ? @"one year ago" : [NSString stringWithFormat:@"%i years ago", years];
【讨论】:
【参考方案14】:鉴于世界和她的丈夫似乎正在发布代码示例,这是我前段时间根据其中几个答案写的。
我特别需要此代码可本地化。所以我有两个类——Grammar
,它指定可本地化的术语,和FuzzyDateExtensions
,它包含一堆扩展方法。我不需要处理未来的日期时间,因此不会尝试使用此代码处理它们。
为了简洁起见,我在源代码中保留了一些 XMLdoc,但删除了大部分(它们很明显)。我也没有将每个班级成员都包括在内:
public class Grammar
/// <summary> Gets or sets the term for "just now". </summary>
public string JustNow get; set;
/// <summary> Gets or sets the term for "X minutes ago". </summary>
/// <remarks>
/// This is a <see cref="String.Format"/> pattern, where <c>0</c>
/// is the number of minutes.
/// </remarks>
public string MinutesAgo get; set;
public string OneHourAgo get; set;
public string HoursAgo get; set;
public string Yesterday get; set;
public string DaysAgo get; set;
public string LastMonth get; set;
public string MonthsAgo get; set;
public string LastYear get; set;
public string YearsAgo get; set;
/// <summary> Gets or sets the term for "ages ago". </summary>
public string AgesAgo get; set;
/// <summary>
/// Gets or sets the threshold beyond which the fuzzy date should be
/// considered "ages ago".
/// </summary>
public TimeSpan AgesAgoThreshold get; set;
/// <summary>
/// Initialises a new <see cref="Grammar"/> instance with the
/// specified properties.
/// </summary>
private void Initialise(string justNow, string minutesAgo,
string oneHourAgo, string hoursAgo, string yesterday, string daysAgo,
string lastMonth, string monthsAgo, string lastYear, string yearsAgo,
string agesAgo, TimeSpan agesAgoThreshold)
...
FuzzyDateString
类包含:
public static class FuzzyDateExtensions
public static string ToFuzzyDateString(this TimeSpan timespan)
return timespan.ToFuzzyDateString(new Grammar());
public static string ToFuzzyDateString(this TimeSpan timespan,
Grammar grammar)
return GetFuzzyDateString(timespan, grammar);
public static string ToFuzzyDateString(this DateTime datetime)
return (DateTime.Now - datetime).ToFuzzyDateString();
public static string ToFuzzyDateString(this DateTime datetime,
Grammar grammar)
return (DateTime.Now - datetime).ToFuzzyDateString(grammar);
private static string GetFuzzyDateString(TimeSpan timespan,
Grammar grammar)
timespan = timespan.Duration();
if (timespan >= grammar.AgesAgoThreshold)
return grammar.AgesAgo;
if (timespan < new TimeSpan(0, 2, 0)) // 2 minutes
return grammar.JustNow;
if (timespan < new TimeSpan(1, 0, 0)) // 1 hour
return String.Format(grammar.MinutesAgo, timespan.Minutes);
if (timespan < new TimeSpan(1, 55, 0)) // 1 hour 55 minutes
return grammar.OneHourAgo;
if (timespan < new TimeSpan(12, 0, 0) // 12 hours
&& (DateTime.Now - timespan).IsToday())
return String.Format(grammar.HoursAgo, timespan.RoundedHours());
if ((DateTime.Now.AddDays(1) - timespan).IsToday())
return grammar.Yesterday;
if (timespan < new TimeSpan(32, 0, 0, 0) // 32 days
&& (DateTime.Now - timespan).IsThisMonth())
return String.Format(grammar.DaysAgo, timespan.RoundedDays());
if ((DateTime.Now.AddMonths(1) - timespan).IsThisMonth())
return grammar.LastMonth;
if (timespan < new TimeSpan(365, 0, 0, 0, 0) // 365 days
&& (DateTime.Now - timespan).IsThisYear())
return String.Format(grammar.MonthsAgo, timespan.RoundedMonths());
if ((DateTime.Now - timespan).AddYears(1).IsThisYear())
return grammar.LastYear;
return String.Format(grammar.YearsAgo, timespan.RoundedYears());
除了本地化之外,我想要实现的关键目标之一是“今天”仅表示“本日历日”,因此 IsToday
、IsThisMonth
、IsThisYear
方法如下所示:
public static bool IsToday(this DateTime date)
return date.DayOfYear == DateTime.Now.DayOfYear && date.IsThisYear();
四舍五入的方法是这样的(我已经包含了RoundedMonths
,因为这有点不同):
public static int RoundedDays(this TimeSpan timespan)
return (timespan.Hours > 12) ? timespan.Days + 1 : timespan.Days;
public static int RoundedMonths(this TimeSpan timespan)
DateTime then = DateTime.Now - timespan;
// Number of partial months elapsed since 1 Jan, AD 1 (DateTime.MinValue)
int nowMonthYears = DateTime.Now.Year * 12 + DateTime.Now.Month;
int thenMonthYears = then.Year * 12 + then.Month;
return nowMonthYears - thenMonthYears;
我希望人们觉得这很有用和/或有趣:o)
【讨论】:
【参考方案15】:使用Fluent DateTime
var dateTime1 = 2.Hours().Ago();
var dateTime2 = 3.Days().Ago();
var dateTime3 = 1.Months().Ago();
var dateTime4 = 5.Hours().FromNow();
var dateTime5 = 2.Weeks().FromNow();
var dateTime6 = 40.Seconds().FromNow();
【讨论】:
【参考方案16】:在 PHP 中,我是这样做的:
<?php
function timesince($original)
// array of time period chunks
$chunks = array(
array(60 * 60 * 24 * 365 , 'year'),
array(60 * 60 * 24 * 30 , 'month'),
array(60 * 60 * 24 * 7, 'week'),
array(60 * 60 * 24 , 'day'),
array(60 * 60 , 'hour'),
array(60 , 'minute'),
);
$today = time(); /* Current unix time */
$since = $today - $original;
if($since > 604800)
$print = date("M jS", $original);
if($since > 31536000)
$print .= ", " . date("Y", $original);
return $print;
// $j saves performing the count function each time around the loop
for ($i = 0, $j = count($chunks); $i < $j; $i++)
$seconds = $chunks[$i][0];
$name = $chunks[$i][1];
// finding the biggest chunk (if the chunk fits, break)
if (($count = floor($since / $seconds)) != 0)
break;
$print = ($count == 1) ? '1 '.$name : "$count $names";
return $print . " ago";
?>
【讨论】:
问题是 C# 标记。为什么这个 PHP 代码 ? 恕我直言,仅适用于 C# 代码【参考方案17】:我想我会尝试使用类和多态性。我之前的迭代使用了子类化,最终开销太大。我已切换到更灵活的委托/公共属性对象模型,该模型要好得多。我的代码稍微准确一些,我希望我能想出一个更好的方法来生成“几个月前”,看起来并不过分设计。
我认为我仍然会坚持使用 Jeff 的 if-then 级联,因为它的代码更少而且更简单(确保它按预期工作肯定更容易)。
对于下面的代码PrintRelativeTime.GetRelativeTimeMessage(TimeSpan ago)返回相对时间消息(例如“昨天”)。
public class RelativeTimeRange : IComparable
public TimeSpan UpperBound get; set;
public delegate string RelativeTimeTextDelegate(TimeSpan timeDelta);
public RelativeTimeTextDelegate MessageCreator get; set;
public int CompareTo(object obj)
if (!(obj is RelativeTimeRange))
return 1;
// note that this sorts in reverse order to the way you'd expect,
// this saves having to reverse a list later
return (obj as RelativeTimeRange).UpperBound.CompareTo(UpperBound);
public class PrintRelativeTime
private static List<RelativeTimeRange> timeRanges;
static PrintRelativeTime()
timeRanges = new List<RelativeTimeRange>
new RelativeTimeRange
UpperBound = TimeSpan.FromSeconds(1),
MessageCreator = (delta) =>
return "one second ago";
,
new RelativeTimeRange
UpperBound = TimeSpan.FromSeconds(60),
MessageCreator = (delta) =>
return delta.Seconds + " seconds ago";
,
new RelativeTimeRange
UpperBound = TimeSpan.FromMinutes(2),
MessageCreator = (delta) =>
return "one minute ago";
,
new RelativeTimeRange
UpperBound = TimeSpan.FromMinutes(60),
MessageCreator = (delta) =>
return delta.Minutes + " minutes ago";
,
new RelativeTimeRange
UpperBound = TimeSpan.FromHours(2),
MessageCreator = (delta) =>
return "one hour ago";
,
new RelativeTimeRange
UpperBound = TimeSpan.FromHours(24),
MessageCreator = (delta) =>
return delta.Hours + " hours ago";
,
new RelativeTimeRange
UpperBound = TimeSpan.FromDays(2),
MessageCreator = (delta) =>
return "yesterday";
,
new RelativeTimeRange
UpperBound = DateTime.Now.Subtract(DateTime.Now.AddMonths(-1)),
MessageCreator = (delta) =>
return delta.Days + " days ago";
,
new RelativeTimeRange
UpperBound = DateTime.Now.Subtract(DateTime.Now.AddMonths(-2)),
MessageCreator = (delta) =>
return "one month ago";
,
new RelativeTimeRange
UpperBound = DateTime.Now.Subtract(DateTime.Now.AddYears(-1)),
MessageCreator = (delta) =>
return (int)Math.Floor(delta.TotalDays / 30) + " months ago";
,
new RelativeTimeRange
UpperBound = DateTime.Now.Subtract(DateTime.Now.AddYears(-2)),
MessageCreator = (delta) =>
return "one year ago";
,
new RelativeTimeRange
UpperBound = TimeSpan.MaxValue,
MessageCreator = (delta) =>
return (int)Math.Floor(delta.TotalDays / 365.24D) + " years ago";
;
timeRanges.Sort();
public static string GetRelativeTimeMessage(TimeSpan ago)
RelativeTimeRange postRelativeDateRange = timeRanges[0];
foreach (var timeRange in timeRanges)
if (ago.CompareTo(timeRange.UpperBound) <= 0)
postRelativeDateRange = timeRange;
return postRelativeDateRange.MessageCreator(ago);
【讨论】:
【参考方案18】:如果您知道查看者的时区,则在日尺度上使用日历日可能会更清楚。我不熟悉 .NET 库,所以很遗憾,我不知道你会如何在 C# 中做到这一点。
在消费者网站上,您也可以在一分钟内轻松应对。 “不到一分钟前”或“刚刚”就足够了。
【讨论】:
【参考方案19】:using System;
using System.Collections.Generic;
using System.Linq;
public static class RelativeDateHelper
private static Dictionary<double, Func<double, string>> sm_Dict = null;
private static Dictionary<double, Func<double, string>> DictionarySetup()
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");
dict.Add(45, (mins) => string.Format("0 minutes", Math.Round(mins)));
dict.Add(90, (mins) => "about an hour");
dict.Add(1440, (mins) => string.Format("about 0 hours", Math.Round(Math.Abs(mins / 60)))); // 60 * 24
dict.Add(2880, (mins) => "a day"); // 60 * 48
dict.Add(43200, (mins) => string.Format("0 days", Math.Floor(Math.Abs(mins / 1440)))); // 60 * 24 * 30
dict.Add(86400, (mins) => "about a month"); // 60 * 24 * 60
dict.Add(525600, (mins) => string.Format("0 months", Math.Floor(Math.Abs(mins / 43200)))); // 60 * 24 * 365
dict.Add(1051200, (mins) => "about a year"); // 60 * 24 * 365 * 2
dict.Add(double.MaxValue, (mins) => string.Format("0 years", Math.Floor(Math.Abs(mins / 525600))));
return dict;
public static string ToRelativeDate(this 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 (null == sm_Dict)
sm_Dict = DictionarySetup();
return sm_Dict.First(n => TotalMinutes < n.Key).Value.Invoke(TotalMinutes) + Suffix;
与another answer to this question 相同,但作为带有静态字典的扩展方法。
【讨论】:
字典在这里给你买了什么? StriplingWarrior:与 switch 语句或 if/else 语句堆栈相比,易于阅读和修改。字典是静态的意味着每次我们想使用 ToRelativeDate 时不必创建它和 Func 对象;与我在答案中链接的相比,它只创建了一次。 我明白了。我只是在想,因为Dictionary
上的文档指出“返回项目的顺序是未定义的”(msdn.microsoft.com/en-us/library/xfhwa508.aspx)当您不关心查找时间时,这可能不是最好的数据结构尽可能让事情井井有条。
StriplingWarrior:我相信 LINQ 在与Dictionary
s 一起使用时会考虑到这一点。如果你还是觉得不舒服,你可以使用SortedDictionary
,但我自己的经验表明这是不必要的。【参考方案20】:
你可以试试这个。我认为它会正常工作。
long delta = new Date().getTime() - date.getTime();
const int SECOND = 1;
const int MINUTE = 60 * SECOND;
const int HOUR = 60 * MINUTE;
const int DAY = 24 * HOUR;
const int MONTH = 30 * DAY;
if (delta < 0L)
return "not yet";
if (delta < 1L * MINUTE)
return ts.Seconds == 1 ? "one second ago" : ts.Seconds + " seconds ago";
if (delta < 2L * MINUTE)
return "a minute ago";
if (delta < 45L * MINUTE)
return ts.Minutes + " minutes ago";
if (delta < 90L * MINUTE)
return "an hour ago";
if (delta < 24L * HOUR)
return ts.Hours + " hours ago";
if (delta < 48L * HOUR)
return "yesterday";
if (delta < 30L * DAY)
return ts.Days + " days ago";
if (delta < 12L * MONTH)
int months = Convert.ToInt32(Math.Floor((double)ts.Days / 30));
return months <= 1 ? "one month ago" : months + " months ago";
else
int years = Convert.ToInt32(Math.Floor((double)ts.Days / 365));
return years <= 1 ? "one year ago" : years + " years ago";
【讨论】:
【参考方案21】:@杰夫
var ts = new TimeSpan(DateTime.UtcNow.Ticks - dt.Ticks);
对DateTime
进行减法运算无论如何都会返回TimeSpan
。
所以你可以这样做
(DateTime.UtcNow - dt).TotalSeconds
我也很惊讶地看到手动将常量相乘,然后将 cmets 与乘法相加。这是一些误导性的优化吗?
【讨论】:
【参考方案22】:您可以通过在客户端执行此逻辑来减少服务器端负载。在一些 Digg 页面上查看源代码以供参考。他们让服务器发出一个由 Javascript 处理的纪元时间值。这样您就不需要管理最终用户的时区。新的服务器端代码类似于:
public string GetRelativeTime(DateTime timeStamp)
return string.Format("<script>printdate(0);</script>", timeStamp.ToFileTimeUtc());
您甚至可以在那里添加一个 NOSCRIPT 块,然后执行一个 ToString()。
【讨论】:
【参考方案23】:您可以使用TimeAgo extension,如下:
public static string TimeAgo(this DateTime dateTime)
string result = string.Empty;
var timeSpan = DateTime.Now.Subtract(dateTime);
if (timeSpan <= TimeSpan.FromSeconds(60))
result = string.Format("0 seconds ago", timeSpan.Seconds);
else if (timeSpan <= TimeSpan.FromMinutes(60))
result = timeSpan.Minutes > 1 ?
String.Format("about 0 minutes ago", timeSpan.Minutes) :
"about a minute ago";
else if (timeSpan <= TimeSpan.FromHours(24))
result = timeSpan.Hours > 1 ?
String.Format("about 0 hours ago", timeSpan.Hours) :
"about an hour ago";
else if (timeSpan <= TimeSpan.FromDays(30))
result = timeSpan.Days > 1 ?
String.Format("about 0 days ago", timeSpan.Days) :
"yesterday";
else if (timeSpan <= TimeSpan.FromDays(365))
result = timeSpan.Days > 30 ?
String.Format("about 0 months ago", timeSpan.Days / 30) :
"about a month ago";
else
result = timeSpan.Days > 365 ?
String.Format("about 0 years ago", timeSpan.Days / 365) :
"about a year ago";
return result;
或者使用 jQuery plugin 和 Timeago 的 Razor 扩展。
【讨论】:
【参考方案24】:这是 *** 使用的算法,但用 perlish 伪代码更简洁地重写了错误修复(没有“一小时前”)。该函数需要一个(正)秒数,然后返回一个对人类友好的字符串,例如“3 hours ago”或“yesterday”。
agoify($delta)
local($y, $mo, $d, $h, $m, $s);
$s = floor($delta);
if($s<=1) return "a second ago";
if($s<60) return "$s seconds ago";
$m = floor($s/60);
if($m==1) return "a minute ago";
if($m<45) return "$m minutes ago";
$h = floor($m/60);
if($h==1) return "an hour ago";
if($h<24) return "$h hours ago";
$d = floor($h/24);
if($d<2) return "yesterday";
if($d<30) return "$d days ago";
$mo = floor($d/30);
if($mo<=1) return "a month ago";
$y = floor($mo/12);
if($y<1) return "$mo months ago";
if($y==1) return "a year ago";
return "$y years ago";
【讨论】:
【参考方案25】:用于客户端 gwt 的 Java:
import java.util.Date;
public class RelativeDateFormat
private static final long ONE_MINUTE = 60000L;
private static final long ONE_HOUR = 3600000L;
private static final long ONE_DAY = 86400000L;
private static final long ONE_WEEK = 604800000L;
public static String format(Date date)
long delta = new Date().getTime() - date.getTime();
if (delta < 1L * ONE_MINUTE)
return toSeconds(delta) == 1 ? "one second ago" : toSeconds(delta)
+ " seconds ago";
if (delta < 2L * ONE_MINUTE)
return "one minute ago";
if (delta < 45L * ONE_MINUTE)
return toMinutes(delta) + " minutes ago";
if (delta < 90L * ONE_MINUTE)
return "one hour ago";
if (delta < 24L * ONE_HOUR)
return toHours(delta) + " hours ago";
if (delta < 48L * ONE_HOUR)
return "yesterday";
if (delta < 30L * ONE_DAY)
return toDays(delta) + " days ago";
if (delta < 12L * 4L * ONE_WEEK)
long months = toMonths(delta);
return months <= 1 ? "one month ago" : months + " months ago";
else
long years = toYears(delta);
return years <= 1 ? "one year ago" : years + " years ago";
private static long toSeconds(long date)
return date / 1000L;
private static long toMinutes(long date)
return toSeconds(date) / 60L;
private static long toHours(long date)
return toMinutes(date) / 60L;
private static long toDays(long date)
return toHours(date) / 24L;
private static long toMonths(long date)
return toDays(date) / 30L;
private static long toYears(long date)
return toMonths(date) / 365L;
【讨论】:
问题是 C# 标记。为什么是这个 Java 代码 ? 恕我直言,仅适用于 C# 代码【参考方案26】:我从比尔盖茨的一个博客中得到了这个答案。我需要在我的浏览器历史记录中找到它,然后我会给你链接。
执行相同操作的 Javascript 代码(按要求):
function posted(t)
var now = new Date();
var diff = parseInt((now.getTime() - Date.parse(t)) / 1000);
if (diff < 60) return 'less than a minute ago';
else if (diff < 120) return 'about a minute ago';
else if (diff < (2700)) return (parseInt(diff / 60)).toString() + ' minutes ago';
else if (diff < (5400)) return 'about an hour ago';
else if (diff < (86400)) return 'about ' + (parseInt(diff / 3600)).toString() + ' hours ago';
else if (diff < (172800)) return '1 day ago';
else return (parseInt(diff / 86400)).toString() + ' days ago';
基本上,您以秒为单位。
【讨论】:
【参考方案27】:我认为已经有很多与这篇文章相关的答案,但是可以使用它,它就像插件一样易于使用,并且对于程序员来说也易于阅读。 发送您的具体日期,并以字符串形式获取其值:
public string RelativeDateTimeCount(DateTime inputDateTime)
string outputDateTime = string.Empty;
TimeSpan ts = DateTime.Now - inputDateTime;
if (ts.Days > 7)
outputDateTime = inputDateTime.ToString("MMMM d, yyyy");
else if (ts.Days > 0)
outputDateTime = ts.Days == 1 ? ("about 1 Day ago") : ("about " + ts.Days.ToString() + " Days ago");
else if (ts.Hours > 0)
outputDateTime = ts.Hours == 1 ? ("an hour ago") : (ts.Hours.ToString() + " hours ago");
else if (ts.Minutes > 0)
outputDateTime = ts.Minutes == 1 ? ("1 minute ago") : (ts.Minutes.ToString() + " minutes ago");
else outputDateTime = "few seconds ago";
return outputDateTime;
【讨论】:
【参考方案28】:var ts = new TimeSpan(DateTime.Now.Ticks - dt.Ticks);
【讨论】:
【参考方案29】:如果你想要像"2 days, 4 hours and 12 minutes ago"
这样的输出,你需要一个时间跨度:
TimeSpan timeDiff = DateTime.Now-CreatedDate;
然后你可以访问你喜欢的值:
timeDiff.Days
timeDiff.Hours
等等……
【讨论】:
【参考方案30】:我会为此提供一些方便的扩展方法并使代码更具可读性。首先,Int32
的几个扩展方法。
public static class TimeSpanExtensions
public static TimeSpan Days(this int value)
return new TimeSpan(value, 0, 0, 0);
public static TimeSpan Hours(this int value)
return new TimeSpan(0, value, 0, 0);
public static TimeSpan Minutes(this int value)
return new TimeSpan(0, 0, value, 0);
public static TimeSpan Seconds(this int value)
return new TimeSpan(0, 0, 0, value);
public static TimeSpan Milliseconds(this int value)
return new TimeSpan(0, 0, 0, 0, value);
public static DateTime Ago(this TimeSpan value)
return DateTime.Now - value;
那么,给DateTime
一个。
public static class DateTimeExtensions
public static DateTime Ago(this DateTime dateTime, TimeSpan delta)
return dateTime - delta;
现在,您可以执行以下操作:
var date = DateTime.Now;
date.Ago(2.Days()); // 2 days ago
date.Ago(7.Hours()); // 7 hours ago
date.Ago(567.Milliseconds()); // 567 milliseconds ago
【讨论】:
以上是关于在 C# 中计算相对时间的主要内容,如果未能解决你的问题,请参考以下文章
C# 中是不是存在一种方法来获取给定两个绝对路径输入的相对路径? [复制]