Calculate relative time in C#

后端 未结 30 2131
生来不讨喜
生来不讨喜 2020-11-21 05:59

Given a specific DateTime value, how do I display relative time, like:

  • 2 hours ago
  • 3 days ago
  • a month ago
30条回答
  •  孤独总比滥情好
    2020-11-21 06:24

    Given the world and her husband appear to be posting code samples, here is what I wrote a while ago, based on a couple of these answers.

    I had a specific need for this code to be localisable. So I have two classes — Grammar, which specifies the localisable terms, and FuzzyDateExtensions, which holds a bunch of extension methods. I had no need to deal with future datetimes, so no attempt is made to handle them with this code.

    I've left some of the XMLdoc in the source, but removed most (where they'd be obvious) for brevity's sake. I've also not included every class member here:

    public class Grammar
    {
        ///  Gets or sets the term for "just now". 
        public string JustNow { get; set; }
        ///  Gets or sets the term for "X minutes ago". 
        /// 
        ///     This is a  pattern, where {0}
        ///     is the number of minutes.
        /// 
        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; }
        ///  Gets or sets the term for "ages ago". 
        public string AgesAgo { get; set; }
    
        /// 
        ///     Gets or sets the threshold beyond which the fuzzy date should be
        ///     considered "ages ago".
        /// 
        public TimeSpan AgesAgoThreshold { get; set; }
    
        /// 
        ///     Initialises a new  instance with the
        ///     specified properties.
        /// 
        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)
        { ... }
    }
    

    The FuzzyDateString class contains:

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

    One of the key things I wanted to achieve, as well as localisation, was that "today" would only mean "this calendar day", so the IsToday, IsThisMonth, IsThisYear methods look like this:

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

    and the rounding methods are like this (I've included RoundedMonths, as that's a bit different):

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

    I hope people find this useful and/or interesting :o)

提交回复
热议问题