关于日期时间:C#计算相对时间

Calculate relative time in C#

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

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


Jeff,您的代码很好,但是可以用常量更清晰(如代码完整部分中建议的那样)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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会自动刷新。
  • 您可以充分利用Web应用程序中的页面和/或片段缓存,因为时间戳不是在服务器上计算的。
  • 你可以像酷孩子一样使用微格式。

只需将它附加到DOM上的时间戳上即可:

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

这将使所有abbr元素在标题中具有timeag类和iso 8601时间戳:

1
July 17, 2008</abbr>

变成这样:

1
4 months ago</abbr>

收益率:4个月前。随着时间的推移,时间戳将自动更新。

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


我就是这么做的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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";

建议?评论?如何改进这个算法?


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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小时前,1小时前,提供了一个小时到2小时


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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";
    }
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static string ToRelativeDate(DateTime input)
{
    TimeSpan oSpan = DateTime.Now.Subtract(input);
    double TotalMinutes = oSpan.TotalMinutes;
    string Suffix =" ago";

    if (TotalMinutes < 0.0)
    {
        TotalMinutes = Math.Abs(TotalMinutes);
        Suffix =" from now";
    }

    var aValue = new SortedList<double, Func<string>>();
    aValue.Add(0.75, () =>"less than a minute");
    aValue.Add(1.5, () =>"about a minute");
    aValue.Add(45, () => string.Format("{0} minutes", Math.Round(TotalMinutes)));
    aValue.Add(90, () =>"about an hour");
    aValue.Add(1440, () => string.Format("about {0} hours", Math.Round(Math.Abs(oSpan.TotalHours)))); // 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版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
static readonly sortedlist<double,func<timespan,string>>offsets=new sortedlist<double,func<timespan,string>>{_0.75,=>"不到一分钟<div class="suo-content">[collapse title=""]<ul><li>这是非常好的:)这是国际海事组织的重构,可以为扩展方法?词典等是对静态的只读一次,然后从后创建和引用?</li><li>纯。krome:stackoverflow.com /问题/我如何计算相对的T - zwnj &;& # 8203;IME / & hellip;</li><li>你可能要拉出来一个一场这样的字典,你churn减少instantiation和GC。你要到<wyn>Func<double></wyn><wyn>Func<string></wyn>相变。</li><li>在JavaScript中drzaus emuzf jsfiddle.net / /</li></ul>[/collapse]</div><hr><P>下面是我添加的一个实现,作为datetime类的扩展方法,它处理将来和过去的日期,并提供一个近似选项,允许您指定要查找的详细级别("3小时前"与"3小时、23分钟、12秒前"):</P>[cc lang="csharp"]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();
}

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

以下是我使用的版本(来自Zach Leatherman)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/*
 * 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);
                });
        };
}


在Nuget上还有一个叫做Humanizer的包,它实际上工作得很好。

1
2
3
4
5
6
7
8
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"

斯科特·汉塞尔曼在他的博客上写了一篇文章


@杰夫

我看你的好像有点长。然而,在"昨天"和"几年"的支持下,它确实显得更加强大。但在我的经验中,当这个被使用的时候,这个人最有可能在前30天查看内容。只有那些真正的铁杆人物才会在那之后出现。所以这就是我通常选择保持简短和简单的原因。

这是我目前在一个网站上使用的方法。这只返回相对的天、小时和时间。然后用户必须在输出中加上"ago"。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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();
}

参加聚会晚了几年,但我有一个要求,在过去和将来都要这样做,所以我把杰夫和文森特的结合在一起。这是一场三重口味的盛会!:)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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";
            }
        }
    }

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

下面是我的快速和肮脏的Java解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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 Objective-C版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
+ (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];
    }
}

考虑到全世界和她丈夫似乎都在发布代码示例,下面是我不久前根据这些答案写的。

我有一个特定的需求,这个代码是可本地化的。所以我有两个类——Grammar,它指定了可本地化的术语,和FuzzyDateExtensions,它拥有一系列的扩展方法。我不需要处理将来的日期时间,所以没有尝试用这个代码来处理它们。

我在源代码中保留了一些xmldoc,但为了简洁起见删除了大部分(在它们很明显的地方)。我也不包括所有的班级成员:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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类包含:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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方法如下:

1
2
3
4
public static bool IsToday(this DateTime date)
{
    return date.DayOfYear == DateTime.Now.DayOfYear && date.IsThisYear();
}

四舍五入的方法是这样的(我已经包括了RoundedMonths,因为这有点不同):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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日期时间

1
2
3
4
5
6
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();

在PHP中,我这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?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";

} ?>


我想我可以用类和多态性来尝试一下。我以前的迭代使用了子类,结果开销太大。我已经切换到更灵活的委托/公共属性对象模型,这明显更好。我的代码稍微准确一点,我希望我能想出一个更好的方法来生成"几个月前"的代码,它看起来并没有设计得太多。

我想我还是会坚持Jeff的if-then cascade,因为它的代码更少,而且更简单(确保它按预期工作肯定更容易)。

对于下面的代码printRelativeTime.getRelativeTimeMessage(TimeSpan Ago)返回相对时间消息(例如"昨天")。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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);
    }
}

当你知道观看者的时区时,以天为单位使用日历日可能会更清晰。我不熟悉.NET库,所以我不知道您在C中是如何做到这一点的,不幸的是。

在消费类网站上,你也可以在一分钟内手摇成波浪形。"不到一分钟前"或刚刚"就足够了。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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";
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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;
    }
}

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


@杰夫

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

DateTime执行减法,无论如何返回TimeSpan

所以你可以这么做

1
(DateTime.UtcNow - dt).TotalSeconds

我还惊讶地看到常量手工相乘,然后在注释中添加了乘法。那是不是有些错误的优化?


用于客户端GWT使用的Java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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;
 }

}


您可以使用TimeAgo扩展名,其外观如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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扩展。


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

1
2
3
4
public string GetRelativeTime(DateTime timeStamp)
{
    return string.Format("printdate({0});", timeStamp.ToFileTimeUtc());
}

甚至可以在那里添加noscript块并执行toString()。


下面是StackOverflow使用的算法,但使用错误修复(不是"一小时前")在Perlish伪代码中更简洁地重写。函数在几秒钟前(正数)返回一个对人友好的字符串,如"3小时前"或"昨天"。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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";

这是我从比尔·盖茨的博客上得到的。我需要在我的浏览器历史上找到它,我会给你链接。

要执行相同操作的javascript代码(按要求):

1
2
3
4
5
6
7
8
9
10
11
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'; }
}

基本上,你是以秒为单位工作的…


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * {@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"这样的输出,则需要一个时间跨度:

1
TimeSpan timeDiff = DateTime.Now-CreatedDate;

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

1
2
timeDiff.Days
timeDiff.Hours

等。。。


我想已经有很多关于这篇文章的答案了,但是我们可以使用这个,它很容易使用,就像插件一样,也很容易被程序员阅读。发送您的特定日期,并以字符串形式获取其值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public string getRelativeDateTime(DateTime date)
{
    TimeSpan ts = DateTime.Now - date;
    if (ts.TotalMinutes < 1)//seconds ago
        return"just now";
    if (ts.TotalHours < 1)//min ago
        return (int)ts.TotalMinutes == 1 ?"1 Minute ago" : (int)ts.TotalMinutes +" Minutes ago";
    if (ts.TotalDays < 1)//hours ago
        return (int)ts.TotalHours == 1 ?"1 Hour ago" : (int)ts.TotalHours +" Hours ago";
    if (ts.TotalDays < 7)//days ago
        return (int)ts.TotalDays == 1 ?"1 Day ago" : (int)ts.TotalDays +" Days ago";
    if (ts.TotalDays < 30.4368)//weeks ago
        return (int)(ts.TotalDays / 7) == 1 ?"1 Week ago" : (int)(ts.TotalDays / 7) +" Weeks ago";
    if (ts.TotalDays < 365.242)//months ago
        return (int)(ts.TotalDays / 30.4368) == 1 ?"1 Month ago" : (int)(ts.TotalDays / 30.4368) +" Months ago";
    //years ago
    return (int)(ts.TotalDays / 365.242) == 1 ?"1 Year ago" : (int)(ts.TotalDays / 365.242) +" Years ago";
}

一个月和一年中的天数转换值取自谷歌。


我将为此提供一些方便的扩展方法,并使代码更具可读性。首先,为Int32提供了两种扩展方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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

1
2
3
4
5
6
7
public static class DateTimeExtensions {

    public static DateTime Ago(this DateTime dateTime, TimeSpan delta) {

        return dateTime - delta;
    }
}

现在,您可以执行如下操作:

1
2
3
4
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

当然,解决"1小时前"问题的一个简单方法是增加"1小时前"的有效窗口。变化

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

进入之内

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

这意味着110分钟前发生的事情将被解读为"一小时前"——这可能并不完美,但我认为这比"1小时前"的现状要好。


这是我的职责,就像一个魅力:)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static string 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;
        }

文森特答案的土耳其语本地化版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    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";
    }

我的方法要简单得多。您可以根据需要调整返回字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    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;
    }