在C#中计算相对时间

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

  • 2 hours ago
  • 3 days ago
  • a month ago
195400 次浏览

我是这样做的

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

建议?评论?改进算法的方法?

@陈志立

恕我直言,你的看起来有点长。然而,支持“昨天”和“岁月”似乎确实更强大一些。但根据我的经验,当使用它时,这个人最有可能在前30天内查看内容。之后才是真正的铁杆人。所以,我通常选择保持简短。

这是我目前在我的一个网站中使用的方法。这只返回相对日、小时和时间。然后用户必须在输出中输入“前”。

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

Jeff,您的代码很好,但常量可以更清晰(如代码完成中建议的)。

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

我想我应该使用类和多态来尝试一下。我之前的迭代使用了子类化,结果开销太大。我切换到更灵活的委托/公共属性对象模型,这要好得多。我的代码稍微准确一些,我希望我能想出更好的方法来生成“几个月前”,这看起来不太过度设计。

我想我仍然会坚持使用Jeff的if-that级联,因为它的代码更少,更简单(确保它按预期工作肯定更容易)。

对于下面的代码获取时间间隔消息返回相对时间消息(例如“昨天”)。

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

我更喜欢这个版本,因为它的简洁性和添加新刻度点的能力。这可以用时间跨度的Latest()扩展来封装,而不是那个长的1行,但为了简短发布,这就可以了。这修复了一小时前,1小时前,通过提供一个小时直到2小时已经过去

@陈志立

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

无论如何,对DateTime进行减法都会返回TimeSpan

所以你可以

(DateTime.UtcNow - dt).TotalSeconds

我也很惊讶地看到用手乘以常数,然后用乘法添加注释。这是一些误导的优化吗?

当您知道查看器的时区时,以日标度使用日历日可能会更清晰。不幸的是,我不熟悉. NET库,所以我不知道在C#中如何做到这一点。

在消费者网站上,你也可以在不到一分钟的时间内手忙脚乱。“不到一分钟前”或“刚刚”就足够了。

您可以通过在客户端执行此逻辑来减少服务器端负载。在一些Digg页面上查看源代码以供参考。他们让服务器发出一个由Javascript处理的时代时间值。这样您就不需要管理最终用户的时区。新的服务器端代码类似于:

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

您甚至可以在那里添加一个NOSCRIPT块并仅执行一个ToString()。

在PHP中,我是这样做的:

<?phpfunction 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 loopfor ($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";
} ?>

当然,摆脱“1小时前”问题的一个简单解决方案是增加“1小时前”有效的窗口。更改

if (delta < 5400) // 90 * 60{return "an hour ago";}

if (delta < 7200) // 120 * 60{return "an hour ago";}

这意味着110分钟前发生的事情将被解读为“一小时前”——这可能并不完美,但我想说它比“1小时前”的当前情况要好。

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 * 24aValue.Add(2880, () => "a day"); // 60 * 48aValue.Add(43200, () => string.Format("{0} days", Math.Floor(Math.Abs(oSpan.TotalDays)))); // 60 * 24 * 30aValue.Add(86400, () => "about a month"); // 60 * 24 * 60aValue.Add(525600, () => string.Format("{0} months", Math.Floor(Math.Abs(oSpan.TotalDays / 30)))); // 60 * 24 * 365aValue.Add(1051200, () => "about a year"); // 60 * 24 * 365 * 2aValue.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;}

jquery.timeago插件

Jeff,因为Stack Overflow广泛使用jQuery,我推荐jquery.timeago插件

好处:

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

只需将其附加到DOM就绪上的时间戳:

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

这将使所有abbr元素具有一个timeago类和标题中的ISO8601时间戳:

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

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

这是stackoverflow使用的算法,但在带有bug修复(没有“一小时前”)的有害伪代码中重写得更简洁。该函数获取(正)秒数,并返回一个友好的字符串,如“3小时前”或“昨天”。

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

我建议也在客户端计算这个。减少服务器的工作量。

以下是我使用的版本(来自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 centuriesif(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 tojQuery(this).text(date);});};}

