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

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

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

Что, если вы хотите рассчитать относительное время от настоящего момента до будущего?

Jhonny D. Cano -Leftware- 26.03.2009 23:42

moment.js - очень хорошая библиотека для анализа дат. Вы можете рассмотреть возможность ее использования (на стороне сервера или на стороне клиента), в зависимости от ваших потребностей. просто fyi, потому что никто не упомянул об этом здесь

Matej 30.06.2014 17:26

Этот проект очень хорош для форматирования даты github.com/Humanizr/Humanizer#humanize-datetime

Aaron Hudon 15.01.2020 22:44
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1 569
3
172 336
39
Перейти к ответу Данный вопрос помечен как решенный

Ответы 39

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

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 < 60 * 2)
{
  return "a minute ago";
}
if (delta < 45 * 60)
{
  return ts.Minutes + " minutes ago";
}
if (delta < 90 * 60)
{
  return "an hour ago";
}
if (delta < 24 * 60 * 60)
{
  return ts.Hours + " hours ago";
}
if (delta < 48 * 60 * 60)
{
  return "yesterday";
}
if (delta < 30 * 24 * 60 * 60)
{
  return ts.Days + " days ago";
}
if (delta < 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";

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

"

Joe 01.02.2009 22:33

Компиляторы обычно довольно хороши в предварительном вычислении константных выражений, например 24 * 60 * 60, поэтому вы можете напрямую использовать их, вместо того, чтобы вычислять самостоятельно как 86400 и помещать исходное выражение в комментарии.

zvolkov 11.06.2009 16:50

@bzlm Я думаю, что сделал для проекта, над которым работал. Моя мотивация здесь заключалась в том, чтобы предупредить других о том, что в этом примере кода не указаны недели. Что касается того, как это сделать, мне это показалось довольно простым.

jray 10.11.2010 02:38

Я также думаю, что это довольно странное определение «вчера», если я сейчас (вечер воскресенья) вижу ответ в пятницу вечером как «вчера».

Paŭlo Ebermann 28.08.2011 22:46

Я думаю, что хороший способ улучшить алгоритм - это отображение двух единиц, например «2 месяца 21 день назад», «1 час 40 минут назад» для повышения точности.

Evgeny Levin 01.02.2012 17:42

Поскольку все эти If..else - это просто временные шкалы, вы можете создать перечисление для лучшей читаемости. Скажите enum TimeSlabs { Seconds = 60, Minutes = 60 * 60, Hours = 60*60*24, Days = 60* 60*24*365, }

Antony Thomas 18.07.2012 21:48

@ Джеффи, ты пропустил расчет високосного года и связанные с ним проверки

Saboor Awan 20.06.2013 09:53

45 минут отображается как «час назад» ?! Разве это не ошибка? Если это не так, почему бы не сделать то же самое для секунд и минут?

Eldritch Conundrum 24.07.2013 15:51

Но в настоящее время SO показывает только формат «Время назад» до 2 дней. Больше 2-х дней и ставишь стандартный формат даты и времени. Я прав ?

Hugo Hilário 08.01.2014 14:59

Я бы просто назначил var delta = ts.Duration(), чтобы вы получили абсолютное значение в объекте TimeSpan, а затем вы можете делать такие вещи, как if (delta < TimeSpan.FromMinutes(1)) или if (delta < TimeSpan.FromDays(1)). По-прежнему читаемый и не требует загадочных чисел или констант.

Rufus L 24.04.2018 07:01

@Джефф

ИМХО твой кажется длинноватым. Однако он кажется немного более надежным с поддержкой «вчера» и «лет». Но по моему опыту, когда это используется, человек, скорее всего, просмотрит контент в первые 30 дней. После этого приходят только действительно хардкорные люди. Поэтому я обычно стараюсь сделать это коротким и простым.

Это метод, который я сейчас использую на одном из своих веб-сайтов. Это возвращает только относительный день, час и время. И затем пользователь должен нажать «назад» в выводе.

public static string ToLongString(this TimeSpan time)
{
    string output = String.Empty;

    if (time.Days > 0)
        output += time.Days + " days ";

    if ((time.Days == 0 || time.Days == 1) && time.Hours > 0)
        output += time.Hours + " hr ";

    if (time.Days == 0 && time.Minutes > 0)
        output += time.Minutes + " min ";

    if (output.Length == 0)
        output += time.Seconds + " sec";

    return output.Trim();
}
Ответ принят как подходящий

Джефф, ваш код - это хорошо, но можно было бы прояснить константы (как предлагается в 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";
}

Я страстно ненавижу такие константы. Кому-нибудь это кажется неправильным? Thread.Sleep(1 * MINUTE)? Потому что это неверно в 1000 раз.

Roman Starkov 17.08.2010 21:06

Есть ли у кого-нибудь рекомендации о том, как это можно реализовать с помощью строки настраиваемого формата, так что `string.Format (mydate," dd / MM / yyyy (YTT) ") ==" 26/04/2011 (сегодня) "?

Neil Barnwell 26.04.2011 19:56

const int SECOND = 1; Странно, что секунда - это одна секунда.

seriousdev 18.06.2011 02:46

не должно быть, если (дельта

Roberto 04.07.2011 22:06

Когда я вижу весь капитал, я вспоминаю стили кодирования, разработанные для C / C++. Рекомендация Microsoft - это корпус из Паскаля. Проверьте ответ на этот вопрос - stackoverflow.com/questions/242534/…

CodeMonkeyKing 09.11.2011 08:33

Этот тип кода практически невозможно локализовать. Если ваше приложение должно оставаться только на английском языке, все в порядке. Но если вы перейдете на другие языки, вы возненавидите себя за подобную логику. Просто чтобы вы знали ...

Nik Reiman 23.05.2012 18:31

@nick Если вы поместите это в IValueConverter, он автоматически обработает CultureInfo (при условии, что вы сначала установили культуру)

Lance McCarthy 12.06.2012 00:28

Я думаю, если бы константы были переименованы, чтобы точно описывать значение, которое в них заключено, было бы легче понять. Итак, SecondsPerMinute = 60; MinutesPerHour = 60; SecondsPerHour = МинутыPerHour * SecondsPerHour; и т.д. Просто назовите его MINUTE = 60, и читатель не сможет определить, что это за значение.

slolife 29.08.2012 20:21

Почему никого (кроме Джо) не волнует неправильное значение «Вчера» или «Дней назад» ??? Вчерашний день - это не часовой расчет, а ежедневный расчет. Так что да, это неправильный код по крайней мере в двух частых случаях.

CtrlX 26.09.2013 19:47

@romkyns Thread.Sleep(1 * MINUTE)делает мне кажется неправильным, потому что в MINUTE не указаны единицы измерения. Теперь, MINUTE_IN_SECONDS, это было бы легко обнаружить и легко исправить: Thread.Sleep(1 * MINUTE_IN_SECONDS * 1000) или Thread.Sleep(1 * MINUTE_IN_SECONDS * SECOND_IN_MILLISECONDS). (Подробно, но лучше, чем скрытые ошибки.)

jpmc26 08.12.2014 15:19

Еще одна ошибка: поскольку дельта является абсолютной, путь для «еще не достигнут» никогда не достигается. Измените на: delta = ts.TotalSeconds;

StefanG 11.02.2015 15:04

Я действительно не понимаю, почему люди не используют TotalMinutes, TotalDays, TotalHours ... Это сильно сократит это ...

lord.fist 26.02.2015 17:23

Ошибка находится во «вчера», это не совсем точно, так как то, что я вижу сегодня в 7 утра, что произошло два дня назад в 22:00, даст «вчера», что неточно.

Ayyash 17.04.2015 09:28

Вы можете оптимизировать решение, используя определенные константы для типа DateTime, вам не нужно определять их вручную.

SOAL ABDELDJALLIL 17.04.2018 20:26

Разве yourDate.Tick не должен быть временем UTC? `yourDate.ToUniversalTime (). Ticks

Franva 09.06.2019 04:40

Я подумал, что попробую использовать классы и полиморфизм. У меня была предыдущая итерация, в которой использовалось подклассификация, что приводило к слишком большим накладным расходам. Я перешел на более гибкую объектную модель делегат / общедоступное свойство, которая значительно лучше. Мой код немного более точен, я хотел бы придумать лучший способ сгенерировать «несколько месяцев назад», который не казался бы слишком изощренным.

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

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

public class RelativeTimeRange : IComparable
{
    public TimeSpan UpperBound { get; set; }

    public delegate string RelativeTimeTextDelegate(TimeSpan timeDelta);

    public RelativeTimeTextDelegate MessageCreator { get; set; }

    public int CompareTo(object obj)
    {
        if (!(obj is RelativeTimeRange))
        {
            return 1;
        }
        // note that this sorts in reverse order to the way you'd expect, 
        // this saves having to reverse a list later
        return (obj as RelativeTimeRange).UpperBound.CompareTo(UpperBound);
    }
}

public class PrintRelativeTime
{
    private static List<RelativeTimeRange> timeRanges;

    static PrintRelativeTime()
    {
        timeRanges = new List<RelativeTimeRange>{
            new RelativeTimeRange
            {
                UpperBound = TimeSpan.FromSeconds(1),
                MessageCreator = (delta) => 
                { return "one second ago"; }
            }, 
            new RelativeTimeRange
            {
                UpperBound = TimeSpan.FromSeconds(60),
                MessageCreator = (delta) => 
                { return delta.Seconds + " seconds ago"; }

            }, 
            new RelativeTimeRange
            {
                UpperBound = TimeSpan.FromMinutes(2),
                MessageCreator = (delta) => 
                { return "one minute ago"; }
            }, 
            new RelativeTimeRange
            {
                UpperBound = TimeSpan.FromMinutes(60),
                MessageCreator = (delta) => 
                { return delta.Minutes + " minutes ago"; }
            }, 
            new RelativeTimeRange
            {
                UpperBound = TimeSpan.FromHours(2),
                MessageCreator = (delta) => 
                { return "one hour ago"; }
            }, 
            new RelativeTimeRange
            {
                UpperBound = TimeSpan.FromHours(24),
                MessageCreator = (delta) => 
                { return delta.Hours + " hours ago"; }
            }, 
            new RelativeTimeRange
            {
                UpperBound = TimeSpan.FromDays(2),
                MessageCreator = (delta) => 
                { return "yesterday"; }
            }, 
            new RelativeTimeRange
            {
                UpperBound = DateTime.Now.Subtract(DateTime.Now.AddMonths(-1)),
                MessageCreator = (delta) => 
                { return delta.Days + " days ago"; }
            }, 
            new RelativeTimeRange
            {
                UpperBound = DateTime.Now.Subtract(DateTime.Now.AddMonths(-2)),
                MessageCreator = (delta) => 
                { return "one month ago"; }
            }, 
            new RelativeTimeRange
            {
                UpperBound = DateTime.Now.Subtract(DateTime.Now.AddYears(-1)),
                MessageCreator = (delta) => 
                { return (int)Math.Floor(delta.TotalDays / 30) + " months ago"; }
            }, 
            new RelativeTimeRange
            {
                UpperBound = DateTime.Now.Subtract(DateTime.Now.AddYears(-2)),
                MessageCreator = (delta) => 
                { return "one year ago"; }
            }, 
            new RelativeTimeRange
            {
                UpperBound = TimeSpan.MaxValue,
                MessageCreator = (delta) => 
                { return (int)Math.Floor(delta.TotalDays / 365.24D) + " years ago"; }
            }
        };

        timeRanges.Sort();
    }

    public static string GetRelativeTimeMessage(TimeSpan ago)
    {
        RelativeTimeRange postRelativeDateRange = timeRanges[0];

        foreach (var timeRange in timeRanges)
        {
            if (ago.CompareTo(timeRange.UpperBound) <= 0)
            {
                postRelativeDateRange = timeRange;
            }
        }

        return postRelativeDateRange.MessageCreator(ago);
    }
}

public static string RelativeDate(DateTime theDate)
{
    Dictionary<long, string> thresholds = new Dictionary<long, string>();
    int minute = 60;
    int hour = 60 * minute;
    int day = 24 * hour;
    thresholds.Add(60, "{0} seconds ago");
    thresholds.Add(minute * 2, "a minute ago");
    thresholds.Add(45 * minute, "{0} minutes ago");
    thresholds.Add(120 * minute, "an hour ago");
    thresholds.Add(day, "{0} hours ago");
    thresholds.Add(day * 2, "yesterday");
    thresholds.Add(day * 30, "{0} days ago");
    thresholds.Add(day * 365, "{0} months ago");
    thresholds.Add(long.MaxValue, "{0} years ago");
    long since = (DateTime.Now.Ticks - theDate.Ticks) / 10000000;
    foreach (long threshold in thresholds.Keys) 
    {
        if (since < threshold) 
        {
            TimeSpan t = new TimeSpan((DateTime.Now.Ticks - theDate.Ticks));
            return string.Format(thresholds[threshold], (t.Days > 365 ? t.Days / 365 : (t.Days > 0 ? t.Days : (t.Hours > 0 ? t.Hours : (t.Minutes > 0 ? t.Minutes : (t.Seconds > 0 ? t.Seconds : 0))))).ToString());
        }
    }
    return "";
}

Я предпочитаю эту версию из-за ее краткости и возможности добавлять новые тиковые точки. Это можно было бы инкапсулировать с расширением Latest() для Timespan вместо этого длинного 1 лайнера, но для краткости публикации это подойдет. Это устраняет проблему, произошедшую час назад, 1 час назад, предоставляя час до истечения 2 часов.

У меня возникают всевозможные проблемы при использовании этой функции, например, если вы издеваетесь над 'theDate = DateTime.Now.AddMinutes (-40);' Я получаю «40 часов назад», но с ответом на рефакторный код Майкла он возвращается правильно на «40 минут назад»?

GONeale 15.12.2008 05:17

я думаю, вам не хватает нуля, попробуйте: long Since = (DateTime.Now.Ticks - theDate.Ticks) / 10000000;

robnardo 07.08.2009 23:37

Хм, хотя этот код может работать, неверно и недействительно предполагать, что порядок ключей в Словаре будет в определенном порядке. Словарь использует Object.GetHashCode (), который возвращает не long, а int !. Если вы хотите, чтобы они были отсортированы, вы должны использовать SortedList. Что не так с оценкой пороговых значений в наборе if / else if /.../ else? Вы получите такое же количество сравнений. К вашему сведению, хэш надолго. MaxValue оказывается таким же, как int.MinValue!

CodeMonkeyKing 09.11.2011 08:47

ОП забыл т. Д.> 30? т. Дней / 30:

Lars Holm Jensen 31.10.2012 14:39

Чтобы исправить проблему, упомянутую @CodeMonkeyKing, вы можете использовать SortedDictionary вместо простого Dictionary: использование такое же, но оно обеспечивает сортировку ключей. Но даже в этом случае алгоритм имеет недостатки, потому что RelativeDate(DateTime.Now.AddMonths(-3).AddDays(-3)) возвращает «95 месяцев назад», независимо от того, какой тип словаря вы используете, что неверно (он должен возвращать «3 месяца назад» или «4 месяца назад» в зависимости от того, какой порог вы используете). - даже если -3 не создает дату в прошлом году (я тестировал это в декабре, поэтому в этом случае этого не должно произойти).

Matt 22.11.2016 15:00

@Джефф

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

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

Так что ты можешь просто сделать

(DateTime.UtcNow - dt).TotalSeconds

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

Когда вы знаете часовой пояс зрителя, будет проще использовать календарные дни в дневной шкале. Я не знаком с библиотеками .NET, поэтому, к сожалению, не знаю, как это сделать на C#.

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

Вы можете уменьшить нагрузку на сервер, выполнив эту логику на стороне клиента. См. Исходный код на некоторых страницах Digg для справки. У них сервер выдает значение времени эпохи, которое обрабатывается Javascript. Таким образом, вам не нужно управлять часовым поясом конечного пользователя. Новый серверный код будет примерно таким:

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

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

В PHP я делаю так:

<?php
function timesince($original) {
    // array of time period chunks
    $chunks = array(
        array(60 * 60 * 24 * 365 , 'year'),
        array(60 * 60 * 24 * 30 , 'month'),
        array(60 * 60 * 24 * 7, 'week'),
        array(60 * 60 * 24 , 'day'),
        array(60 * 60 , 'hour'),
        array(60 , 'minute'),
    );

    $today = time(); /* Current unix time  */
    $since = $today - $original;

    if ($since > 604800) {
    $print = date("M jS", $original);

    if ($since > 31536000) {
        $print .= ", " . date("Y", $original);
    }

    return $print;
}

// $j saves performing the count function each time around the loop
for ($i = 0, $j = count($chunks); $i < $j; $i++) {

    $seconds = $chunks[$i][0];
    $name = $chunks[$i][1];

    // finding the biggest chunk (if the chunk fits, break)
    if (($count = floor($since / $seconds)) != 0) {
        break;
    }
}

$print = ($count == 1) ? '1 '.$name : "$count {$name}s";

return $print . " ago";

} ?>

Вопрос C# с тегами. Почему этот Код PHP? IMHO, применяется только код C#

Kiquenet 06.03.2017 13:39

Несомненно, простым решением проблемы «1 час назад» было бы увеличение окна, для которого действует «час назад». Изменять

if (delta < 5400) // 90 * 60
{
    return "an hour ago";
}

в

if (delta < 7200) // 120 * 60
{
    return "an hour ago";
}

Это означает, что то, что произошло 110 минут назад, будет считаться «час назад» - это может быть не идеально, но я бы сказал, что это лучше, чем текущая ситуация «1 час назад».

public static string ToRelativeDate(DateTime input)
{
    TimeSpan oSpan = DateTime.Now.Subtract(input);
    double TotalMinutes = oSpan.TotalMinutes;
    string Suffix = " ago";

    if (TotalMinutes < 0.0)
    {
        TotalMinutes = Math.Abs(TotalMinutes);
        Suffix = " from now";
    }

    var aValue = new SortedList<double, Func<string>>();
    aValue.Add(0.75, () => "less than a minute");
    aValue.Add(1.5, () => "about a minute");
    aValue.Add(45, () => string.Format("{0} minutes", Math.Round(TotalMinutes)));
    aValue.Add(90, () => "about an hour");
    aValue.Add(1440, () => string.Format("about {0} hours", Math.Round(Math.Abs(oSpan.TotalHours)))); // 60 * 24
    aValue.Add(2880, () => "a day"); // 60 * 48
    aValue.Add(43200, () => string.Format("{0} days", Math.Floor(Math.Abs(oSpan.TotalDays)))); // 60 * 24 * 30
    aValue.Add(86400, () => "about a month"); // 60 * 24 * 60
    aValue.Add(525600, () => string.Format("{0} months", Math.Floor(Math.Abs(oSpan.TotalDays / 30)))); // 60 * 24 * 365 
    aValue.Add(1051200, () => "about a year"); // 60 * 24 * 365 * 2
    aValue.Add(double.MaxValue, () => string.Format("{0} years", Math.Floor(Math.Abs(oSpan.TotalDays / 365))));

    return aValue.First(n => TotalMinutes < n.Key).Value.Invoke() + Suffix;
}

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

Версия C# 6:

static readonly SortedList<double, Func<TimeSpan, string>> offsets = 
   new SortedList<double, Func<TimeSpan, string>>
{
    { 0.75, _ => "less than a minute"},
    { 1.5, _ => "about a minute"},
    { 45, x => $"{x.TotalMinutes:F0} minutes"},
    { 90, x => "about an hour"},
    { 1440, x => $"about {x.TotalHours:F0} hours"},
    { 2880, x => "a day"},
    { 43200, x => $"{x.TotalDays:F0} days"},
    { 86400, x => "about a month"},
    { 525600, x => $"{x.TotalDays / 30:F0} months"},
    { 1051200, x => "about a year"},
    { double.MaxValue, x => $"{x.TotalDays / 365:F0} years"}
};

public static string ToRelativeDate(this DateTime input)
{
    TimeSpan x = DateTime.Now - input;
    string Suffix = x.TotalMinutes > 0 ? " ago" : " from now";
    x = new TimeSpan(Math.Abs(x.Ticks));
    return offsets.First(n => x.TotalMinutes < n.Key).Value(x) + Suffix;
}

это очень приятно, ИМО :) Это тоже можно было бы реорганизовать как метод расширения? может ли словарь стать статическим, чтобы он был создан только один раз, а затем на него ссылались?

