Объект реального времени с .Years &.Months

Рассмотрим следующие два сценария: Сценарий 1). Сегодня 1 мая 2012 года и Сценарий 2). Сегодня 1 сентября 2012 года.

Теперь подумайте, что мы пишем на нашей веб-странице следующее о комментарии, который кто-то оставил: "Этот комментарий был написан 3 месяца и 12 дней назад". Количество дней в обоих этих сценариях ВСЕГДА будет отличаться, хотя утверждение будет точно таким же. В Сценарии 1 "3 месяца и 12 дней" будут равны 102 days. Однако в Сценарии 2 "3 месяца и 12 дней" будут 104 days!

Теперь, чтобы углубиться в мою точку зрения, давайте использовать другой пример и скажем, что кто-то оставил комментарий на нашем сайте 30 января 2013 года, а сегодня - 10 марта 2013 года. Наш реальный объект TimeSpan должен знать эту относительную дату, и может выяснить следующее:

  • В марте 10 дней,
  • Что есть 1 день в январе (с 30 по 31).
  • То, что месяц Фев - один месяц, независимо от того, сколько дней в нем (хотя это 28 дней).

Итак, это означало бы 10 дней + 1 день + 1 месяц, переведя на This comment was posted 1 Month and 11 Days ago.

Теперь, если вы использовали объект TimeSpan в стиле MS (или любой объект TimeSpan на любом языке), он дал бы вам количество дней с 30 января по 10 марта (39 дней), а так как объект TimeSpan не (базовая/начальная дата, которую мы вычтем, чтобы получить TimeSpan), если вы спросили, сколько месяцев и дней было, предполагается, что есть 30 дней в месяц или, что еще хуже, среднее значение больше чем за 30 дней, и вернуть остаток за несколько дней, чтобы добраться до 39 дней, он скажет вам, что это 1 месяц и 9 дней, и вы получите сообщение This comment was posted 1 Month and 9 Days ago. Помните, что оба эти сценария имеют одинаковую дату начала и ту же текущую/конечную дату, да, объект Microsoft TimeSpan, не позволяя нам сказать, что месяц Февраля 2013 года должен быть рассмотрен, дал нам совершенно другой TimeSpan, целых 2 дня. Это, по сути, солгало нам.

Проблема в том, что люди будут этому верить, и кто знает, какое восприятие они могут иметь, как их восприятие прошлого может измениться, а также решения и выбор жизни, которые они могут предпринять, пытаясь воссоздать события в прошлом в их собственных умах, хотя никогда не замечает или не понимает недостаток и неотъемлемый отказ от представления времени, которое так распространено повсюду сегодня. Они не поймут, что языки программирования не понимают (или не заботятся) о том, что в прошлом месяце в нем было 31 день, как это было 30, 29 или 28 или наоборот, и это добавляется при увеличении TimeSpan.

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

К моему разочарованию я заметил, что нет реального объекта timepan, если вы получаете промежуток времени, и делаете .years или .months, вы ничего не получите, вы получите только .days и ниже, потому что Объект timeSpan не несет ничего, чтобы сообщить ему, в каком месяце или году был создан timeSpan. Поэтому он никогда не узнает, сколько месяцев прошло с тех пор, как дни в каждом месяце меняются в течение года и даже в течение високосного года.

В ответ на это, я опубликую функцию, которую я разработал, чтобы получить ТОЧНЫЕ показания и иметь возможность возвращать на моей веб-странице ASP.NET такие вещи, как следующее:

Отправлено 4 года, 3 месяца, 14 дней, 15 часов, 18 минут и 24 секунд назад

Я понял, что будет & hellip;

timeSpan.GetActualNumberOf[Months/Days/Hours/etc] (базовая дата должна быть предоставлена, конечно)

& hellip; типа в этом типе данных, но не было.

Все, что вам действительно нужно сделать, это создать еще одно свойство объекта timeSpan, чтобы дать ему базовую дату, на которой была рассчитана разница, тогда эта прекрасная строка будет легко вычисляться, а .year и .month будет существовать!

