用C#计算相对时间

给定一个特定的DateTime值,如何显示相对时间,如:

  • 2小时前
  • 3天前
  • 一个月前

杰夫, 你的代码是好的,但可以更清晰的常量(如代码完成build议)。

 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"; } 

jquery.timeago插件

杰夫,因为堆栈溢出广泛使用jQuery,我推荐jquery.timeago插件 。

优点:

  • 避免时间戳记为“1分钟前”,即使页面在10分钟前打开; timeago自动刷新。
  • 您可以充分利用网页应用程序中的页面和/或片段caching,因为时间戳不在服务器上计算。
  • 你可以使用像酷儿一样的微格式。

只需将其附加到DOM准备好的时间戳:

 jQuery(document).ready(function() { jQuery('abbr.timeago').timeago(); }); 

这将在标题中使用一个timeago和一个ISO 8601时间戳来转换所有的abbr元素:

 <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个月前。 随着时间的推移,时间戳会自动更新。

免责声明:我写了这个插件,所以我有点偏见。

那么,这里是我们如何做堆栈溢出。

 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 < 120) { return "a minute ago"; } if (delta < 2700) // 45 * 60 { return ts.Minutes + " minutes ago"; } if (delta < 5400) // 90 * 60 { return "an hour ago"; } if (delta < 86400) // 24 * 60 * 60 { return ts.Hours + " hours ago"; } if (delta < 172800) // 48 * 60 * 60 { return "yesterday"; } if (delta < 2592000) // 30 * 24 * 60 * 60 { return ts.Days + " days ago"; } if (delta < 31104000) // 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"; 

build议? 注释? 如何改善这个algorithm?

 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小时已经过去

这里是从PHP的Jeffs脚本重写:

 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"; } } 
 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; } 

这是一个我作为DateTime类的扩展方法添加的实现,它处理将来和过去的date,并提供一个近似选项,允许您指定要查找的详细程度(“3小时前”vs“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("{0}{1} {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("{0}{1} {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("{0}{1} {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(); } 

我会build议在客户端计算这个。 减less服务器的工作。

以下是我使用的版本(来自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); }); }; } 

@jeff

恕我直言,你的看起来有点长。 不过,对“昨天”和“年”的支持看起来似乎更强劲。 但根据我的经验,这个人最有可能在头30天看内容。 那只是真正的铁杆人。 所以我通常select保持简短。

这是我目前在我的一个网站上使用的方法。 这只会返回一个相对的日子,小时,时间。 然后用户不得不在输出中的“前”击中。

 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(); } 

在Nuget上也有一个名为Humanizer的包,它实际上工作得很好

 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在他的博客上写了一篇文章

这个晚会晚了两三年,但是我有要求在过去和未来的日子都这样做,所以我把Jeff和Vincent结合起来了。 这是一个三元的盛会! 🙂

 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. Eg: "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"; } } } 

在Java中有这样一个简单的方法吗? java.util.Date类似乎相当有限。

这是我的快速和肮脏的Java解决scheme:

 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; } 

iPhone的obj – 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]; } 

}

在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 {$name}s"; return $print . " ago"; } ?> 

鉴于世界和她的丈夫似乎是张贴代码示例,这是我刚才写的基于这些答案的几个。

我特别需要这个代码是可以本地化的。 所以我有两个类 – Grammar ,它指定可局部的术语,和FuzzyDateExtensions ,它包含一堆扩展方法。 我不需要处理未来的date,所以没有尝试用这个代码来处理它们。

我已经在源代码中留下了一些XMLdoc,但为了简洁起见,删除了大部分(显而易见的地方)。 我也没有在这里列出每个class级成员:

 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()); } } 

我想实现的以及本地化的关键之一是“今天”只意味着“今天的日子”,所以IsThisMonthIsThisYearIsThisYear方法看起来像这样:

  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)

使用Fluent DateTime https://github.com/FluentDateTime

  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(); 