Pure.Krome 06.05.2009 16:29

Pure.Krome: stackoverflow.com/questions/11/how-do-i-calculate-relative-t‌ ime /…

Chris Charabaruk 17.07.2009 06:48

Вы, вероятно, захотите вытащить этот словарь в поле, чтобы уменьшить создание экземпляров и отток сборщика мусора. Вам придется заменить Func<string> на Func<double>.

Drew Noakes 23.08.2010 14:34

плагин jquery.timeago

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

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

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

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

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

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

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

примерно так:

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

который дает: 4 месяца назад. По прошествии времени отметки времени будут автоматически обновляться.

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

А что, если у пользователя отключен JavaScript? jQuery (и JavaScritp вообще говоря) следует использовать только для улучшения работы на стороне клиента, но функции вашего приложения не должны зависеть от клиентских технологий. Вы можете сделать это без использования JavaScript.

Seb 22.03.2009 03:27

Seb, Если у вас отключен Javascript, то отображается строка, которую вы изначально поместили между тегами abbr. Обычно это просто дата или время в любом желаемом формате. Тимаго изящно деградирует. Это не намного проще.

Ryan McGeary 24.03.2009 07:40

Райан, я предложил ТАК использовать timeago некоторое время назад. Ответ Джеффа заставил меня плакать, предлагаю вам сесть: stackoverflow.uservoice.com/pages/1722-general/suggestions/…

