Oblicz czas względny w C #


1513

Biorąc pod uwagę określoną DateTimewartość, jak wyświetlić czas względny, na przykład:

  • 2 godziny temu
  • 3 dni temu
  • miesiąc temu

80
Co jeśli chcesz obliczyć względny czas od teraz do przyszłości?
Jhonny D. Cano-Lewica-

2
moment.js to bardzo fajna biblioteka parsująca daty. Możesz rozważyć użycie tej strony (po stronie serwera lub po stronie klienta), w zależności od potrzeb. po prostu fyi, bo nikt o tym nie wspominał
Matej

1
Istnieje pakiet .net github.com/NickStrupat/TimeAgo, który właściwie spełnia to, o co się pyta.
Rossco,

Ten projekt jest dość elegancki w zakresie formatowania daty github.com/Humanizr/Humanizer#humanize-datetime
Aaron Hudon

Odpowiedzi:


988

Jeff, twój kod jest fajny, ale może być jaśniejszy dzięki stałym (jak zasugerowano w 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";
}

220
Nienawidzę takich stałych z pasją. Czy to komuś wygląda źle? Thread.Sleep(1 * MINUTE)? Ponieważ jest to błąd 1000 razy.
Roman Starkov

31
const int SECOND = 1;Dziwne, sekunda to jedna sekunda.
poważnedev

62
Ten typ kodu jest prawie niemożliwy do zlokalizowania. Jeśli Twoja aplikacja musi pozostać tylko w języku angielskim, to dobrze. Ale jeśli przejdziesz do innych języków, będziesz nienawidził siebie za robienie takiej logiki. Tylko żebyście wszyscy wiedzieli ...
Nik Reiman

73
Myślę, że gdyby zmieniono nazwy stałych, aby dokładnie opisać zawartą w nich wartość, łatwiej byłoby to zrozumieć. SecondsPerMinute = 60; MinutyPogodziny = 60; SecondsPerHour = MinutyPerHour * SecondsPerHour; itp. Samo wywołanie go MINUTA = 60 nie pozwala czytelnikowi określić, jaka jest wartość.
slolife

14
Dlaczego nikt (oprócz Joe) nie przejmuje się niewłaściwą wartością „Wczoraj” lub „dni temu” ??? Wczoraj nie chodzi o obliczenia godzinowe, ale codzienne. Tak, to zły kod, przynajmniej w dwóch częstych przypadkach.
CtrlX

363

wtyczka jquery.timeago

Jeff, ponieważ Stack Overflow intensywnie korzysta z jQuery, polecam wtyczkę jquery.timeago .

Korzyści:

  • Unikaj znaczników czasu z „1 minuty temu”, mimo że strona została otwarta 10 minut temu; timeago odświeża się automatycznie.
  • Możesz w pełni wykorzystać buforowanie stron i / lub fragmentów w swoich aplikacjach internetowych, ponieważ znaczniki czasu nie są obliczane na serwerze.
  • Możesz używać mikroformatów jak fajne dzieci.

Po prostu dołącz go do znaczników czasu w DOM gotowych:

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

Spowoduje to obrócenie wszystkich abbrelementów z klasą czasu i znacznikiem czasu ISO 8601 w tytule:

<abbr class="timeago" title="2008-07-17T09:24:17Z">July 17, 2008</abbr>

w coś takiego:

<abbr class="timeago" title="July 17, 2008">4 months ago</abbr>

co daje: 4 miesiące temu. W miarę upływu czasu znaczniki czasu będą automatycznie aktualizowane.

Oświadczenie: Napisałem tę wtyczkę, więc jestem stronniczy.


39
Seb, Jeśli masz wyłączoną obsługę Javascript, wyświetlony zostanie łańcuch, który pierwotnie umieściłeś między znacznikami abbr. Zazwyczaj jest to tylko data lub godzina w dowolnym formacie. Timeago degraduje się z gracją. To nie staje się dużo prostsze.
Ryan McGeary

23
Ryan, zasugerowałem, że SO używają jakiś czas temu. Odpowiedź Jeffa zmusiła mnie do płaczu, sugeruję usiąść: stackoverflow.uservoice.com/pages/1722-general/suggestions/…
Rob Fonseca-Ensor

7
Hej, dzięki Rob. W porządku Jest to ledwo zauważalne, zwłaszcza gdy podczas przejścia zmienia się tylko jedna liczba, chociaż strony SO mają wiele znaczników czasu. Wydawało mi się jednak, że doceniłby przynajmniej zalety buforowania stron, nawet jeśli zdecyduje się unikać automatycznych aktualizacji. Jestem pewien, że Jeff mógł również wyrazić opinię na temat ulepszenia wtyczki. Pocieszam się, wiedząc, że korzystają z niego witryny takie jak arstechnica.com .
Ryan McGeary,

