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

2小时前3天前一个月前


当前回答

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

其他回答

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

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

iPhone Objective-C版本

+ (NSString *)timeAgoString:(NSDate *)date {
    int delta = -(int)[date timeIntervalSinceNow];

    if (delta < 60)
    {
        return delta == 1 ? @"one second ago" : [NSString stringWithFormat:@"%i seconds ago", delta];
    }
    if (delta < 120)
    {
        return @"a minute ago";
    }
    if (delta < 2700)
    {
        return [NSString stringWithFormat:@"%i minutes ago", delta/60];
    }
    if (delta < 5400)
    {
        return @"an hour ago";
    }
    if (delta < 24 * 3600)
    {
        return [NSString stringWithFormat:@"%i hours ago", delta/3600];
    }
    if (delta < 48 * 3600)
    {
        return @"yesterday";
    }
    if (delta < 30 * 24 * 3600)
    {
        return [NSString stringWithFormat:@"%i days ago", delta/(24*3600)];
    }
    if (delta < 12 * 30 * 24 * 3600)
    {
        int months = delta/(30*24*3600);
        return months <= 1 ? @"one month ago" : [NSString stringWithFormat:@"%i months ago", months];
    }
    else
    {
        int years = delta/(12*30*24*3600);
        return years <= 1 ? @"one year ago" : [NSString stringWithFormat:@"%i years ago", years];
    }
}

文森特接受的答案做出了许多武断的决定。为什么45分钟舍入为一小时,而45秒不舍入为一分钟?在年和月的计算中,它的圈复杂度增加了,这使得遵循逻辑变得更加复杂。它假设TimeSpan是相对于过去(2天前)的,而它很可能是在未来(2天后)。它定义了不必要的常量,而不是使用TimeSpan.TicksPerSecond等。

此实现解决了上述问题,并更新了语法以使用开关表达式和关系模式

/// <summary>
/// Convert a <see cref="TimeSpan"/> to a natural language representation.
/// </summary>
/// <example>
/// <code>
/// TimeSpan.FromSeconds(10).ToNaturalLanguage();
/// // 10 seconds
/// </code>
/// </example>
public static string ToNaturalLanguage(this TimeSpan @this)
{
    const int daysInWeek = 7;
    const int daysInMonth = 30;
    const int daysInYear = 365;
    const long threshold = 100 * TimeSpan.TicksPerMillisecond;
    @this = @this.TotalSeconds < 0
        ? TimeSpan.FromSeconds(@this.TotalSeconds * -1)
        : @this;
    return (@this.Ticks + threshold) switch
    {
        < 2 * TimeSpan.TicksPerSecond => "a second",
        < 1 * TimeSpan.TicksPerMinute => @this.Seconds + " seconds",
        < 2 * TimeSpan.TicksPerMinute => "a minute",
        < 1 * TimeSpan.TicksPerHour => @this.Minutes + " minutes",
        < 2 * TimeSpan.TicksPerHour => "an hour",
        < 1 * TimeSpan.TicksPerDay => @this.Hours + " hours",
        < 2 * TimeSpan.TicksPerDay => "a day",
        < 1 * daysInWeek * TimeSpan.TicksPerDay => @this.Days + " days",
        < 2 * daysInWeek * TimeSpan.TicksPerDay => "a week",
        < 1 * daysInMonth * TimeSpan.TicksPerDay => (@this.Days / daysInWeek).ToString("F0") + " weeks",
        < 2 * daysInMonth * TimeSpan.TicksPerDay => "a month",
        < 1 * daysInYear * TimeSpan.TicksPerDay => (@this.Days / daysInMonth).ToString("F0") + " months",
        < 2 * daysInYear * TimeSpan.TicksPerDay => "a year",
        _ => (@this.Days / daysInYear).ToString("F0") + " years"
    };
}

/// <summary>
/// Convert a <see cref="DateTime"/> to a natural language representation.
/// </summary>
/// <example>
/// <code>
/// (DateTime.Now - TimeSpan.FromSeconds(10)).ToNaturalLanguage()
/// // 10 seconds ago
/// </code>
/// </example>
public static string ToNaturalLanguage(this DateTime @this)
{
    TimeSpan timeSpan = @this - DateTime.Now;
    return timeSpan.TotalSeconds switch
    {
        >= 1 => timeSpan.ToNaturalLanguage() + " until",
        <= -1 => timeSpan.ToNaturalLanguage() + " ago",
        _ => "now",
    };
}

可以使用NUnit对其进行如下测试:

[TestCase("a second", 0)]
[TestCase("a second", 1)]
[TestCase("2 seconds", 2)]
[TestCase("a minute", 0, 1)]
[TestCase("5 minutes", 0, 5)]
[TestCase("an hour", 0, 0, 1)]
[TestCase("2 hours", 0, 0, 2)]
[TestCase("a day", 0, 0, 24)]
[TestCase("a day", 0, 0, 0, 1)]
[TestCase("6 days", 0, 0, 0, 6)]
[TestCase("a week", 0, 0, 0, 7)]
[TestCase("4 weeks", 0, 0, 0, 29)]
[TestCase("a month", 0, 0, 0, 30)]
[TestCase("6 months", 0, 0, 0, 6 * 30)]
[TestCase("a year", 0, 0, 0, 365)]
[TestCase("68 years", int.MaxValue)]
public void NaturalLanguageHelpers_TimeSpan(
    string expected,
    int seconds,
    int minutes = 0,
    int hours = 0,
    int days = 0
)
{
    // Arrange
    TimeSpan timeSpan = new(days, hours, minutes, seconds);

    // Act
    string result = timeSpan.ToNaturalLanguage();

    // Assert
    Assert.That(result, Is.EqualTo(expected));
}

[TestCase("now", 0)]
[TestCase("10 minutes ago", 0, -10)]
[TestCase("10 minutes until", 10, 10)]
[TestCase("68 years until", int.MaxValue)]
[TestCase("68 years ago", int.MinValue)]
public void NaturalLanguageHelpers_DateTime(
    string expected,
    int seconds,
    int minutes = 0,
    int hours = 0,
    int days = 0
)
{
    // Arrange
    TimeSpan timeSpan = new(days, hours, minutes, seconds);
    DateTime now = DateTime.Now;
    DateTime dateTime = now + timeSpan;

    // Act
    string result = dateTime.ToNaturalLanguage();

    // Assert
    Assert.That(result, Is.EqualTo(expected));
}

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

当您知道查看者的时区时,以日为单位使用日历日可能会更清晰。我不熟悉.NET库,所以我不知道如何在C#中实现这一点。

在消费者网站上,你也可以在一分钟内用手洗。“不到一分钟前”或“刚刚”就足够了。