Rob Fonseca-Ensor 26.12.2009 10:26

Хех, спасибо, Роб. Это нормально. Это едва заметно, особенно когда во время перехода изменяется только одно число, хотя на страницах SO много отметок времени. Я бы подумал, что он хотя бы оценил преимущества кеширования страниц, даже если он решит избегать автообновлений. Я уверен, что Джефф мог бы также предоставить отзыв, чтобы улучшить плагин. Я утешаюсь тем, что его используют такие сайты, как arstechnica.com.

Ryan McGeary 26.12.2009 16:56

@Rob Fonseca-Ensor - теперь я тоже плачу от этого. Как происходит обновление один раз в минуту для отображения точной информации, в любом случае связано с миганием текста раз в секунду?

Daniel Earwicker 20.04.2010 04:23

Возможность отключить автообновление не должна быть такой сложной, не так ли? Если он еще не существует, то есть ... Просто обновите временные метки один раз при загрузке страницы и покончите с этим.

Dean Harding 21.04.2010 10:43

@RyanMcGeary: Вероятно, это неправильное место, чтобы спросить, но у меня есть вопрос об использовании TimeAgo. Я нахожусь в Великобритании (GMT, включая DST), и даты, хранящиеся в моей базе данных, - это UTC. Если я вставляю дату в свою БД как UTC летом, очевидно, что время выйдет на 1 час. Таким образом, вместо того, чтобы печатать TimeAgo «около 1 минуты назад», будет сказано «около 1 часа назад». Имеет ли плагин TimeAgo дело с различиями в летнем времени?