19
@Rob Fonseca-Ensor - teraz też sprawia, że ​​płaczę. Jak aktualizuje się raz na minutę, aby wyświetlać dokładne informacje, w jakikolwiek sposób związane z miganiem tekstu co sekundę?
Daniel Earwicker

25
Pytanie dotyczy C #, nie widzę znaczenia wtyczki jQuery.
BartoszKP

331

Oto jak to robię

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

Propozycje? Komentarze? Sposoby ulepszenia tego algorytmu?


112
„<48 * 60 * 60s” to dość niekonwencjonalna definicja „wczoraj”. Jeśli w środę jest 9 rano, to czy naprawdę myślisz o 9:01 w poniedziałek jako „wczoraj”? Myślałem, że algorytm na wczoraj lub „n dni temu” powinien rozważyć przed / po północy.
Joe

139
Kompilatory są zwykle bardzo dobre w obliczaniu stałych wyrażeń, takich jak 24 * 60 * 60, więc możesz bezpośrednio ich używać zamiast obliczać je jako 86400 i umieszczać oryginalne wyrażenie w komentarzach
zvolkov

11
@bzlm Myślę, że zrobiłem projekt, nad którym pracowałem. Moją motywacją było ostrzeżenie innych, że w tym przykładzie kodu pominięto tygodnie. Jeśli chodzi o to, jak to zrobić, wydawało mi się to całkiem proste.
jray

9
Myślę, że dobrym sposobem na ulepszenie algorytmu jest wyświetlenie 2 jednostek, takich jak „2 miesiące 21 dni temu”, „1 godzina 40 minut temu” dla zwiększenia dokładności.
Evgeny Levin

5
@ Jeffy, przeoczyłeś obliczenia dotyczące roku przestępnego i powiązanych kontroli
Saboor Awan,

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

Wolę tę wersję ze względu na zwięzłość i możliwość dodawania nowych punktów zaznaczenia. Można by to zawrzeć w Latest()rozszerzeniu Timespan zamiast tego długiego 1 linijki, ale ze względu na zwięzłość przy wysyłaniu, tak się stanie. To naprawia godzinę temu, 1 godzinę temu, zapewniając godzinę do upływu 2 godzin


Używam tej funkcji, mam wiele problemów, na przykład, jeśli kpisz sobie z „theDate = DateTime.Now.AddMinutes (-40);” Dostaję „40 godzin temu”, ale przy odpowiedzi kodu refactormycode Michaela zwraca poprawność „40 minut temu”?
GONeale,

myślę, że brakuje Ci zera, spróbuj: dawno od = (DateTime.Now.Ticks - theDate.Ticks) / 10000000;
robnardo

8
Hmm, chociaż ten kod może działać, niepoprawne i niepoprawne jest założenie, że kolejność kluczy w Słowniku będzie w określonej kolejności. Słownik używa Object.GetHashCode (), która nie zwraca długiej, ale int !. Jeśli chcesz je posortować, powinieneś użyć SortedList <long, string>. Co jest złego w ocenie progów w zbiorze if / else if /.../ else? Otrzymujesz taką samą liczbę porównań. FYI hash na długo.MaxValue okazuje się być taki sam jak int.MinValue!
CodeMonkeyKing

OP zapomniał t.Dzień> 30? t.Days / 30:
Lars Holm Jensen

Aby rozwiązać problem wspomniany przez @CodeMonkeyKing, możesz użyć SortedDictionaryzamiast zwykłego Dictionary: użycie jest takie samo, ale zapewnia sortowanie kluczy. Ale nawet wtedy algorytm ma wady, ponieważ RelativeDate(DateTime.Now.AddMonths(-3).AddDays(-3))zwraca „95 miesięcy temu” , niezależnie od używanego typu słownika, co jest niepoprawne (powinno zwrócić „3 miesiące temu” lub „4 miesiące temu” w zależności od tego, który próg „ ponownie używam) - nawet jeśli -3 nie tworzy daty w poprzednim roku (przetestowałem to w grudniu, więc w tym przypadku nie powinno się to zdarzyć).
Matt

71

Oto przepis z Jeffs Script dla 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";
    }
}    

7
Pytanie jest oznaczone w C # Dlaczego kod PHP ?
Kiquenet,

65
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

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