I thought I'd give this a shot using classes and polymorphism. I had a previous iteration which used sub-classing which ended up having way too much overhead. I've switched to a more flexible delegate / public property object model which is significantly better. My code is very slightly more accurate, I wish I could come up with a better way to generate "months ago" that didn't seem too over-engineered.

I think I'd still stick with Jeff's if-then cascade because it's less code and it's simpler (it's definitely easier to ensure it'll work as expected).

For the below code PrintRelativeTime.GetRelativeTimeMessage(TimeSpan ago) returns the relative time message (eg "yesterday").

 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); } } 

When you know the viewer's time zone, it might be clearer to use calendar days at the day scale. I'm not familiar with the .NET libraries so I don't know how you'd do that in C#, unfortunately.

On consumer sites, you could also be hand-wavier under a minute. "Less than a minute ago" or "just now" could be good enough.

 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; } } 

The same as another answer to this question but as an extension method with a static dictionary.

you can try this.I think it will work correctly.

 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"; } 

You can reduce the server-side load by performing this logic client-side. View source on some Digg pages for reference. They have the server emit an epoch time value that gets processed by Javascript. This way you don't need to manage the end user's time zone. The new server-side code would be something like:

  public string GetRelativeTime(DateTime timeStamp) { return string.Format("<script>printdate({0});</script>", timeStamp.ToFileTimeUtc()); } 

You could even add a NOSCRIPT block there and just perform a ToString().

Java for client-side gwt usage:

 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; } } 

@Jeff

var ts = new TimeSpan(DateTime.UtcNow.Ticks – dt.Ticks);

Doing a subtraction on DateTime returns a TimeSpan anyway.

所以你可以做

 (DateTime.UtcNow - dt).TotalSeconds 

I'm also surprised to see the constants multiplied-out by hand and then comments added with the multiplications in. Was that some misguided optimisation?

Here's the algorithm stackoverflow uses but rewritten more concisely in perlish pseudocode with a bug fix (no "one hours ago"). The function takes a (positive) number of seconds ago and returns a human-friendly string like "3 hours ago" or "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"; 

You can use TimeAgo extension from which looks like the following:

 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; } 

Or use jQuery plugin with Razor extension from Timeago.

This, I got from one of Bill Gates' blog. I need to find it on my browser history and I'll give you the link.

The Javascript code to do the same thing (as requested):

 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'; } } 

Basically, you work in terms of seconds…

I think there is already a number of answers related to this post, but one can use this which is easy to use just like plugin and also easily readable for programmers. Send your specific date, and get its value in string form:

 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; } 
 /** * {@code date1} has to be earlier than {@code date2}. */ public static String relativize(Date date1, Date date2) { assert date2.getTime() >= date1.getTime(); long duration = date2.getTime() - date1.getTime(); long converted; if ((converted = TimeUnit.MILLISECONDS.toDays(duration)) > 0) { return String.format("%d %s ago", converted, converted == 1 ? "day" : "days"); } else if ((converted = TimeUnit.MILLISECONDS.toHours(duration)) > 0) { return String.format("%d %s ago", converted, converted == 1 ? "hour" : "hours"); } else if ((converted = TimeUnit.MILLISECONDS.toMinutes(duration)) > 0) { return String.format("%d %s ago", converted, converted == 1 ? "minute" : "minutes"); } else if ((converted = TimeUnit.MILLISECONDS.toSeconds(duration)) > 0) { return String.format("%d %s ago", converted, converted == 1 ? "second" : "seconds"); } else { return "just now"; } } 

If you want to have an output like "2 days, 4 hours and 12 minutes ago", you need a timespan:

 TimeSpan timeDiff = DateTime.Now-CreatedDate; 

Then you can access the values you like:

 timeDiff.Days timeDiff.Hours 

等等

 var ts = new TimeSpan(DateTime.Now.Ticks - dt.Ticks);