TheCarver 14.02.2013 07:58

@PaparazzoKid Да, Timeago отлично обрабатывает часовые пояса, если ваша временная метка является правильной временной меткой ISO8601. Но ты прав. Это неправильное место, чтобы задавать этот вопрос. Вместо этого идите сюда: github.com/rmm5t/jquery-timeago/issues

Ryan McGeary 01.04.2013 03:56

отличный плагин - просто для удовольствия я "уменьшил его", уменьшив функциональность, например, автообновление, и объединил его с рефакторный код ответа - jsfiddle.net/drzaus/eMUzF

drzaus 26.09.2013 17:18

Вопрос в C#, я не понимаю, насколько актуален плагин jQuery.

BartoszKP 30.11.2015 15:25

Вот алгоритм, который использует stackoverflow, но более кратко переписан в псевдокоде Perlish с исправлением ошибки (не «час назад»). Функция берет (положительное) количество секунд назад и возвращает удобную для человека строку, например «3 часа назад» или «вчера».

agoify($delta)
  local($y, $mo, $d, $h, $m, $s);
  $s = floor($delta);
  if ($s<=1)            return "a second ago";
  if ($s<60)            return "$s seconds ago";
  $m = floor($s/60);
  if ($m==1)            return "a minute ago";
  if ($m<45)            return "$m minutes ago";
  $h = floor($m/60);
  if ($h==1)            return "an hour ago";
  if ($h<24)            return "$h hours ago";
  $d = floor($h/24);
  if ($d<2)             return "yesterday";
  if ($d<30)            return "$d days ago";
  $mo = floor($d/30);
  if ($mo<=1)           return "a month ago";
  $y = floor($mo/12);
  if ($y<1)             return "$mo months ago";
  if ($y==1)            return "a year ago";
  return "$y years ago";

Я бы порекомендовал вычислить это и на стороне клиента. Меньше работы на сервере.

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

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

