Calculer le temps relatif en C #


1515

Étant donné une DateTimevaleur spécifique , comment puis-je afficher le temps relatif, comme:

  • il y a 2 heures
  • Il ya 3 jours
  • il y a un mois

80
Et si vous voulez calculer un temps relatif entre maintenant et le futur?
Jhonny D. Cano -Leftware-

2
moment.js est une très belle bibliothèque d'analyse de dates. Vous pouvez envisager de l'utiliser (côté serveur ou côté client), selon vos besoins. juste pour info car personne ne l'a mentionné ici
Matej

1
Il y a le package .net github.com/NickStrupat/TimeAgo qui fait à peu près ce qui est demandé.
Rossco

Ce projet est assez lisse pour le formatage de la date github.com/Humanizr/Humanizer#humanize-datetime
Aaron Hudon

Réponses:


988

Jeff, votre code est agréable mais pourrait être plus clair avec des constantes (comme suggéré dans 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
Je déteste ces constantes avec passion. Cela vous semble-t-il faux? Thread.Sleep(1 * MINUTE)? Parce que c'est faux d'un facteur 1000.
Roman Starkov

31
const int SECOND = 1;Donc, une seconde bizarre est une seconde.
seriousdev

62
Ce type de code est presque impossible à localiser. Si votre application doit uniquement rester en anglais, alors très bien. Mais si vous passez à d'autres langues, vous vous détesterez d'avoir fait une logique comme celle-ci. Pour que vous le sachiez tous ...
Nik Reiman

73
Je pense que si les constantes étaient renommées pour décrire avec précision la valeur qui s'y trouve, ce serait plus facile à comprendre. Donc SecondsPerMinute = 60; MinutesPerHour = 60; SecondsPerHour = MinutesPerHour * SecondsPerHour; etc. Le simple fait d'appeler MINUTE = 60 ne permet pas au lecteur de déterminer quelle est la valeur.
slolife

14
Pourquoi personne (sauf Joe) ne se soucie de la mauvaise valeur "Hier" ou "Il y a jours" ??? Hier n'est pas un calcul d'heure, mais un calcul de jour en jour. Alors oui, c'est un mauvais code au moins dans deux cas fréquents.
CtrlX

363

plugin jquery.timeago

Jeff, parce que Stack Overflow utilise largement jQuery, je recommande le plugin jquery.timeago .

Avantages:

  • Évitez les horodatages datés de "il y a 1 minute" même si la page a été ouverte il y a 10 minutes; timeago se rafraîchit automatiquement.
  • Vous pouvez tirer pleinement parti de la mise en cache des pages et / ou des fragments dans vos applications Web, car les horodatages ne sont pas calculés sur le serveur.
  • Vous pouvez utiliser des microformats comme les enfants cool.

Attachez-le simplement à vos horodatages sur DOM ready:

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

Cela transformera tous les abbréléments avec une classe de timeago et un horodatage ISO 8601 dans le titre:

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

en quelque chose comme ça:

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

qui donne: il y a 4 mois. Au fil du temps, les horodatages seront automatiquement mis à jour.

Avertissement: j'ai écrit ce plugin, donc je suis partial.


39
Seb, Si Javascript est désactivé, la chaîne que vous avez mise à l'origine entre les balises abbr s'affiche. En règle générale, il s'agit simplement d'une date ou d'une heure dans le format de votre choix. Timeago se dégrade gracieusement. Cela ne devient pas beaucoup plus simple.
Ryan McGeary

23
Ryan, j'ai suggéré que SO utilise timeago il y a quelque temps. La réponse de Jeff m'a fait pleurer, je vous suggère de vous asseoir: stackoverflow.uservoice.com/pages/1722-general/suggestions/…
Rob Fonseca-Ensor

7
Hé, merci Rob. C'est bon. C'est à peine perceptible, surtout quand un seul numéro change pendant la transition, bien que les pages SO aient beaucoup d'horodatages. J'aurais pensé qu'il aurait au moins apprécié les avantages de la mise en cache des pages, même s'il choisissait d'éviter les mises à jour automatiques. Je suis sûr que Jeff aurait également pu fournir des commentaires pour améliorer le plugin. Je prends du réconfort en sachant que des sites comme arstechnica.com l' utilisent.
Ryan McGeary

19
@Rob Fonseca-Ensor - maintenant ça me fait aussi pleurer. Comment est une mise à jour une fois par minute, pour afficher des informations précises, en rapport avec le clignotement du texte une fois par seconde?
Daniel Earwicker

25
La question concerne C #, je ne vois pas en quoi un plugin jQuery est pertinent.
BartoszKP

331

Voici comment je le fais

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

Suggestions? Commentaires? Comment améliorer cet algorithme?


112
"<48 * 60 * 60s" est une définition plutôt non conventionnelle pour "hier". Si c'est 9h mercredi, pensez-vous vraiment à 9h01 lundi comme "hier". J'aurais pensé qu'un algorithme pour hier ou "il y a n jours" devrait envisager avant / après minuit.
Joe

139
Les compilateurs sont généralement assez bons pour pré-calculer des expressions constantes, comme 24 * 60 * 60, vous pouvez donc les utiliser directement au lieu de les calculer vous-même à 86400 et de mettre l'expression originale dans les commentaires
zvolkov

11
@bzlm Je pense l'avoir fait pour un projet sur lequel je travaillais. Ma motivation ici était d'alerter les autres que des semaines avaient été omises de cet exemple de code. Quant à la façon de procéder, cela m'a semblé assez simple.
jray

9
Je pense que le bon moyen d'améliorer l'algorithme est d'afficher 2 unités comme "il y a 2 mois 21 jours", "il y a 1 heure 40 minutes" pour augmenter la précision.
Evgeny Levin

5
@ Jeffy, vous avez manqué le calcul pour l'année bissextile et les contrôles associés
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 "";
}