以下是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";}}

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

这是我作为扩展方法添加到DateTime类的实现,它处理未来和过去的日期,并提供近似值选项,允许您指定要查找的详细级别(“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();}
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 * 24dict.Add(2880, (mins) => "a day"); // 60 * 48dict.Add(43200, (mins) => string.Format("{0} days", Math.Floor(Math.Abs(mins / 1440)))); // 60 * 24 * 30dict.Add(86400, (mins) => "about a month"); // 60 * 24 * 60dict.Add(525600, (mins) => string.Format("{0} months", Math.Floor(Math.Abs(mins / 43200)))); // 60 * 24 * 365dict.Add(1051200, (mins) => "about a year"); // 60 * 24 * 365 * 2dict.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;}}

这个问题的另一个答案相同,但作为具有静态字典的扩展方法。

使用流利的日期时间

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

客户端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;}
}

我从比尔盖茨的博客中得到了这个答案。我需要在我的浏览器历史记录中找到它,我会给你链接。

执行相同操作的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'; }}

基本上,你以秒为单位工作。

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

派对迟到了几年,但我有要求为过去和未来的日期做这件事,所以我把文森特的2">杰夫和文森特的结合在一起。这是一个三位一体的盛会!:)

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

鉴于这个世界和她的丈夫似乎正在发布代码样本,这是我不久前写的,基于其中的几个答案。

我特别需要这段代码是可本地化的。所以我有两个类——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());}}

我想要实现的关键事情之一,以及本地化,是“今天”只意味着“这个日历日”,所以IsTodayIsThisMonthIsThisYear方法如下所示:

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)

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

我会为此提供一些方便的扩展方法,使代码更具可读性。首先,为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 agodate.Ago(7.Hours()); // 7 hours agodate.Ago(567.Milliseconds()); // 567 milliseconds ago