Вопрос в том, C# с тегами Почему Код Javascript?

Kiquenet 06.03.2017 13:37

Вот перезапись из скрипта Джеффа для 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";
    }
}    

Вопрос в том, C# с тегами Почему Код PHP?

Kiquenet 06.03.2017 13:31

Есть ли простой способ сделать это на Java? Класс java.util.Date кажется довольно ограниченным.

Вот мое быстрое и грязное решение для Java:

import java.util.Date;
import javax.management.timer.Timer;

String getRelativeDate(Date date) {     
  long delta = new Date().getTime() - date.getTime();
  if (delta < 1L * Timer.ONE_MINUTE) {
    return toSeconds(delta) == 1 ? "one second ago" : toSeconds(delta) + " seconds ago";
  }
  if (delta < 2L * Timer.ONE_MINUTE) {
    return "a minute ago";
  }
  if (delta < 45L * Timer.ONE_MINUTE) {
    return toMinutes(delta) + " minutes ago";
  }
  if (delta < 90L * Timer.ONE_MINUTE) {
    return "an hour ago";
  }
  if (delta < 24L * Timer.ONE_HOUR) {
    return toHours(delta) + " hours ago";
  }
  if (delta < 48L * Timer.ONE_HOUR) {
    return "yesterday";
  }
  if (delta < 30L * Timer.ONE_DAY) {
    return toDays(delta) + " days ago";
  }
  if (delta < 12L * 4L * Timer.ONE_WEEK) { // a month
    long months = toMonths(delta); 
    return months <= 1 ? "one month ago" : months + " months ago";
  }
  else {
    long years = toYears(delta);
    return years <= 1 ? "one year ago" : years + " years ago";
  }
}

private long toSeconds(long date) {
  return date / 1000L;
}

private long toMinutes(long date) {
  return toSeconds(date) / 60L;
}

private long toHours(long date) {
  return toMinutes(date) / 60L;
}

private long toDays(long date) {
  return toHours(date) / 24L;
}

private long toMonths(long date) {
  return toDays(date) / 30L;
}

private long toYears(long date) {
  return toMonths(date) / 365L;
}

Вопрос в том, C# с тегами Почему Код Java?

Kiquenet 06.03.2017 13:39

Вот реализация, которую я добавил в качестве метода расширения к классу DateTime, который обрабатывает как будущие, так и прошлые даты и предоставляет опцию аппроксимации, которая позволяет вам указать уровень детализации, который вы ищете («3 часа назад» против «3 часа, 23 минуты 12 секунд назад "):

using System.Text;

/// <summary>
/// Compares a supplied date to the current date and generates a friendly English 
/// comparison ("5 days ago", "5 days from now")
/// </summary>
/// <param name = "date">The date to convert</param>
/// <param name = "approximate">When off, calculate timespan down to the second.
/// When on, approximate to the largest round unit of time.</param>
/// <returns></returns>
public static string ToRelativeDateString(this DateTime value, bool approximate)
{
    StringBuilder sb = new StringBuilder();

    string suffix = (value > DateTime.Now) ? " from now" : " ago";

    TimeSpan timeSpan = new TimeSpan(Math.Abs(DateTime.Now.Subtract(value).Ticks));

    if (timeSpan.Days > 0)
    {
        sb.AppendFormat("{0} {1}", timeSpan.Days,
          (timeSpan.Days > 1) ? "days" : "day");
        if (approximate) return sb.ToString() + suffix;
    }
    if (timeSpan.Hours > 0)
    {
        sb.AppendFormat("{0}{1} {2}", (sb.Length > 0) ? ", " : string.Empty,
          timeSpan.Hours, (timeSpan.Hours > 1) ? "hours" : "hour");
        if (approximate) return sb.ToString() + suffix;
    }
    if (timeSpan.Minutes > 0)
    {
        sb.AppendFormat("{0}{1} {2}", (sb.Length > 0) ? ", " : string.Empty, 
          timeSpan.Minutes, (timeSpan.Minutes > 1) ? "minutes" : "minute");
        if (approximate) return sb.ToString() + suffix;
    }
    if (timeSpan.Seconds > 0)
    {
        sb.AppendFormat("{0}{1} {2}", (sb.Length > 0) ? ", " : string.Empty, 
          timeSpan.Seconds, (timeSpan.Seconds > 1) ? "seconds" : "second");
        if (approximate) return sb.ToString() + suffix;
    }
    if (sb.Length == 0) return "right now";

    sb.Append(suffix);
    return sb.ToString();
}

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

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

Что вам здесь дает словарь?

StriplingWarrior 27.05.2011 01:46

StriplingWarrior: легкость чтения и изменения по сравнению с оператором switch или стеком операторов if / else. Статический словарь означает, что его и объекты Func не нужно создавать каждый раз, когда мы хотим использовать ToRelativeDate; он создается только один раз, по сравнению с тем, который я указал в своем ответе.

Chris Charabaruk 31.05.2011 08:42

Я понимаю. Я просто подумал, поскольку в документации по Dictionary указано, что «Порядок, в котором возвращаются элементы, не определен» (msdn.microsoft.com/en-us/library/xfhwa508.aspx), возможно, это не лучшая структура данных для использования, когда вам не нужно столько времени на поиск, сколько иметь все остается в порядке.

StriplingWarrior 31.05.2011 20:00

StriplingWarrior: Я считаю, что LINQ учитывает это при использовании с Dictionary. Если вам это все еще неудобно, вы можете использовать SortedDictionary, но мой собственный опыт показывает, что в этом нет необходимости.

Chris Charabaruk 01.06.2011 06:44

используя Беглый 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();

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

import java.util.Date;

public class RelativeDateFormat {

 private static final long ONE_MINUTE = 60000L;
 private static final long ONE_HOUR = 3600000L;
 private static final long ONE_DAY = 86400000L;
 private static final long ONE_WEEK = 604800000L;

 public static String format(Date date) {

  long delta = new Date().getTime() - date.getTime();
  if (delta < 1L * ONE_MINUTE) {
   return toSeconds(delta) == 1 ? "one second ago" : toSeconds(delta)
     + " seconds ago";
  }
  if (delta < 2L * ONE_MINUTE) {
   return "one minute ago";
  }
  if (delta < 45L * ONE_MINUTE) {
   return toMinutes(delta) + " minutes ago";
  }
  if (delta < 90L * ONE_MINUTE) {
   return "one hour ago";
  }
  if (delta < 24L * ONE_HOUR) {
   return toHours(delta) + " hours ago";
  }
  if (delta < 48L * ONE_HOUR) {
   return "yesterday";
  }
  if (delta < 30L * ONE_DAY) {
   return toDays(delta) + " days ago";
  }
  if (delta < 12L * 4L * ONE_WEEK) {
   long months = toMonths(delta);
   return months <= 1 ? "one month ago" : months + " months ago";
  } else {
   long years = toYears(delta);
   return years <= 1 ? "one year ago" : years + " years ago";
  }
 }

 private static long toSeconds(long date) {
  return date / 1000L;
 }

 private static long toMinutes(long date) {
  return toSeconds(date) / 60L;
 }

 private static long toHours(long date) {
  return toMinutes(date) / 60L;
 }

 private static long toDays(long date) {
  return toHours(date) / 24L;
 }

 private static long toMonths(long date) {
  return toDays(date) / 30L;
 }

 private static long toYears(long date) {
  return toMonths(date) / 365L;
 }

}

Вопрос C# с тегами. Почему этот Код Java? IMHO, применяется только код C#

Kiquenet 06.03.2017 13:40

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

Код Javascript, выполняющий то же самое (по запросу):

function posted(t) {
    var now = new Date();
    var diff = parseInt((now.getTime() - Date.parse(t)) / 1000);
    if (diff < 60) { return 'less than a minute ago'; }
    else if (diff < 120) { return 'about a minute ago'; }
    else if (diff < (2700)) { return (parseInt(diff / 60)).toString() + ' minutes ago'; }
    else if (diff < (5400)) { return 'about an hour ago'; }
    else if (diff < (86400)) { return 'about ' + (parseInt(diff / 3600)).toString() + ' hours ago'; }
    else if (diff < (172800)) { return '1 day ago'; } 
    else {return (parseInt(diff / 86400)).toString() + ' days ago'; }
}

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

Версия для 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];
    }
}