Je préfère cette version pour sa concision et sa capacité à ajouter de nouveaux points de tick. Cela pourrait être encapsulé avec une Latest()extension de Timespan au lieu de cette longue ligne 1, mais pour des raisons de brièveté dans la publication, cela suffira. Cela corrige il y a une heure, il y a 1 heures, en prévoyant une heure jusqu'à ce que 2 heures se soient écoulées


Je reçois toutes sortes de problèmes en utilisant cette fonction, par exemple si vous vous moquez de 'theDate = DateTime.Now.AddMinutes (-40);' Je reçois «il y a 40 heures», mais avec la réponse refactormycode de Michael, il revient correctement à «il y a 40 minutes»?
GONeale

je pense que vous manquez un zéro, essayez: depuis longtemps = (DateTime.Now.Ticks - theDate.Ticks) / 10000000;
robnardo

8
Hmm, bien que ce code puisse fonctionner, il est incorrect et non valide de supposer que l'ordre des clés dans le dictionnaire sera dans un ordre spécifique. Le dictionnaire utilise Object.GetHashCode () qui ne renvoie pas un long mais un int !. Si vous souhaitez que ces éléments soient triés, vous devez utiliser un SortedList <long, string>. Quel est le problème avec les seuils évalués dans un ensemble de if / else if /.../ else? Vous obtenez le même nombre de comparaisons. Pour info le hachage pour long.MaxValue s'avère être le même que int.MinValue!
CodeMonkeyKing

OP oublié t.Days> 30? t.Days / 30:
Lars Holm Jensen