ОБНОВЛЕНИЕ: я значительно расширил и обновил свои официальные ответы и данные об использовании кода в моем ответе ниже, 100% рабочий ответ и код (в полном объеме), точное и точное относительное время/даты, без приближений - спасибо.

Ответ 1

Вот главный ответ с кодом, обратите внимание, что вы можете получить любое количество дат/времени, секунды, минуты, минуты, минуты и дни, в любом месте до года (что будет содержать 6 частей/сегментов). Если вы укажете две верхние и более года, она вернется "1 год и 3 месяца назад" и не вернет остальных, потому что вы запросили два сегмента. если ему всего несколько часов, тогда он вернется только "2 часа и 1 минута назад". Конечно, те же правила применяются, если вы укажете 1, 2, 3, 4, 5 или 6 segmets (максимум на 6, поскольку секунды, минуты, часы, дни, месяцы, годы составляют только 6 типов). Он также будет исправлять проблемы с грамматикой, такие как "минуты" и "минута", в зависимости от того, будет ли она 1 минута или более, одинаковой для всех типов, а генерируемая строка будет всегда грамматически корректной.

Вот несколько примеров использования: bAllowSegments определяет количество сегментов для отображения... то есть: если 3, то возвращаемая строка будет (в качестве примера)... "3 years, 2 months and 13 days" (не будет включать в себя часы, минуты и секунды, когда вернутся 3 категории времени), если, однако, дата была более новой датой, например, что-то несколько дней назад, указав одни и те же сегменты (3), вместо этого вернет "4 days, 1 hour and 13 minutes ago", поэтому это учитывает все!