На пару лет опоздал на вечеринку, но у меня было требование сделать это как в прошлые, так и в будущие даты, поэтому я объединил Джефф и Винсента в это. Это тройная феерия! :)

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

Учитывая, что мир и ее муж, похоже, публикуют образцы кода, вот что я написал некоторое время назад на основе пары этих ответов.

У меня была особая потребность в локализации этого кода. Итак, у меня есть два класса - Grammar, который определяет локализуемые термины, и FuzzyDateExtensions, который содержит набор методов расширения. Мне не нужно было иметь дело с будущими датами, поэтому я не пытаюсь справиться с ними с помощью этого кода.

Я оставил часть XMLdoc в источнике, но удалил большую часть (там, где они были бы очевидны) для краткости. Я также не включил сюда всех учеников:

public class Grammar
{
    /// <summary> Gets or sets the term for "just now". </summary>
    public string JustNow { get; set; }
    /// <summary> Gets or sets the term for "X minutes ago". </summary>
    /// <remarks>
    ///     This is a <see cref = "String.Format"/> pattern, where <c>{0}</c>
    ///     is the number of minutes.
    /// </remarks>
    public string MinutesAgo { get; set; }
    public string OneHourAgo { get; set; }
    public string HoursAgo { get; set; }
    public string Yesterday { get; set; }
    public string DaysAgo { get; set; }
    public string LastMonth { get; set; }
    public string MonthsAgo { get; set; }
    public string LastYear { get; set; }
    public string YearsAgo { get; set; }
    /// <summary> Gets or sets the term for "ages ago". </summary>
    public string AgesAgo { get; set; }

    /// <summary>
    ///     Gets or sets the threshold beyond which the fuzzy date should be
    ///     considered "ages ago".
    /// </summary>
    public TimeSpan AgesAgoThreshold { get; set; }

    /// <summary>
    ///     Initialises a new <see cref = "Grammar"/> instance with the
    ///     specified properties.
    /// </summary>
    private void Initialise(string justNow, string minutesAgo,
        string oneHourAgo, string hoursAgo, string yesterday, string daysAgo,
        string lastMonth, string monthsAgo, string lastYear, string yearsAgo,
        string agesAgo, TimeSpan agesAgoThreshold)
    { ... }
}

Класс FuzzyDateString содержит:

public static class FuzzyDateExtensions
{
    public static string ToFuzzyDateString(this TimeSpan timespan)
    {
        return timespan.ToFuzzyDateString(new Grammar());
    }

    public static string ToFuzzyDateString(this TimeSpan timespan,
        Grammar grammar)
    {
        return GetFuzzyDateString(timespan, grammar);
    }

    public static string ToFuzzyDateString(this DateTime datetime)
    {
        return (DateTime.Now - datetime).ToFuzzyDateString();
    }

    public static string ToFuzzyDateString(this DateTime datetime,
       Grammar grammar)
    {
        return (DateTime.Now - datetime).ToFuzzyDateString(grammar);
    }


    private static string GetFuzzyDateString(TimeSpan timespan,
       Grammar grammar)
    {
        timespan = timespan.Duration();

        if (timespan >= grammar.AgesAgoThreshold)
        {
            return grammar.AgesAgo;
        }

        if (timespan < new TimeSpan(0, 2, 0))    // 2 minutes
        {
            return grammar.JustNow;
        }

        if (timespan < new TimeSpan(1, 0, 0))    // 1 hour
        {
            return String.Format(grammar.MinutesAgo, timespan.Minutes);
        }

        if (timespan < new TimeSpan(1, 55, 0))    // 1 hour 55 minutes
        {
            return grammar.OneHourAgo;
        }

        if (timespan < new TimeSpan(12, 0, 0)    // 12 hours
            && (DateTime.Now - timespan).IsToday())
        {
            return String.Format(grammar.HoursAgo, timespan.RoundedHours());
        }

        if ((DateTime.Now.AddDays(1) - timespan).IsToday())
        {
            return grammar.Yesterday;
        }

        if (timespan < new TimeSpan(32, 0, 0, 0)    // 32 days
            && (DateTime.Now - timespan).IsThisMonth())
        {
            return String.Format(grammar.DaysAgo, timespan.RoundedDays());
        }

        if ((DateTime.Now.AddMonths(1) - timespan).IsThisMonth())
        {
            return grammar.LastMonth;
        }

        if (timespan < new TimeSpan(365, 0, 0, 0, 0)    // 365 days
            && (DateTime.Now - timespan).IsThisYear())
        {
            return String.Format(grammar.MonthsAgo, timespan.RoundedMonths());
        }

        if ((DateTime.Now - timespan).AddYears(1).IsThisYear())
        {
            return grammar.LastYear;
        }

        return String.Format(grammar.YearsAgo, timespan.RoundedYears());
    }
}

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

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