Pour résoudre le problème mentionné par @CodeMonkeyKing, vous pouvez utiliser un SortedDictionaryau lieu d'un simple Dictionary: l'utilisation est la même, mais elle garantit que les clés sont triées. Mais même dans ce cas, l'algorithme a des défauts, car RelativeDate(DateTime.Now.AddMonths(-3).AddDays(-3))renvoie "il y a 95 mois" , quel que soit le type de dictionnaire que vous utilisez, ce qui est incorrect (il devrait renvoyer "il y a 3 mois" ou "il y a 4 mois" selon le seuil que vous ' réutilisation) - même si -3 ne crée pas de date au cours de la dernière année (j'ai testé cela en décembre, donc dans ce cas cela ne devrait pas se produire).
Matt

71

Voici une réécriture de Jeffs Script pour 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
La question est taguée C # Pourquoi du code 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

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

c'est très agréable IMO :) Cela pourrait également être refactorisé comme une méthode d'extension? le dictionnaire pourrait-il devenir statique de sorte qu'il n'est créé qu'une seule fois et référencé par la suite?
Pure.Krome


5
Vous voudrez probablement extraire ce dictionnaire dans un champ afin de réduire l'instanciation et le désabonnement GC. Vous devez changer Func<string>pour Func<double>.
Drew Noakes

49

Voici une implémentation que j'ai ajoutée comme méthode d'extension à la classe DateTime qui gère à la fois les dates futures et passées et fournit une option d'approximation qui vous permet de spécifier le niveau de détail que vous recherchez ("il y a 3 heures" vs "3 heures, Il y a 23 minutes, 12 secondes "):

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

Je recommanderais également de calculer cela du côté client. Moins de travail pour le serveur.

Ce qui suit est la version que j'utilise (de 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
La question est taguée C # Pourquoi du code Javascript ?
Kiquenet

36

Il existe également un package appelé Humanizr sur Nuget, qui fonctionne réellement très bien et se trouve dans la .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 a un article à ce sujet sur son blog


3
note amicale: sur .net 4.5 ou supérieur, n'installez pas Humanizer complet ... installez uniquement Humanizer.Core en partie .. car les autres packages de langue ne sont pas pris en charge sur cette version
Ahmad

Tellement utile! Cette réponse doit être beaucoup plus élevée dans cette liste. Si j'avais 100 voix, je donnerais cela. Apparemment (venant de JS-land), la recherche de ce package n'a pas été facile.
kumarharsh

29

@jeff

À mon humble avis, le vôtre semble un peu long. Cependant, il semble un peu plus robuste avec un support pour "hier" et "années". Mais d'après mon expérience, lorsque cette option est utilisée, la personne est plus susceptible de voir le contenu au cours des 30 premiers jours. Ce ne sont que les gens vraiment hardcore qui viennent après ça. C'est pourquoi je choisis généralement de garder cela court et simple.

C'est la méthode que j'utilise actuellement sur l'un de mes sites Web. Cela ne renvoie qu'un jour, une heure et une heure relatifs. Et puis l'utilisateur doit gifler "il y a" dans la sortie.

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

Quelques années de retard à la fête, mais j'avais une obligation de le faire pour les dates passées et futures, j'ai donc combiné Jeff et Vincent à cela. C'est une extravagance ternarytastique! :)

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

Existe-t-il un moyen simple de le faire en Java? La java.util.Dateclasse semble plutôt limitée.

Voici ma solution Java rapide et sale:

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
La question est taguée C # Pourquoi du code Java ?
Kiquenet

20

Version 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

Étant donné que le monde et son mari semblent publier des exemples de code, voici ce que j'ai écrit il y a quelque temps, basé sur quelques-unes de ces réponses.

J'avais un besoin spécifique pour que ce code soit localisable. J'ai donc deux classes - Grammar, qui spécifie les termes localisables, et FuzzyDateExtensions, qui contient un tas de méthodes d'extension. Je n'avais pas besoin de m'occuper des futures datetimes, donc aucune tentative n'est faite pour les gérer avec ce code.

J'ai laissé une partie du XMLdoc dans la source, mais j'ai supprimé la plupart (où ils seraient évidents) par souci de concision. Je n'ai pas non plus inclus tous les membres de la classe ici:

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

La FuzzyDateStringclasse contient:

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

L' une des principales choses que je voulais réaliser, ainsi que la localisation, était que « aujourd'hui » ne ferait que dire « ce jour de calendrier », de sorte que le IsToday, IsThisMonth, les IsThisYearméthodes ressemblent à ceci:

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

et les méthodes d'arrondi sont comme ceci (j'ai inclus RoundedMonths, car c'est un peu différent):

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

J'espère que les gens trouveront cela utile et / ou intéressant: o)


17

En PHP, je le fais de cette façon:

<?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
La question est balisée C # . Pourquoi ce code PHP ? À
mon humble avis

17

en utilisant 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

Je pensais que je pourrais essayer ceci en utilisant des classes et le polymorphisme. J'ai eu une itération précédente qui utilisait la sous-classification qui a fini par avoir beaucoup trop de frais généraux. Je suis passé à un modèle d'objet délégué / propriété publique plus flexible, ce qui est nettement meilleur. Mon code est très légèrement plus précis, j'aurais aimé pouvoir trouver une meilleure façon de générer des "mois passés" qui ne semblaient pas trop sur-conçus.

Je pense que je resterais avec la cascade if-then de Jeff car c'est moins de code et c'est plus simple (il est certainement plus facile de s'assurer que cela fonctionnera comme prévu).

Pour le code ci-dessous PrintRelativeTime.GetRelativeTimeMessage (TimeSpan ago) renvoie le message d'heure relative (par exemple "hier").

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

Identique à une autre réponse à cette question mais comme méthode d'extension avec un dictionnaire statique.


Qu'est-ce que le dictionnaire vous achète ici?
StriplingWarrior

StriplingWarrior: facilité de lecture et de modification par rapport à une instruction switch ou à une pile d'instructions if / else. Le dictionnaire étant statique signifie que lui et les objets Func <,> ne doivent pas être créés à chaque fois que nous voulons utiliser ToRelativeDate; il n'est créé qu'une seule fois, par rapport à celui que j'ai lié dans ma réponse.
Chris Charabaruk

Je vois. Je pensais juste, car la documentation sur Dictionaryindique que "L'ordre dans lequel les éléments sont renvoyés n'est pas défini", ( msdn.microsoft.com/en-us/library/xfhwa508.aspx ) ce n'est peut-être pas la meilleure structure de données à utiliser lorsque vous ne vous souciez pas autant des temps de recherche que du maintien de l'ordre.
StriplingWarrior

StriplingWarrior: Je crois que LINQ en tient compte lorsqu'il est utilisé avec l' Dictionaryart. Si vous n'êtes toujours pas à l'aise avec cela, vous pouvez l'utiliser SortedDictionary, mais ma propre expérience montre que cela n'est pas nécessaire.
Chris Charabaruk

12

Lorsque vous connaissez le fuseau horaire du spectateur, il peut être plus clair d'utiliser les jours civils à l'échelle du jour. Je ne connais pas les bibliothèques .NET, donc je ne sais pas comment vous feriez cela en C #, malheureusement.

Sur les sites de consommateurs, vous pourriez également être manié à la main en moins d'une minute. "Il y a moins d'une minute" ou "tout à l'heure" pourrait être suffisant.


11

vous pouvez essayer cela. Je pense que cela fonctionnera correctement.

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 pour une utilisation gwt côté client:

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

}

La question est balisée C # . Pourquoi ce code Java ? À
mon humble avis

9

@Jeff

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

Faire une soustraction sur DateTimerenvoie TimeSpanquand même un .

Vous pouvez donc faire

(DateTime.UtcNow - dt).TotalSeconds

Je suis également surpris de voir les constantes multipliées à la main, puis les commentaires ajoutés avec les multiplications. Était-ce une optimisation erronée?


8

Voici l'algorithme utilisé par stackoverflow mais réécrit de manière plus concise en pseudocode perlish avec une correction de bogue (pas "il y a" une heure "). La fonction prend un nombre (positif) de secondes auparavant et renvoie une chaîne conviviale comme "il y a 3 heures" ou "hier".

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

Vous pouvez utiliser l' extension TimeAgo à partir de laquelle ressemble à ceci:

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

Ou utilisez le plugin jQuery avec l'extension Razor de Timeago.


8

Vous pouvez réduire la charge côté serveur en effectuant cette logique côté client. Voir la source sur certaines pages Digg pour référence. Ils ont le serveur émettre une valeur de temps d'époque qui est traitée par Javascript. De cette façon, vous n'avez pas besoin de gérer le fuseau horaire de l'utilisateur final. Le nouveau code côté serveur ressemblerait à ceci:

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

Vous pouvez même y ajouter un bloc NOSCRIPT et simplement effectuer une ToString ().


8

Ça, je l'ai reçu d'un des blogs de Bill Gates. Je dois le trouver dans l'historique de mon navigateur et je vous donnerai le lien.

Le code Javascript pour faire la même chose (comme demandé):

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

Fondamentalement, vous travaillez en termes de secondes ...


6

Je pense qu'il y a déjà un certain nombre de réponses liées à ce post, mais on peut utiliser cela qui est facile à utiliser comme un plugin et également facilement lisible pour les programmeurs. Envoyez votre date spécifique et obtenez sa valeur sous forme de chaîne:

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

Si vous voulez avoir une sortie comme "2 days, 4 hours and 12 minutes ago", vous avez besoin d'une période de temps:

TimeSpan timeDiff = DateTime.Now-CreatedDate;

Ensuite, vous pouvez accéder aux valeurs que vous aimez:

timeDiff.Days
timeDiff.Hours

etc...


4

Je fournirais quelques méthodes d'extensions pratiques pour cela et rendrais le code plus lisible. Tout d'abord, quelques méthodes d'extension pour 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;
    }
}

Ensuite, un pour DateTime.

public static class DateTimeExtensions {

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

        return dateTime - delta;
    }
}

Maintenant, vous pouvez faire quelque chose comme ci-dessous:

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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.