если bAllowSegments равно 2, он вернет "3 years and 2 months", и если 6 (максимальное значение) вернет "3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds", но напомните, что он будет NEVER RETURN что-то вроде этого "0 years, 0 months, 0 days, 3 hours, 2 minutes and 13 seconds ago", поскольку он понимает, что нет данных даты в верхних 3 сегментах и ​​игнорирует их, даже если вы укажете 6 сегментов, так что не беспокойтесь:). Конечно, если в нем есть сегмент с 0, он учитывает это при формировании строки и будет отображаться как "3 days and 4 seconds ago" и игнорирует часть "0 часов"! Наслаждайтесь и прокомментируйте, если хотите.

 Public Function RealTimeUntilNow(ByVal dt As DateTime, Optional ByVal bAllowSegments As Byte = 2) As String
  ' bAllowSegments identifies how many segments to show... ie: if 3, then return string would be (as an example)...
  ' "3 years, 2 months and 13 days" the top 3 time categories are returned, if bAllowSegments is 2 it would return
  ' "3 years and 2 months" and if 6 (maximum value) would return "3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds"
  Dim rYears, rMonths, rDays, rHours, rMinutes, rSeconds As Int16
  Dim dtNow = DateTime.Now
  Dim daysInBaseMonth = Date.DaysInMonth(dt.Year, dt.Month)

  rYears = dtNow.Year - dt.Year
  rMonths = dtNow.Month - dt.Month
  If rMonths < 0 Then rMonths += 12 : rYears -= 1 ' add 1 year to months, and remove 1 year from years.
  rDays = dtNow.Day - dt.Day
  If rDays < 0 Then rDays += daysInBaseMonth : rMonths -= 1
  rHours = dtNow.Hour - dt.Hour
  If rHours < 0 Then rHours += 24 : rDays -= 1
  rMinutes = dtNow.Minute - dt.Minute
  If rMinutes < 0 Then rMinutes += 60 : rHours -= 1
  rSeconds = dtNow.Second - dt.Second
  If rSeconds < 0 Then rSeconds += 60 : rMinutes -= 1

  ' this is the display functionality
  Dim sb As StringBuilder = New StringBuilder()
  Dim iSegmentsAdded As Int16 = 0

  If rYears > 0 Then sb.Append(rYears) : sb.Append(" year" & If(rYears <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rMonths > 0 Then sb.AppendFormat(rMonths) : sb.Append(" month" & If(rMonths <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rDays > 0 Then sb.Append(rDays) : sb.Append(" day" & If(rDays <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rHours > 0 Then sb.Append(rHours) : sb.Append(" hour" & If(rHours <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rMinutes > 0 Then sb.Append(rMinutes) : sb.Append(" minute" & If(rMinutes <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rSeconds > 0 Then sb.Append(rSeconds) : sb.Append(" second" & If(rSeconds <> 1, "s", "") & "") : iSegmentsAdded += 1

parseAndReturn:

  ' if the string is entirely empty, that means it was just posted so its less than a second ago, and an empty string getting passed will cause an error
  ' so we construct our own meaningful string which will still fit into the "Posted * ago " syntax...

  If sb.ToString = "" Then sb.Append("less than 1 second")

  Return ReplaceLast(sb.ToString.TrimEnd(" ", ",").ToString, ",", " and")

 End Function

Конечно, вам понадобится функция "ReplaceLast", которая берет исходную строку и аргумент, определяющий, что нужно заменить, и другой аргумент, определяющий, что вы хотите заменить, и он заменяет только последнее событие из этой строки... я включил мой, если у вас его нет или он не хочет его реализовывать, так что вот он, он будет работать "как есть" без каких-либо изменений. Я знаю, что функция reverseit больше не нужна (существует в .net), но функции ReplaceLast и ReverseIt переносятся с дней pre -.net, поэтому, пожалуйста, извините, как это датируется (все еще работает 100% tho, используется em более десяти лет, может гарантировать, что они являются ошибками бесплатно)...:). Кроме того, если вы используете VB6, вы можете использовать StrReverse (обертывая его вокруг строки, расширенной с помощью метода расширения .ReverseIt), вместо использования функции ReverseIt() (предоставляемой как метод расширения). Таким образом, вместо выполнения sReplacable.ReverseIt вы бы сделали StrReverse (sReplacable), поскольку StrReverse() является встроенной функцией VB6 (и делает то же самое, меняет данную строку и не делает ничего больше). Если вы используете StrReverse() вместо моей общей функции ReverseIt, не стесняйтесь удалять функцию/расширение ReverseIt. Функция StrReverse() должна быть доступна в .NET до тех пор, пока вы импортируете устаревшую библиотеку ms-visualbasic-dll. Не имеет никакого значения в любом случае, я написал ReverseIt(), прежде чем я даже знаю, что функция StrReverse() существовала и использовала ее с тех пор по привычке (нет реальной причины использовать мой, а не встроенную общую функцию StrReverse) - на самом деле, я уверен, что StrReverse (или аналогичная, более новая .NET-версия функции реверсирования строк) будет написана более эффективно:). веселит.

<Extension()> _ 
Public Function ReplaceLast(ByVal sReplacable As String, ByVal sReplaceWhat As String, ByVal sReplaceWith As String) As String 
    ' let empty string arguments run, incase we dont know if we are sending and empty string or not. 
    sReplacable = sReplacable.ReverseIt 
    sReplacable = Replace(sReplacable, sReplaceWhat.ReverseIt, sReplaceWith.ReverseIt, , 1) ' only does first item on reversed version! 
    Return sReplacable.ReverseIt.ToString 
End Function 

<Extension()> _ 
Public Function ReverseIt(ByVal strS As String, Optional ByVal n As Integer = -1) As String 
    Dim strTempX As String = "", intI As Integer 

    If n > strS.Length Or n = -1 Then n = strS.Length 

    For intI = n To 1 Step -1 
        strTempX = strTempX + Mid(strS, intI, 1) 
    Next intI 

    ReverseIt = strTempX + Right(strS, Len(strS) - n) 

End Function 

Ответ 2

Здесь, как добавить некоторые методы расширения для этого с помощью С#, используя средние значения:

public static class TimeSpanExtensions
{
    public static int GetYears(this TimeSpan timespan)
    {
        return (int)(timespan.Days/365.2425);
    }
    public static int GetMonths(this TimeSpan timespan)
    {
        return (int)(timespan.Days/30.436875);
    }
}

Ответ 3

То, что вы ищете, действительно не соответствует TimeSpan. TimeSpan представляет интервал как счетчик тиков без учета базы DateTime или Calendar.

Новый тип DateDifference может иметь больше смысла здесь: метод конструктора или factory, использующий базу DateTime, целевую DateTime и необязательно a Calendar (по умолчанию для CultureInfo.CurrentCulture) с который вычисляет различные компоненты разности (годы, месяцы и т.д.).

РЕДАКТИРОВАТЬ: Мне кажется, что Noda Time может иметь нужные вам инструменты для этого — Period class "[r] представляет период времени, выраженный в хронологических терминах человека: часы, дни, недели, месяцы и т.д.", и в частности Period.Between(then, now, PeriodUnits.AllUnits), по-видимому, является точным вычислением, который вы запрашиваете для — но это обязательно гораздо более сложный класс, чем TimeSpan. Страница Key Concepts в вики Node Time объясняет, как "люди делают время грязным":

Оставляя в стороне сложные хиты астрономии и относительности, человечество до сих пор трудно договориться. Если мы все использовали тики из Unix эпоху говорить о времени, не было бы необходимости в библиотеке, как Время Ноды.

Но нет, нам нравится говорить годами, месяцами, днями, неделями - и для некоторых причина, по которой нам нравится 12 часов (что смутно доходит до 1 часа дня), чтобы быть грубо время, когда солнце самое высокое... так что у нас есть часовые пояса.

Не только это, но мы не все согласны с тем, сколько месяцев есть. Различные цивилизации придумали разные способы расщепления за год, и разные цифры за годы, чтобы начать. Эти являются календарными системами.

Ответ 4

Хорошо, лучше поздно, тогда я не предполагаю;)

Функция С#, предоставляющая все

И это моя измененная версия:

private string GetElapsedTime(DateTime from_date, DateTime to_date) {
int years;
int months;
int days;
int hours;
int minutes;
int seconds;
int milliseconds;

//------------------
// Handle the years.
//------------------
years = to_date.Year - from_date.Year;

//------------------------
// See if we went too far.
//------------------------
DateTime test_date = from_date.AddMonths(12 * years);

if (test_date > to_date)
{
    years--;
    test_date = from_date.AddMonths(12 * years);
}

//--------------------------------
// Add months until we go too far.
//--------------------------------
months = 0;

while (test_date <= to_date)
{
    months++;
    test_date = from_date.AddMonths(12 * years + months);
}

months--;

//------------------------------------------------------------------
// Subtract to see how many more days, hours, minutes, etc. we need.
//------------------------------------------------------------------
from_date = from_date.AddMonths(12 * years + months);
TimeSpan remainder = to_date - from_date;
days = remainder.Days;
hours = remainder.Hours;
minutes = remainder.Minutes;
seconds = remainder.Seconds;
milliseconds = remainder.Milliseconds;

return (years > 0 ? years.ToString() + " years " : "") +
       (months > 0 ? months.ToString() + " months " : "") +
       (days > 0 ? days.ToString() + " days " : "") +
       (hours > 0 ? hours.ToString() + " hours " : "") +
       (minutes > 0 ? minutes.ToString() + " minutes " : "");}

Ответ 5

Я бы сказал, что текущий TimeSpan - это реальный объект времени, т.е. количество времени между 1 января 2008 года 1:31 и 3 февраля 2008 года в 6:45 утра совпадает с количеством времени между 5 февраля 2008 года в 13:45 и 9 марта 2008 года в 6:59 вечера. То, что вы ищете, в действительности представляет собой разницу между двумя датами.

Что касается .MakeMagicHappen.gimmeSomethingPretty.surelyMShasThoughtAboutThisDilema для удовлетворения конкретных потребностей вашей системы, то почему люди нанимают вас в качестве программиста. Если структура, которую вы используете, делает абсолютно все, ваша компания просто сможет нажать одну кнопку, и их система выйдет полностью сформированной, и вы окажетесь на линии безработицы вместе с остальными нами программистами.

Ответ 6

Используя класс .Net 4.5 и CultureInfo, можно добавить месяцы и годы к указанной дате.

DateTime datetime = DateTime.UtcNow;
int years = 15;
int months = 7;

DateTime yearsAgo = CultureInfo.InvariantCulture.Calendar.AddYears(datetime, -years);
DateTime monthsInFuture = CultureInfo.InvariantCulture.Calendar.AddMonths(datetime, months);

С тех пор, как много набрав, я предпочитаю создавать методы расширения:

public static DateTime AddYears(this DateTime datetime, int years)
{
    return CultureInfo.InvariantCulture.Calendar.AddYears(datetime, years);
}

public static DateTime AddMonths(this DateTime datetime, int months)
{
    return CultureInfo.InvariantCulture.Calendar.AddMonths(datetime, months);
}

DateTime yearsAgo = datetime.AddYears(-years);
DateTime monthsInFuture = datetime.AddMonths(months);

Ответ 7

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

public static string ElapsedTime(DateTime dtEvent)
{
    TimeSpan TS = DateTime.Now - dtEvent;

    int intYears = TS.Days / 365;
    int intMonths = TS.Days / 30;
    int intDays = TS.Days;
    int intHours = TS.Hours;
    int intMinutes = TS.Minutes;
    int intSeconds = TS.Seconds;

    if (intYears > 0) return String.Format("há {0} {1}", intYears, (intYears == 1) ? "ano" : "anos");
    else if (intMonths > 0) return String.Format("há {0} {1}", intMonths, (intMonths == 1) ? "mês" : "meses");
    else if (intDays > 0) return String.Format("há {0} {1}", intDays, (intDays == 1) ? "dia" : "dias");
    else if (intHours > 0) return String.Format("há ± {0} {1}", intHours, (intHours == 1) ? "hora" : "horas");
    else if (intMinutes > 0) return String.Format("há ± {0} {1}", intMinutes, (intMinutes == 1) ? "minuto" : "minutos");
    else if (intSeconds > 0) return String.Format("há ± {0} {1}", intSeconds, (intSeconds == 1) ? "segundo" : "segundos");
    else
    {
        return String.Format("em {0} às {1}", dtEvent.ToShortDateString(), dtEvent.ToShortTimeString());
    }
}

Ответ 8

Я взял принятый ответ и преобразовал его из VB.Net в С#, а также сделал несколько изменений/улучшений. Я избавился от инверсий строк, которые использовались для замены последнего экземпляра строки, и использовал метод расширения, который более непосредственно находит и заменяет последний экземпляр строки.

Пример того, как вызвать метод:

PeriodBetween(#2/28/2011#, DateTime.UtcNow, 6)

Основной метод:

public static string PeriodBetween(DateTime then, DateTime now, byte numberOfPeriodUnits = 2)
{
    // Translated from VB.Net to C# from: https://stackoverflow.com/a/1956265

    // numberOfPeriodUnits identifies how many time period units to show.
    // If numberOfPeriodUnits = 3, function would return:
    //      "3 years, 2 months and 13 days"
    // If numberOfPeriodUnits = 2, function would return:
    //      "3 years and 2 months"
    // If numberOfPeriodUnits = 6, (maximum value), function would return:
    //      "3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds"

    if (numberOfPeriodUnits > 6 || numberOfPeriodUnits < 1)
    {
        throw new ArgumentOutOfRangeException($"Parameter [{nameof(numberOfPeriodUnits)}] is out of bounds. Valid range is 1 to 6.");
    }

    short Years = 0;
    short Months = 0;
    short Days = 0;
    short Hours = 0;
    short Minutes = 0;
    short Seconds = 0;
    short DaysInBaseMonth = (short)(DateTime.DaysInMonth(then.Year, then.Month));

    Years = (short)(now.Year - then.Year);

    Months = (short)(now.Month - then.Month);
    if (Months < 0)
    {
        Months += 12;
        Years--; // add 1 year to months, and remove 1 year from years.
    }

    Days = (short)(now.Day - then.Day);
    if (Days < 0)
    {
        Days += DaysInBaseMonth;
        Months--;
    }

    Hours = (short)(now.Hour - then.Hour);
    if (Hours < 0)
    {
        Hours += 24;
        Days--;
    }

    Minutes = (short)(now.Minute - then.Minute);
    if (Minutes < 0)
    {
        Minutes += 60;
        Hours--;
    }

    Seconds = (short)(now.Second - then.Second);
    if (Seconds < 0)
    {
        Seconds += 60;
        Minutes--;
    }

    // This is the display functionality.
    StringBuilder TimePeriod = new StringBuilder();
    short NumberOfPeriodUnitsAdded = 0;

    if (Years > 0)
    {
        TimePeriod.Append(Years);
        TimePeriod.Append(" year" + (Years != 1 ? "s" : "") + ", ");
        NumberOfPeriodUnitsAdded++;
    }

    if (numberOfPeriodUnits == NumberOfPeriodUnitsAdded)
    {
        goto ParseAndReturn;
    }

    if (Months > 0)
    {
        TimePeriod.AppendFormat(Months.ToString());
        TimePeriod.Append(" month" + (Months != 1 ? "s" : "") + ", ");
        NumberOfPeriodUnitsAdded++;
    }

    if (numberOfPeriodUnits == NumberOfPeriodUnitsAdded)
    {
        goto ParseAndReturn;
    }

    if (Days > 0)
    {
        TimePeriod.Append(Days);
        TimePeriod.Append(" day" + (Days != 1 ? "s" : "") + ", ");
        NumberOfPeriodUnitsAdded++;
    }

    if (numberOfPeriodUnits == NumberOfPeriodUnitsAdded)
    {
        goto ParseAndReturn;
    }

    if (Hours > 0)
    {
        TimePeriod.Append(Hours);
        TimePeriod.Append(" hour" + (Hours != 1 ? "s" : "") + ", ");
        NumberOfPeriodUnitsAdded++;
    }

    if (numberOfPeriodUnits == NumberOfPeriodUnitsAdded)
    {
        goto ParseAndReturn;
    }

    if (Minutes > 0)
    {
        TimePeriod.Append(Minutes);
        TimePeriod.Append(" minute" + (Minutes != 1 ? "s" : "") + ", ");
        NumberOfPeriodUnitsAdded++;
    }

    if (numberOfPeriodUnits == NumberOfPeriodUnitsAdded)
    {
        goto ParseAndReturn;
    }

    if (Seconds > 0)
    {
        TimePeriod.Append(Seconds);
        TimePeriod.Append(" second" + (Seconds != 1 ? "s" : "") + "");
        NumberOfPeriodUnitsAdded++;
    }

    ParseAndReturn:
    // If the string is empty, that means the datetime is less than a second in the past.
    // An empty string being passed will cause an error, so we construct our own meaningful
    // string which will still fit into the "Posted * ago " syntax.

    if (TimePeriod.ToString() == "")
    {
        TimePeriod.Append("less than 1 second");
    }

    return TimePeriod.ToString().TrimEnd(' ', ',').ToString().ReplaceLast(",", " and");
}

Метод расширения ReplaceLast:

public static string ReplaceLast(this string source, string search, string replace)
{
    int pos = source.LastIndexOf(search);

    if (pos == -1)
    {
        return source;
    }

    return source.Remove(pos, search.Length).Insert(pos, replace);
}