to jest bardzo fajne IMO :) Można to również refaktoryzować jako metodę rozszerzenia? czy słownik może stać się statyczny, więc jest tworzony tylko raz i do którego można się później odwoływać?
Pure.Krome


5
Prawdopodobnie zechcesz wyciągnąć ten słownik do pola, aby zmniejszyć tworzenie instancji i rezygnację z GC. Musisz się zmienić Func<string>na Func<double>.
Drew Noakes,

49

Oto implementacja, którą dodałem jako metodę rozszerzenia do klasy DateTime, która obsługuje zarówno przyszłe, jak i przeszłe daty i zapewnia opcję przybliżenia, która pozwala określić poziom szczegółowości, którego szukasz („3 godziny temu” w porównaniu do „3 godzin, 23 minuty, 12 sekund temu ”):

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

38

Poleciłbym to również po stronie klienta. Mniej pracy dla serwera.

Oto wersja, której używam (od Zach Leatherman)

/*
 * Javascript Humane Dates
 * Copyright (c) 2008 Dean Landolt (deanlandolt.com)
 * Re-write by Zach Leatherman (zachleat.com)
 * 
 * Adopted from the John Resig's pretty.js
 * at http://ejohn.org/blog/javascript-pretty-date
 * and henrah's proposed modification 
 * at http://ejohn.org/blog/javascript-pretty-date/#comment-297458
 * 
 * Licensed under the MIT license.
 */

function humane_date(date_str){
        var time_formats = [
                [60, 'just now'],
                [90, '1 minute'], // 60*1.5
                [3600, 'minutes', 60], // 60*60, 60
                [5400, '1 hour'], // 60*60*1.5
                [86400, 'hours', 3600], // 60*60*24, 60*60
                [129600, '1 day'], // 60*60*24*1.5
                [604800, 'days', 86400], // 60*60*24*7, 60*60*24
                [907200, '1 week'], // 60*60*24*7*1.5
                [2628000, 'weeks', 604800], // 60*60*24*(365/12), 60*60*24*7
                [3942000, '1 month'], // 60*60*24*(365/12)*1.5
                [31536000, 'months', 2628000], // 60*60*24*365, 60*60*24*(365/12)
                [47304000, '1 year'], // 60*60*24*365*1.5
                [3153600000, 'years', 31536000], // 60*60*24*365*100, 60*60*24*365
                [4730400000, '1 century'] // 60*60*24*365*100*1.5
        ];

        var time = ('' + date_str).replace(/-/g,"/").replace(/[TZ]/g," "),
                dt = new Date,
                seconds = ((dt - new Date(time) + (dt.getTimezoneOffset() * 60000)) / 1000),
                token = ' ago',
                i = 0,
                format;

        if (seconds < 0) {
                seconds = Math.abs(seconds);
                token = '';
        }

        while (format = time_formats[i++]) {
                if (seconds < format[0]) {
                        if (format.length == 2) {
                                return format[1] + (i > 1 ? token : ''); // Conditional so we don't return Just Now Ago
                        } else {
                                return Math.round(seconds / format[2]) + ' ' + format[1] + (i > 1 ? token : '');
                        }
                }
        }

        // overflow for 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);
                });
        };
}

4
Pytanie jest oznaczone w C # Dlaczego kod JavaScript ?
Kiquenet,

36

Istnieje także pakiet o nazwie Humanizr w Nuget, który faktycznie działa naprawdę dobrze i jest w .NET Foundation.

DateTime.UtcNow.AddHours(-30).Humanize() => "yesterday"
DateTime.UtcNow.AddHours(-2).Humanize() => "2 hours ago"

DateTime.UtcNow.AddHours(30).Humanize() => "tomorrow"
DateTime.UtcNow.AddHours(2).Humanize() => "2 hours from now"

TimeSpan.FromMilliseconds(1299630020).Humanize() => "2 weeks"
TimeSpan.FromMilliseconds(1299630020).Humanize(3) => "2 weeks, 1 day, 1 hour"

Scott Hanselman napisał o tym na swoim blogu


3
przyjazna uwaga: na .net 4.5 lub nowszym nie instaluj pełnego Humanizera ... zainstaluj tylko Humanizer.Core część tego .. ponieważ inne pakiety językowe nie są obsługiwane w tej wersji
Ahmad

Tak przydatne! Ta odpowiedź musi być znacznie wyższa na tej liście. Gdybym miał 100 głosów, oddałbym to temu. Najwyraźniej (pochodzący z JS-land) poszukiwanie tego pakietu nie było łatwe.
kumarharsh