我认为已经有很多与这篇文章相关的答案,但是可以使用这个很容易使用的插件,也很容易为程序员阅读。发送您的特定日期,并以字符串形式获取其值:

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;}
public string getRelativeDateTime(DateTime date){TimeSpan ts = DateTime.Now - date;if (ts.TotalMinutes < 1)//seconds agoreturn "just now";if (ts.TotalHours < 1)//min agoreturn (int)ts.TotalMinutes == 1 ? "1 Minute ago" : (int)ts.TotalMinutes + " Minutes ago";if (ts.TotalDays < 1)//hours agoreturn (int)ts.TotalHours == 1 ? "1 Hour ago" : (int)ts.TotalHours + " Hours ago";if (ts.TotalDays < 7)//days agoreturn (int)ts.TotalDays == 1 ? "1 Day ago" : (int)ts.TotalDays + " Days ago";if (ts.TotalDays < 30.4368)//weeks agoreturn (int)(ts.TotalDays / 7) == 1 ? "1 Week ago" : (int)(ts.TotalDays / 7) + " Weeks ago";if (ts.TotalDays < 365.242)//months agoreturn (int)(ts.TotalDays / 30.4368) == 1 ? "1 Month ago" : (int)(ts.TotalDays / 30.4368) + " Months ago";//years agoreturn (int)(ts.TotalDays / 365.242) == 1 ? "1 Year ago" : (int)(ts.TotalDays / 365.242) + " Years ago";}

一个月和一年中几天的转换值取自Google。

你可以试试这个。我想它会正常工作的。

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

Nuget上还有一个名为人道主义者的包,它实际上运行得非常好,并且位于. 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在他的博客上有一篇文章

/*** {@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";}}

如果你想要像"2 days, 4 hours and 12 minutes ago"这样的输出,你需要一个时间跨度:

TimeSpan timeDiff = DateTime.Now-CreatedDate;

然后您可以访问您喜欢的值:

timeDiff.DaystimeDiff.Hours

等…

您可以使用TimeAgo扩展如下:

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插件与Timeago的Razor扩展一起使用。

这是我的功能,像魅力一样工作:)

public static string RelativeDate(DateTime theDate){var span = DateTime.Now - theDate;if (span.Days > 365){var years = (span.Days / 365);if (span.Days % 365 != 0)years += 1;return $"about {years} {(years == 1 ? "year" : "years")} ago";}if (span.Days > 30){var months = (span.Days / 30);if (span.Days % 31 != 0)months += 1;return $"about {months} {(months == 1 ? "month" : "months")} ago";}if (span.Days > 0)return $"about {span.Days} {(span.Days == 1 ? "day" : "days")} ago";if (span.Hours > 0)return $"about {span.Hours} {(span.Hours == 1 ? "hour" : "hours")} ago";if (span.Minutes > 0)return $"about {span.Minutes} {(span.Minutes == 1 ? "minute" : "minutes")} ago";if (span.Seconds > 5)return $"about {span.Seconds} seconds ago";
return span.Seconds <= 5 ? "about 5 seconds ago" : string.Empty;}

我的方法简单多了你可以随意调整返回字符串

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

土耳其本地化版本的Vincents答案。

    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 + " saniye önce";
if (delta < 45 * MINUTE)return ts.Minutes + " dakika önce";
if (delta < 24 * HOUR)return ts.Hours + " saat önce";
if (delta < 48 * HOUR)return "dün";
if (delta < 30 * DAY)return ts.Days + " gün önce";
if (delta < 12 * MONTH){int months = Convert.ToInt32(Math.Floor((double)ts.Days / 30));return months + " ay önce";}else{int years = Convert.ToInt32(Math.Floor((double)ts.Days / 365));return years + " yıl önce";}
// Calculate total days in current yearint daysInYear;
for (var i = 1; i <= 12; i++)daysInYear += DateTime.DaysInMonth(DateTime.Now.Year, i);
// Past dateDateTime dateToCompare = DateTime.Now.Subtract(TimeSpan.FromMinutes(582));
// Calculate difference between current date and past datedouble diff = (DateTime.Now - dateToCompare).TotalMilliseconds;
TimeSpan ts = TimeSpan.FromMilliseconds(diff);
var years = ts.TotalDays / daysInYear; // Yearsvar months = ts.TotalDays / (daysInYear / (double)12); // Monthsvar weeks = ts.TotalDays / 7; // Weeksvar days = ts.TotalDays; // Daysvar hours = ts.TotalHours; // Hoursvar minutes = ts.TotalMinutes; // Minutesvar seconds = ts.TotalSeconds; // Seconds
if (years >= 1)Console.WriteLine(Math.Round(years, 0) + " year(s) ago");else if (months >= 1)Console.WriteLine(Math.Round(months, 0) + " month(s) ago");else if (weeks >= 1)Console.WriteLine(Math.Round(weeks, 0) + " week(s) ago");else if (days >= 1)Console.WriteLine(Math.Round(days, 0) + " days(s) ago");else if (hours >= 1)Console.WriteLine(Math.Round(hours, 0) + " hour(s) ago");else if (minutes >= 1)Console.WriteLine(Math.Round(minutes, 0) + " minute(s) ago");else if (seconds >= 1)Console.WriteLine(Math.Round(seconds, 0) + " second(s) ago");
Console.ReadLine();

在某种程度上,你用DateTime函数计算相对时间,从几秒到几年,尝试这样的方法:

using System;
public class Program {public static string getRelativeTime(DateTime past) {DateTime now = DateTime.Today;string rt = "";int time;string statement = "";if (past.Second >= now.Second) {if (past.Second - now.Second == 1) {rt = "second ago";}rt = "seconds ago";time = past.Second - now.Second;statement = "" + time;return (statement + rt);}if (past.Minute >= now.Minute) {if (past.Second - now.Second == 1) {rt = "second ago";} else {rt = "minutes ago";}time = past.Minute - now.Minute;statement = "" + time;return (statement + rt);}// This process will go on until years}public static void Main() {DateTime before = new DateTime(1995, 8, 24);string date = getRelativeTime(before);Console.WriteLine("Windows 95 was {0}.", date);}}

不完全工作,但如果你稍微修改和调试一下,它可能会完成这项工作。

使用解构和Linq得到“n[最大时间单位]前”的“单线”:

TimeSpan timeSpan = DateTime.Now - new DateTime(1234, 5, 6, 7, 8, 9);
(string unit, int value) = new Dictionary<string, int>\{\{"year(s)", (int)(timeSpan.TotalDays / 365.25)}, //https://en.wikipedia.org/wiki/Year#Intercalation{"month(s)", (int)(timeSpan.TotalDays / 29.53)}, //https://en.wikipedia.org/wiki/Month{"day(s)", (int)timeSpan.TotalDays},{"hour(s)", (int)timeSpan.TotalHours},{"minute(s)", (int)timeSpan.TotalMinutes},{"second(s)", (int)timeSpan.TotalSeconds},{"millisecond(s)", (int)timeSpan.TotalMilliseconds}}.First(kvp => kvp.Value > 0);
Console.WriteLine($"{value} {unit} ago");

你得到786 year(s) ago

与当前年份和月份一样

TimeSpan timeSpan = DateTime.Now - new DateTime(2020, 12, 6, 7, 8, 9);

你得到4 day(s) ago

实际的日期,比如

TimeSpan timeSpan = DateTime.Now - DateTime.Now.Date;

你得到9 hour(s) ago

简单和100%的工作解决方案。

处理以前和未来的时间,以及…以防万一

        public string GetTimeSince(DateTime postDate){string message = "";DateTime currentDate = DateTime.Now;TimeSpan timegap = currentDate - postDate;
     
if (timegap.Days > 365){message = string.Format(L("Ago") + " {0} " + L("Years"), (((timegap.Days) / 30) / 12));}else if (timegap.Days > 30){message = string.Format(L("Ago") + " {0} " + L("Months"), timegap.Days/30);}else if (timegap.Days > 0){message = string.Format(L("Ago") + " {0} " + L("Days"), timegap.Days);}else if (timegap.Hours > 0){message = string.Format(L("Ago") + " {0} " + L("Hours"), timegap.Hours);}else if (timegap.Minutes > 0){message = string.Format(L("Ago") + " {0} " + L("Minutes"), timegap.Minutes);}else if (timegap.Seconds > 0){message = string.Format(L("Ago") + " {0} " + L("Seconds"), timegap.Seconds);}
// let's handle future times..just in caseelse if (timegap.Days < -365){message = string.Format(L("In") + " {0} " + L("Years"), (((Math.Abs(timegap.Days)) / 30) / 12));}else if (timegap.Days < -30){message = string.Format(L("In") + " {0} " + L("Months"), ((Math.Abs(timegap.Days)) / 30));}else if (timegap.Days < 0){message = string.Format(L("In") + " {0} " + L("Days"), Math.Abs(timegap.Days));}      
else if (timegap.Hours < 0){message = string.Format(L("In") + " {0} " + L("Hours"), Math.Abs(timegap.Hours));}else if (timegap.Minutes < 0){message = string.Format(L("In") + " {0} " + L("Minutes"), Math.Abs(timegap.Minutes));}else if (timegap.Seconds < 0){message = string.Format(L("In") + " {0} " + L("Seconds"), Math.Abs(timegap.Seconds));}

else{message = "a bit";}
return message;}

文森特接受的答案做出了相当多的武断决定。为什么45分钟舍入为1小时,而45秒不舍入为1分钟?它在年和月计算中具有更高的圈复杂度,这使得遵循逻辑变得更加复杂。它假设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){// ArrangeTimeSpan timeSpan = new(days, hours, minutes, seconds);
// Actstring result = timeSpan.ToNaturalLanguage();
// AssertAssert.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){// ArrangeTimeSpan timeSpan = new(days, hours, minutes, seconds);DateTime now = DateTime.Now;DateTime dateTime = now + timeSpan;
// Actstring result = dateTime.ToNaturalLanguage();
// AssertAssert.That(result, Is.EqualTo(expected));}

或者作为一个要点:https://gist.github.com/StudioLE/2dd394e3f792e79adc927ede274df56e