и методы округления такие (я включил RoundedMonths, так как он немного отличается):

public static int RoundedDays(this TimeSpan timespan)
{
    return (timespan.Hours > 12) ? timespan.Days + 1 : timespan.Days;
}

public static int RoundedMonths(this TimeSpan timespan)
{
    DateTime then = DateTime.Now - timespan;

    // Number of partial months elapsed since 1 Jan, AD 1 (DateTime.MinValue)
    int nowMonthYears = DateTime.Now.Year * 12 + DateTime.Now.Month;
    int thenMonthYears = then.Year * 12 + then.Month;                    

    return nowMonthYears - thenMonthYears;
}

Я надеюсь, что люди найдут это полезным и / или интересным: o)

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

Я бы предоставил для этого несколько удобных методов расширения и сделал бы код более читабельным. Во-первых, пара методов расширения для 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;
    }
}

Затем один для DateTime.

public static class DateTimeExtensions {

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

        return dateTime - delta;
    }
}

Теперь вы можете сделать что-то вроде следующего:

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

Я думаю, что уже есть несколько ответов, связанных с этим постом, но можно использовать его, который прост в использовании, как и плагин, а также легко читается программистами. Отправьте конкретную дату и получите ее значение в строковой форме:

public string RelativeDateTimeCount(DateTime inputDateTime)
{
    string outputDateTime = string.Empty;
    TimeSpan ts = DateTime.Now - inputDateTime;

    if (ts.Days > 7)
    { outputDateTime = inputDateTime.ToString("MMMM d, yyyy"); }

    else if (ts.Days > 0)
    {
        outputDateTime = ts.Days == 1 ? ("about 1 Day ago") : ("about " + ts.Days.ToString() + " Days ago");
    }
    else if (ts.Hours > 0)
    {
        outputDateTime = ts.Hours == 1 ? ("an hour ago") : (ts.Hours.ToString() + " hours ago");
    }
    else if (ts.Minutes > 0)
    {
        outputDateTime = ts.Minutes == 1 ? ("1 minute ago") : (ts.Minutes.ToString() + " minutes ago");
    }
    else outputDateTime = "few seconds ago";

    return outputDateTime;
}

public string getRelativeDateTime(DateTime date)
{
    TimeSpan ts = DateTime.Now - date;
    if (ts.TotalMinutes < 1)//seconds ago
        return "just now";
    if (ts.TotalHours < 1)//min ago
        return (int)ts.TotalMinutes == 1 ? "1 Minute ago" : (int)ts.TotalMinutes + " Minutes ago";
    if (ts.TotalDays < 1)//hours ago
        return (int)ts.TotalHours == 1 ? "1 Hour ago" : (int)ts.TotalHours + " Hours ago";
    if (ts.TotalDays < 7)//days ago
        return (int)ts.TotalDays == 1 ? "1 Day ago" : (int)ts.TotalDays + " Days ago";
    if (ts.TotalDays < 30.4368)//weeks ago
        return (int)(ts.TotalDays / 7) == 1 ? "1 Week ago" : (int)(ts.TotalDays / 7) + " Weeks ago";
    if (ts.TotalDays < 365.242)//months ago
        return (int)(ts.TotalDays / 30.4368) == 1 ? "1 Month ago" : (int)(ts.TotalDays / 30.4368) + " Months ago";
    //years ago
    return (int)(ts.TotalDays / 365.242) == 1 ? "1 Year ago" : (int)(ts.TotalDays / 365.242) + " Years ago";
}

Значения конверсии для дней в месяце и году были взяты из Google.

Вы можете попробовать это. Думаю, все будет работать правильно.

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

На Nuget также есть пакет под названием Humanizr, он действительно хорошо работает и находится в .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"

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

дружественное примечание: на .net 4.5 или выше не устанавливайте полный Humanizer ... только устанавливайте Humanizer.Core его часть .. потому что другие языковые пакеты не поддерживаются в этой версии

Ahmad 10.03.2017 21:50

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

Если вы хотите получить такой результат, как "2 days, 4 hours and 12 minutes ago", вам нужен временной интервал:

TimeSpan timeDiff = DateTime.Now-CreatedDate;

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

timeDiff.Days
timeDiff.Hours

так далее...

Вы можете использовать Расширение TimeAgo, как показано ниже:

public static string TimeAgo(this DateTime dateTime)
{
    string result = string.Empty;
    var timeSpan = DateTime.Now.Subtract(dateTime);
 
    if (timeSpan <= TimeSpan.FromSeconds(60))
    {
        result = string.Format("{0} seconds ago", timeSpan.Seconds);
    }
    else if (timeSpan <= TimeSpan.FromMinutes(60))
    {
        result = timeSpan.Minutes > 1 ? 
            String.Format("about {0} minutes ago", timeSpan.Minutes) :
            "about a minute ago";
    }
    else if (timeSpan <= TimeSpan.FromHours(24))
    {
        result = timeSpan.Hours > 1 ? 
            String.Format("about {0} hours ago", timeSpan.Hours) : 
            "about an hour ago";
    }
    else if (timeSpan <= TimeSpan.FromDays(30))
    {
        result = timeSpan.Days > 1 ? 
            String.Format("about {0} days ago", timeSpan.Days) : 
            "yesterday";
    }
    else if (timeSpan <= TimeSpan.FromDays(365))
    {
        result = timeSpan.Days > 30 ? 
            String.Format("about {0} months ago", timeSpan.Days / 30) : 
            "about a month ago";
    }
    else
    {
        result = timeSpan.Days > 365 ? 
            String.Format("about {0} years ago", timeSpan.Days / 365) : 
            "about a year ago";
    }
 
    return result;
}

Или используйте плагин jQuery с расширением Razor от Timeago.

Это моя функция, работает как шарм :)