29

@jeff

IMHO twoje wydaje się trochę długie. Wydaje się jednak, że jest nieco bardziej solidny dzięki obsłudze „wczoraj” i „lat”. Jednak z mojego doświadczenia wynika, że ​​osoba ta najprawdopodobniej wyświetli treść w ciągu pierwszych 30 dni. Potem są tylko naprawdę hardcorowi ludzie. Dlatego zwykle decyduję się na to, aby było to krótkie i proste.

Jest to metoda, której obecnie używam na jednej z moich stron internetowych. Zwraca tylko względny dzień, godzinę i godzinę. A potem użytkownik musi uderzyć „temu” w wynik.

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

24

Kilka lat spóźniłem się na przyjęcie, ale musiałem to zrobić zarówno dla dat przeszłych, jak i przyszłych, więc połączyłem w to Jeffa i Vincenta . To ternarytastyczna ekstrawagancja! :)

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

21

Czy jest łatwy sposób to zrobić w Javie? java.util.DateKlasa wydaje się raczej ograniczona.

Oto moje szybkie i brudne rozwiązanie 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;
}

1
Pytanie jest oznaczone w C # Dlaczego kod Java ?
Kiquenet,

20

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

19

Biorąc pod uwagę, że świat i jej mąż wydają się publikować próbki kodu, oto, co napisałem jakiś czas temu, w oparciu o kilka tych odpowiedzi.

Szczególnie potrzebowałem, aby ten kod był zlokalizowany. Mam więc dwie klasy - Grammarktóre określają lokalizowalne warunki i FuzzyDateExtensionsktóre zawierają wiele metod rozszerzenia. Nie musiałem zajmować się przyszłymi czasami danych, więc nie próbuję obsłużyć ich przy pomocy tego kodu.

Część XMLdoc zostawiłem w źródle, ale usunąłem większość (tam, gdzie byłyby oczywiste) ze względu na zwięzłość. Nie uwzględniłem tu również każdego członka klasy:

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)
    { ... }
}

FuzzyDateStringKlasa zawiera:

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

Jedną z najważniejszych rzeczy, które chcieliśmy osiągnąć, jak również lokalizacja, było to, że „dziś” będzie jedynie oznaczać „ten dzień kalendarzowy”, więc IsToday, IsThisMonth, IsThisYearmetody wyglądać następująco:

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

i metody zaokrąglania są takie (uwzględniłem RoundedMonths, ponieważ jest to nieco inne):

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

Mam nadzieję, że ludzie uznają to za przydatne i / lub interesujące: o)


17

W PHP robię to w ten sposób:

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

} ?>

5
Pytanie ma oznaczenie C # . Dlaczego ten kod PHP ? IMHO stosuje tylko kod C #
Kiquenet

17

używając 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

Myślałem, że dam temu szansę za pomocą zajęć i polimorfizmu. Miałem poprzednią iterację, w której wykorzystano podklasę, która skończyła się zbyt dużym obciążeniem. Zmieniłem na bardziej elastyczny model obiektu delegowanego / własności publicznej, który jest znacznie lepszy. Mój kod jest bardzo nieznacznie dokładniejszy, chciałbym wymyślić lepszy sposób na wygenerowanie „miesięcy temu”, który nie wydawał się zbyt zawyżony.

Myślę, że nadal trzymałbym się kaskady Jeffa jeśli-to, ponieważ jest mniej kodu i jest prostszy (zdecydowanie łatwiej jest upewnić się, że zadziała zgodnie z oczekiwaniami).

Dla poniższego kodu PrintRelativeTime.GetRelativeTimeMessage (TimeSpan temu) zwraca komunikat o względnym czasie (np. „Wczoraj”).

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

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

To samo co inna odpowiedź na to pytanie, ale jako metoda rozszerzenia ze słownikiem statycznym.


Co kupuje słownik tutaj?
StriplingWarrior

StriplingWarrior: Łatwość odczytu i modyfikacji w porównaniu do instrukcji switch lub stosu instrukcji if / else. Słownik jest statyczny, co oznacza, że ​​nie trzeba tworzyć obiektów Func <,> za każdym razem, gdy chcemy użyć ToRelativeDate; jest tworzony tylko raz, w porównaniu do tego, który podłączyłem w mojej odpowiedzi.
Chris Charabaruk

Widzę. Właśnie myślałem, ponieważ dokumentacja Dictionarymówi, że „Kolejność zwracania elementów jest niezdefiniowana” ( msdn.microsoft.com/en-us/library/xfhwa508.aspx ) być może nie jest to najlepsza struktura danych do użycia gdy nie zależy Ci na czasach wyszukiwania, a nie na utrzymywaniu porządku.
StriplingWarrior

