Вычислить относительное время в C#

Учитывая определенное DateTimeзначение, как я могу отобразить относительное время, например:

  • 2 часа назад
  • 3 дня назад
  • месяц назад

30 ответов

  1. Вот как я это делаю

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

    Предложения? Комментарии? Как улучшить этот алгоритм?

  2. 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()расширением Timespan вместо этого длинного 1 лайнера, но ради краткости в публикации это будет делать.
    Это исправляет час назад, 1 час назад, обеспечивая час до 2 часов прошло

  3. Вот переписать из Jeffs скрипт для 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";
        }
    }    
    
  4. public static string ToRelativeDate(DateTime input)
    {
        TimeSpan oSpan = DateTime.Now.Subtract(input);
        double TotalMinutes = oSpan.TotalMinutes;
        string Suffix = " ago";
    
        if (TotalMinutes < 0.0)
        {
            TotalMinutes = Math.Abs(TotalMinutes);
            Suffix = " from now";
        }
    
        var aValue = new SortedList<double, Func<string>>();
        aValue.Add(0.75, () => "less than a minute");
        aValue.Add(1.5, () => "about a minute");
        aValue.Add(45, () => string.Format("{0} minutes", Math.Round(TotalMinutes)));
        aValue.Add(90, () => "about an hour");
        aValue.Add(1440, () => string.Format("about {0} hours", Math.Round(Math.Abs(oSpan.TotalHours)))); // 60 * 24
        aValue.Add(2880, () => "a day"); // 60 * 48
        aValue.Add(43200, () => string.Format("{0} days", Math.Floor(Math.Abs(oSpan.TotalDays)))); // 60 * 24 * 30
        aValue.Add(86400, () => "about a month"); // 60 * 24 * 60
        aValue.Add(525600, () => string.Format("{0} months", Math.Floor(Math.Abs(oSpan.TotalDays / 30)))); // 60 * 24 * 365 
        aValue.Add(1051200, () => "about a year"); // 60 * 24 * 365 * 2
        aValue.Add(double.MaxValue, () => string.Format("{0} years", Math.Floor(Math.Abs(oSpan.TotalDays / 365))));
    
        return aValue.First(n => TotalMinutes < n.Key).Value.Invoke() + Suffix;
    }
    

    http://refactormycode.com/codes/493-twitter-esque-relative-dates

    Версия c# 6:

    static readonly SortedList<double, Func<TimeSpan, string>> offsets = 
       new SortedList<double, Func<TimeSpan, string>>
    {
        { 0.75, _ => "less than a minute"},
        { 1.5, _ => "about a minute"},
        { 45, x => $"{x.TotalMinutes:F0} minutes"},
        { 90, x => "about an hour"},
        { 1440, x => $"about {x.TotalHours:F0} hours"},
        { 2880, x => "a day"},
        { 43200, x => $"{x.TotalDays:F0} days"},
        { 86400, x => "about a month"},
        { 525600, x => $"{x.TotalDays / 30:F0} months"},
        { 1051200, x => "about a year"},
        { double.MaxValue, x => $"{x.TotalDays / 365:F0} years"}
    };
    
    public static string ToRelativeDate(this DateTime input)
    {
        TimeSpan x = DateTime.Now - input;
        string Suffix = x.TotalMinutes > 0 ? " ago" : " from now";
        x = new TimeSpan(Math.Abs(x.Ticks));
        return offsets.First(n => x.TotalMinutes < n.Key).Value(x) + Suffix;
    }
    
  5. Вот реализация, которую я добавил в качестве метода расширения в класс DateTime, который обрабатывает как будущие, так и прошлые даты и предоставляет опцию аппроксимации, которая позволяет указать уровень детализации, который вы ищете («3 часа назад «против» 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();
    }
    
  6. Я бы рекомендовал вычислить это на стороне клиента. Меньше работы для сервера.

    Ниже приводится версия, которую я использую (от Зака Leatherman)

    /*
     * Javascript Humane Dates
     * Copyright (c) 2008 Dean Landolt (deanlandolt.com)
     * Re-write by Zach Leatherman (zachleat.com)
     * 
     * Adopted from the John Resig's pretty.js
     * at http://ejohn.org/blog/javascript-pretty-date
     * and henrah's proposed modification 
     * at http://ejohn.org/blog/javascript-pretty-date/#comment-297458
     * 
     * Licensed under the MIT license.
     */
    
    function humane_date(date_str){
            var time_formats = [
                    [60, 'just now'],
                    [90, '1 minute'], // 60*1.5
                    [3600, 'minutes', 60], // 60*60, 60
                    [5400, '1 hour'], // 60*60*1.5
                    [86400, 'hours', 3600], // 60*60*24, 60*60
                    [129600, '1 day'], // 60*60*24*1.5
                    [604800, 'days', 86400], // 60*60*24*7, 60*60*24
                    [907200, '1 week'], // 60*60*24*7*1.5
                    [2628000, 'weeks', 604800], // 60*60*24*(365/12), 60*60*24*7
                    [3942000, '1 month'], // 60*60*24*(365/12)*1.5
                    [31536000, 'months', 2628000], // 60*60*24*365, 60*60*24*(365/12)
                    [47304000, '1 year'], // 60*60*24*365*1.5
                    [3153600000, 'years', 31536000], // 60*60*24*365*100, 60*60*24*365
                    [4730400000, '1 century'] // 60*60*24*365*100*1.5
            ];
    
            var time = ('' + date_str).replace(/-/g,"/").replace(/[TZ]/g," "),
                    dt = new Date,
                    seconds = ((dt - new Date(time) + (dt.getTimezoneOffset() * 60000)) / 1000),
                    token = ' ago',
                    i = 0,
                    format;
    
            if (seconds < 0) {
                    seconds = Math.abs(seconds);
                    token = '';
            }
    
            while (format = time_formats[i++]) {
                    if (seconds < format[0]) {
                            if (format.length == 2) {
                                    return format[1] + (i > 1 ? token : ''); // Conditional so we don't return Just Now Ago
                            } else {
                                    return Math.round(seconds / format[2]) + ' ' + format[1] + (i > 1 ? token : '');
                            }
                    }
            }
    
            // overflow for centuries
            if(seconds > 4730400000)
                    return Math.round(seconds / 4730400000) + ' centuries' + token;
    
            return date_str;
    };
    
    if(typeof jQuery != 'undefined') {
            jQuery.fn.humane_dates = function(){
                    return this.each(function(){
                            var date = humane_date(this.title);
                            if(date && jQuery(this).text() != date) // don't modify the dom if we don't have to
                                    jQuery(this).text(date);
                    });
            };
    }
    
  7. Есть также пакет под названием Humanizer на Nuget, и он на самом деле работает очень хорошо

    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"
    

    Скотт Ханзельман написал об этом в своем блоге

  8. @Джефф

    ИМХО твое кажется немного длинным. Однако это кажется немного более надежным с поддержкой «вчера»и » лет». Но по моему опыту, когда это используется, человек, скорее всего, будет просматривать контент в первые 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();
    }
    
  9. Несколько лет опаздывал на вечеринку, но у меня было требование сделать это для прошлых и будущих дат, поэтому я объединил Джеффа и Винсента в этом. Это тернаритастическая феерия!

    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";
                }
            }
        }
    
  10. Есть ли простой способ сделать это на 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 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;
    }
    
  11. 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];
        }
    }
    
  12. Учитывая, что мир и ее муж, похоже, публикуют образцы кода, вот что я написал некоторое время назад, основываясь на нескольких этих ответах.

    У меня была особая потребность в том, чтобы этот код был локальным. Так что у меня есть два класса —Grammar, который определяет localisable термины, и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());
        }
    }
    

    Одной из ключевых вещей, которых я хотел достичь, а также локализации, было то, что «сегодня» будет означать только «этот календарный день», поэтомуIsTodayIsThisMonth, IsThisYearметоды выглядят так:

    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)

  13. использование Fluent DateTime

    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();
    
  14. В PHP я делаю это так:

    <?php
    function timesince($original) {
        // array of time period chunks
        $chunks = array(
            array(60 * 60 * 24 * 365 , 'year'),
            array(60 * 60 * 24 * 30 , 'month'),
            array(60 * 60 * 24 * 7, 'week'),
            array(60 * 60 * 24 , 'day'),
            array(60 * 60 , 'hour'),
            array(60 , 'minute'),
        );
    
        $today = time(); /* Current unix time  */
        $since = $today - $original;
    
        if($since > 604800) {
        $print = date("M jS", $original);
    
        if($since > 31536000) {
            $print .= ", " . date("Y", $original);
        }
    
        return $print;
    }
    
    // $j saves performing the count function each time around the loop
    for ($i = 0, $j = count($chunks); $i < $j; $i++) {
    
        $seconds = $chunks[$i][0];
        $name = $chunks[$i][1];
    
        // finding the biggest chunk (if the chunk fits, break)
        if (($count = floor($since / $seconds)) != 0) {
            break;
        }
    }
    
    $print = ($count == 1) ? '1 '.$name : "$count {$name}s";
    
    return $print . " ago";
    
    } ?>
    
  15. Я думал, что сделаю это, используя классы и полиморфизм. У меня была предыдущая итерация, которая использовала подкласс, который в конечном итоге слишком много накладных расходов. Я переключился на более гибкую объектную модель делегата / публичного свойства, которая значительно лучше. Мой код немного более точен, я хотел бы придумать лучший способ генерировать «месяцы назад», который не казался слишком перегруженным.

    Я думаю, что я все еще буду придерживаться Джеффа if-then cascade, потому что это меньше кода, и это проще (определенно легче гарантировать, что он будет работать так, как ожидалось).

    Для приведенного ниже кода PrintRelativeTime.GetRelativeTimeMessage (TimeSpan назад) возвращает сообщение об относительном времени (например, «вчера»).

    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);
        }
    }
    
  16. Когда вы знаете часовой пояс зрителя, было бы лучше использовать календарные дни в дневном масштабе. Я не знаком с библиотеками .NET, поэтому я не знаю, как вы бы сделали это на C#, к сожалению.

    На потребительских сайтах вы также можете быть более волнистыми в течение минуты. «Меньше минуты назад» или «только сейчас» может быть достаточно хорошо.

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

    То же самое, что и другой ответ на этот вопрос, но как метод расширения со статическим словарем.

  18. вы можете попробовать это.Я думаю, что это будет работать правильно.

    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";
    }
    
  19. @Джефф

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

    Выполнение вычитания на DateTimeвозвращает a TimeSpanв любом случае.

    Так вы можете как раз сделать

    (DateTime.UtcNow - dt).TotalSeconds
    

    Я также удивлен, увидев, что константы размножаются вручную, а затем комментарии добавляются с умножениями. Это была какая-то ошибочная оптимизация?

  20. Java для использования на стороне клиента gwt:

    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;
     }
    
    }
    
  21. Вот алгоритм stackoverflow использует, но более сжато переписан в perlish pseudocode с исправлением ошибки (нет»часов назад»). Функция принимает (положительное) количество секунд назад и возвращает дружественную человеку строку типа «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";
    
  22. Вы можете использовать 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 с расширением Razor от Timeago.

  23. Можно уменьшить нагрузку на сервер, выполнив эту логику на стороне клиента. Просмотр источника на некоторых страницах Digg для справки. У них есть сервер, выдающий значение времени epoch, которое обрабатывается Javascript. Таким образом, вам не нужно управлять часовым поясом конечного пользователя. Новый код на стороне сервера будет чем-то вроде:

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

    Вы даже можете добавить там блок NOSCRIPT и просто выполнить ToString().

  24. Это я получил из одного из блогов Билла Гейтса. Мне нужно найти его в моей истории браузера, и я дам вам ссылку.

    Код 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'; }
    }
    

    В принципе, вы работаете в секундах.

  25. /** 
     * {@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";
        }
    }
    
  26. Я думаю, что уже есть ряд ответов, связанных с этим сообщением, но можно использовать это, которое легко использовать так же, как плагин, а также легко читается для программистов.
    Отправьте конкретную дату и получите ее значение в виде строки:

    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;
    }
    
  27. Если вы хотите иметь выходные данные, как"2 days, 4 hours and 12 minutes ago", вам нужно timespan:

    TimeSpan timeDiff = DateTime.Now-CreatedDate;
    

    Затем вы можете получить доступ к значениям, которые вам нравятся:

    timeDiff.Days
    timeDiff.Hours
    

    так далее.

  28. jquery.плагин timeago

    Джефф, поскольку Stack Overflow широко использует jQuery, я рекомендую jquery.плагин timeago .

    Преимущества:

    • Избегайте отметок времени, датированных «1 минута назад», даже если страница была открыта 10 минут назад; timeago обновляется автоматически.
    • Вы можете в полной мере использовать кэширование страниц и/или фрагментов в веб-приложениях, так как временные метки не рассчитываются на сервере.
    • Вы можете использовать микроформаты, как крутые дети.

    Просто прикрепите его к вашим меткам времени на DOM ready:

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

    Это превратит все abbrэлементы с классом timeago и меткой времени ISO 8601 в заголовке:

    <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 месяца назад. По прошествии времени метки времени будут автоматически обновляться.

    Отказ от ответственности: я написал этот плагин, поэтому я предвзят.

  29. Джефф, ваш код хороший, но может быть более ясным с константами (как предлагается в Code Complete).

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