public static string RelativeDate(DateTime theDate)
        {
            var span = DateTime.Now - theDate;
            if (span.Days > 365)
            {
                var years = (span.Days / 365);
                if (span.Days % 365 != 0)
                    years += 1;
                return $"about {years} {(years == 1 ? "year" : "years")} ago";
            }
            if (span.Days > 30)
            {
                var months = (span.Days / 30);
                if (span.Days % 31 != 0)
                    months += 1;
                return $"about {months} {(months == 1 ? "month" : "months")} ago";
            }
            if (span.Days > 0)
                return $"about {span.Days} {(span.Days == 1 ? "day" : "days")} ago";
            if (span.Hours > 0)
                return $"about {span.Hours} {(span.Hours == 1 ? "hour" : "hours")} ago";
            if (span.Minutes > 0)
                return $"about {span.Minutes} {(span.Minutes == 1 ? "minute" : "minutes")} ago";
            if (span.Seconds > 5)
                return $"about {span.Seconds} seconds ago";

            return span.Seconds <= 5 ? "about 5 seconds ago" : string.Empty;
        }

Мой способ намного проще. Вы можете настроить возвращаемые строки по своему усмотрению

    public static string TimeLeft(DateTime utcDate)
    {
        TimeSpan timeLeft = DateTime.UtcNow - utcDate;
        string timeLeftString = "";
        if (timeLeft.Days > 0)
        {
            timeLeftString += timeLeft.Days == 1 ? timeLeft.Days + " day" : timeLeft.Days + " days";
        }
        else if (timeLeft.Hours > 0)
        {
            timeLeftString += timeLeft.Hours == 1 ? timeLeft.Hours + " hour" : timeLeft.Hours + " hours";
        }
        else
        {
            timeLeftString += timeLeft.Minutes == 1 ? timeLeft.Minutes+" minute" : timeLeft.Minutes + " minutes";
        }
        return timeLeftString;
    }

турецкий локализованная версия ответа Винсента.

    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 + " saniye önce";

    if (delta < 45 * MINUTE)
        return ts.Minutes + " dakika önce";

    if (delta < 24 * HOUR)
        return ts.Hours + " saat önce";

    if (delta < 48 * HOUR)
        return "dün";

    if (delta < 30 * DAY)
        return ts.Days + " gün önce";

    if (delta < 12 * MONTH)
    {
        int months = Convert.ToInt32(Math.Floor((double)ts.Days / 30));
        return months + " ay önce";
    }
    else
    {
        int years = Convert.ToInt32(Math.Floor((double)ts.Days / 365));
        return years + " yıl önce";
    }

// Calculate total days in current year
int daysInYear;

for (var i = 1; i <= 12; i++)
    daysInYear += DateTime.DaysInMonth(DateTime.Now.Year, i);

// Past date
DateTime dateToCompare = DateTime.Now.Subtract(TimeSpan.FromMinutes(582));

// Calculate difference between current date and past date
double diff = (DateTime.Now - dateToCompare).TotalMilliseconds;

TimeSpan ts = TimeSpan.FromMilliseconds(diff);

var years = ts.TotalDays / daysInYear; // Years
var months = ts.TotalDays / (daysInYear / (double)12); // Months
var weeks = ts.TotalDays / 7; // Weeks
var days = ts.TotalDays; // Days
var hours = ts.TotalHours; // Hours
var minutes = ts.TotalMinutes; // Minutes
var seconds = ts.TotalSeconds; // Seconds

if (years >= 1)
    Console.WriteLine(Math.Round(years, 0) + " year(s) ago");
else if (months >= 1)
    Console.WriteLine(Math.Round(months, 0) + " month(s) ago");
else if (weeks >= 1)
    Console.WriteLine(Math.Round(weeks, 0) + " week(s) ago");
else if (days >= 1)
    Console.WriteLine(Math.Round(days, 0) + " days(s) ago");
else if (hours >= 1)
    Console.WriteLine(Math.Round(hours, 0) + " hour(s) ago");
else if (minutes >= 1)
    Console.WriteLine(Math.Round(minutes, 0) + " minute(s) ago");
else if (seconds >= 1)
    Console.WriteLine(Math.Round(seconds, 0) + " second(s) ago");

Console.ReadLine();

В способе, которым вы выполняете свою функцию DateTime при вычислении относительного времени от секунд до лет, попробуйте что-то вроде этого:

using System;

public class Program {
    public static string getRelativeTime(DateTime past) {
        DateTime now = DateTime.Today;
        string rt = "";
        int time;
        string statement = "";
        if (past.Second >= now.Second) {
            if (past.Second - now.Second == 1) {
                rt = "second ago";
            }
            rt = "seconds ago";
            time = past.Second - now.Second;
            statement = "" + time;
            return (statement + rt);
        }
        if (past.Minute >= now.Minute) {
            if (past.Second - now.Second == 1) {
                rt = "second ago";
            } else {
                rt = "minutes ago";
            }
            time = past.Minute - now.Minute;
            statement = "" + time;
            return (statement + rt);
        }
        // This process will go on until years
    }
    public static void Main() {
        DateTime before = new DateTime(1995, 8, 24);
        string date = getRelativeTime(before);
        Console.WriteLine("Windows 95 was {0}.", date);
    }
}

Не совсем работает, но если вы немного измените и отладите его, он, скорее всего, сработает.

«Однострочный», использующий деконструкцию и Linq для получения «n [наибольшая единица времени] назад»:

TimeSpan timeSpan = DateTime.Now - new DateTime(1234, 5, 6, 7, 8, 9);

(string unit, int value) = new Dictionary<string, int>
{
    {"year(s)", (int)(timeSpan.TotalDays / 365.25)}, //https://en.wikipedia.org/wiki/Year#Intercalation
    {"month(s)", (int)(timeSpan.TotalDays / 29.53)}, //https://en.wikipedia.org/wiki/Month
    {"day(s)", (int)timeSpan.TotalDays},
    {"hour(s)", (int)timeSpan.TotalHours},
    {"minute(s)", (int)timeSpan.TotalMinutes},
    {"second(s)", (int)timeSpan.TotalSeconds},
    {"millisecond(s)", (int)timeSpan.TotalMilliseconds}
}.First(kvp => kvp.Value > 0);

Console.WriteLine($"{value} {unit} ago");

Вы получаете 786 year(s) ago

С текущим годом и месяцем, например

TimeSpan timeSpan = DateTime.Now - new DateTime(2020, 12, 6, 7, 8, 9);

вы получаете 4 day(s) ago

С актуальной датой, например

TimeSpan timeSpan = DateTime.Now - DateTime.Now.Date;

вы получаете 9 hour(s) ago

Другие вопросы по теме