StriplingWarrior: Wierzę, że LINQ bierze to pod uwagę, gdy używa się go z Dictionarys. Jeśli nadal czujesz się z tym niekomfortowo, możesz go użyć SortedDictionary, ale z mojego własnego doświadczenia wynika, że ​​jest to niepotrzebne.
Chris Charabaruk

12

Jeśli znasz strefę czasową widza, łatwiej będzie użyć dni kalendarzowych w skali dnia. Nie znam bibliotek .NET, więc niestety nie wiem, jak byś to zrobił w C #.

W witrynach konsumenckich możesz też być nieco bardziej machający ręką. „Mniej niż minutę temu” lub „przed chwilą” może być wystarczająco dobre.


11

możesz spróbować. Myślę, że będzie działać poprawnie.

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

9

Java dla użycia gwt po stronie klienta:

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

}

Pytanie ma oznaczenie C # . Dlaczego ten kod Java ? IMHO stosuje tylko kod C #
Kiquenet

9

@Jeff

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

Mimo to odejmowanie DateTimezwraca TimeSpan.

Więc możesz po prostu zrobić

(DateTime.UtcNow - dt).TotalSeconds

Jestem również zaskoczony, widząc, że stałe są mnożone ręcznie, a następnie dodawane są komentarze z mnożeniem. Czy to była jakaś błędna optymalizacja?


8

Oto algorytm nakładania stosów algorytmów, ale przepisany bardziej zwięźle w pseudokodzie „zgiń” z poprawką (nie „godzinę temu”). Funkcja zajmuje (dodatnią) liczbę sekund temu i zwraca przyjazny dla człowieka ciąg, taki jak „3 godziny temu” lub „wczoraj”.

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

8

Możesz użyć rozszerzenia TimeAgo, z którego wygląda następująco:

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

Lub użyj wtyczki jQuery z rozszerzeniem Razor od Timeago.


8

Można zmniejszyć obciążenie po stronie serwera, wykonując tę ​​logikę po stronie klienta. Zobacz źródło na niektórych stronach Digg w celach informacyjnych. Mają serwer, który emituje wartość czasu epoki, która jest przetwarzana przez Javascript. W ten sposób nie musisz zarządzać strefą czasową użytkownika końcowego. Nowy kod po stronie serwera mógłby wyglądać następująco:

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

Możesz nawet dodać tam blok NOSCRIPT i po prostu wykonać ToString ().


8

Dostałem to z jednego z blogów Billa Gatesa. Muszę to znaleźć w historii przeglądarki i dam ci link.

Kod JavaScript, aby zrobić to samo (zgodnie z żądaniem):

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

Zasadniczo pracujesz w sekundach ...


6

Myślę, że istnieje już wiele odpowiedzi związanych z tym postem, ale można użyć tego, który jest łatwy w użyciu, podobnie jak wtyczka, a także łatwy do odczytania dla programistów. Wyślij konkretną datę i uzyskaj jej wartość w postaci ciągu:

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

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

5

Jeśli chcesz mieć dane wyjściowe "2 days, 4 hours and 12 minutes ago", potrzebujesz przedziału czasu:

TimeSpan timeDiff = DateTime.Now-CreatedDate;

Następnie możesz uzyskać dostęp do wartości, które lubisz:

timeDiff.Days
timeDiff.Hours

itp...


4

Podałbym do tego kilka przydatnych metod rozszerzeń i uczyniłbym kod bardziej czytelnym. Po pierwsze, kilka metod rozszerzenia dla Int32.

public static class TimeSpanExtensions {

    public static TimeSpan Days(this int value) {

        return new TimeSpan(value, 0, 0, 0);
    }

    public static TimeSpan Hours(this int value) {

        return new TimeSpan(0, value, 0, 0);
    }

    public static TimeSpan Minutes(this int value) {

        return new TimeSpan(0, 0, value, 0);
    }

    public static TimeSpan Seconds(this int value) {

        return new TimeSpan(0, 0, 0, value);
    }

    public static TimeSpan Milliseconds(this int value) {

        return new TimeSpan(0, 0, 0, 0, value);
    }

    public static DateTime Ago(this TimeSpan value) {

        return DateTime.Now - value;
    }
}

Potem jeden za DateTime.

public static class DateTimeExtensions {

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

        return dateTime - delta;
    }
}

Teraz możesz zrobić coś takiego:

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.