TimeSpan в дружественную библиотеку строк (C #) - PullRequest
10 голосов
/ 16 июля 2009

Кто-нибудь знает хорошую библиотеку (или фрагмент кода) для преобразования объекта TimeSpan в «дружественную» строку, такую ​​как:

  • Два года, три месяца и четыре дня
  • Одна неделя и два дня

(Это для системы истечения срока действия документа, где срок действия может быть от нескольких дней до нескольких десятилетий)

Просто чтобы уточнить, скажем, у меня был TimeSpan с 7 днями, который должен печатать "1 неделя", 14 дней "2 недели", 366 дней "1 год и 1 день" и т. Д. И т. Д.

Ответы [ 8 ]

15 голосов
/ 26 августа 2011

Я просто наткнулся на этот вопрос, потому что я хотел сделать подобное. После некоторого поиска в Google я все еще не нашел то, что хотел: отображать временную шкалу в некотором роде «округло». Я имею в виду: когда какое-то событие занимало несколько дней, не всегда имеет смысл отображать миллисекунды. Однако, когда это заняло минуты, это, вероятно, так и есть. И в этом случае я не хочу отображать 0 дней и 0 часов. Итак, я хочу параметризовать количество соответствующих частей временного интервала для отображения. Это привело к следующему коду:

public static class TimeSpanExtensions
{
    private enum TimeSpanElement
    {
        Millisecond,
        Second,
        Minute,
        Hour,
        Day
    }

    public static string ToFriendlyDisplay(this TimeSpan timeSpan, int maxNrOfElements)
    {
        maxNrOfElements = Math.Max(Math.Min(maxNrOfElements, 5), 1);
        var parts = new[]
                        {
                            Tuple.Create(TimeSpanElement.Day, timeSpan.Days),
                            Tuple.Create(TimeSpanElement.Hour, timeSpan.Hours),
                            Tuple.Create(TimeSpanElement.Minute, timeSpan.Minutes),
                            Tuple.Create(TimeSpanElement.Second, timeSpan.Seconds),
                            Tuple.Create(TimeSpanElement.Millisecond, timeSpan.Milliseconds)
                        }
                                    .SkipWhile(i => i.Item2 <= 0)
                                    .Take(maxNrOfElements);

        return string.Join(", ", parts.Select(p => string.Format("{0} {1}{2}", p.Item2, p.Item1, p.Item2 > 1 ? "s" : string.Empty)));
    }
}

Пример (LinqPad):

new TimeSpan(1,2,3,4,5).ToFriendlyDisplay(3).Dump();
new TimeSpan(0,5,3,4,5).ToFriendlyDisplay(3).Dump();

Отображение:

1 Day, 2 Hours, 3 Minutes
5 Hours, 3 Minutes, 4 Seconds

Подходит мне, посмотрим, подходит ли оно вам.

14 голосов
/ 16 июля 2009

Не полнофункциональная реализация, но она должна достаточно приблизить вас.

DateTime dtNow = DateTime.Now;
DateTime dtYesterday = DateTime.Now.AddDays(-435.0);
TimeSpan ts = dtNow.Subtract(dtYesterday);

int years = ts.Days / 365; //no leap year accounting
int months = (ts.Days % 365) / 30; //naive guess at month size
int weeks = ((ts.Days % 365) % 30) / 7;
int days = (((ts.Days % 365) % 30) % 7);

StringBuilder sb = new StringBuilder();
if(years > 0)
{
    sb.Append(years.ToString() + " years, ");
}
if(months > 0)
{
    sb.Append(months.ToString() + " months, ");
}
if(weeks > 0)
{
    sb.Append(weeks.ToString() + " weeks, ");
}
if(days > 0)
{
    sb.Append(days.ToString() + " days.");
}
string FormattedTimeSpan = sb.ToString();

В конце концов, действительно ли вам нужно, чтобы кто-то знал, что срок действия документа истекает ровно через 1 год, 5 месяцев, 2 недели и 3 дня? Не могли бы вы сказать им, что срок действия документа истекает через 1 год или через 5 месяцев? Просто возьмите самую большую единицу и скажите более n этой единицы.

3 голосов
/ 16 июля 2009

Объект TimeSpan имеет свойства Days, Hours, Minutes и Seconds, поэтому нетрудно создать фрагмент, который форматирует эти значения в дружественную строку.

К сожалению, Дни - самое большое значение. Что-нибудь более длинное, и вам придется начать беспокоиться о днях в месяце на каждый год ... и т. Д. По моему мнению, вам лучше остановиться на несколько дней (дополнительные усилия не стоят выигрыша).

UPDATE

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

Понятно, но действительно ли этот документ истекает через 10 лет, 3 месяца, 21 день, 2 часа и 30 минут "более полезным или менее глупым? Если бы это было до меня, так как ни одно из представлений не кажется очень полезным ...

Я бы оставил время до истечения срока, пока дата не станет достаточно близкой (30 или 60 дней, если вы беспокоитесь о получении документа обновляется).

Мне кажется, выбор UX намного лучше.

1 голос
/ 16 июля 2009

Это, вероятно, не будет делать все, что вы ищете, но в версии 4 Microsoft будет реализовывать IFormattable на TimeSpan.

0 голосов
/ 28 мая 2019

Вот мое решение для этого. Он основан на других ответах в этой теме, с добавленной поддержкой года и месяца, как было запрошено в исходном вопросе (и это было то, что мне было нужно)

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

Тесты *; 1005 *

[Test]
public void ToFriendlyDuration_produces_expected_result()
{
    new DateTime(2019, 5, 28).ToFriendlyDuration(null).Should().Be("Until further notice");
    new DateTime(2019, 5, 28).ToFriendlyDuration(new DateTime(2020, 5, 28)).Should().Be("1 year");
    new DateTime(2019, 5, 28).ToFriendlyDuration(new DateTime(2021, 5, 28)).Should().Be("2 years");
    new DateTime(2019, 5, 28).ToFriendlyDuration(new DateTime(2021, 8, 28)).Should().Be("2 years, 3 months");
    new DateTime(2019, 5, 28).ToFriendlyDuration(new DateTime(2019, 8, 28)).Should().Be("3 months");
    new DateTime(2019, 5, 28).ToFriendlyDuration(new DateTime(2019, 8, 31)).Should().Be("3 months, 3 days");
    new DateTime(2019, 5, 1).ToFriendlyDuration(new DateTime(2019, 5, 31)).Should().Be("30 days");
    new DateTime(2010, 5, 28).ToFriendlyDuration(new DateTime(2020, 8, 28)).Should().Be("10 years, 3 months");
    new DateTime(2010, 5, 28).ToFriendlyDuration(new DateTime(2020, 5, 29)).Should().Be("10 years, 1 day");
}

Осуществление;

private class TermAndValue
{
    public TermAndValue(string singular, string plural, int value)
    {
        Singular = singular;
        Plural = plural;
        Value = value;
    }

    public string Singular { get; }
    public string Plural { get; }
    public int Value { get; }
    public string Term => Value > 1 ? Plural : Singular;
}

public static string ToFriendlyDuration(this DateTime value, DateTime? endDate, int maxNrOfElements = 2)
{
    if (!endDate.HasValue)
        return "Until further notice";

    var extendedTimeSpan = new TimeSpanWithYearAndMonth(value, endDate.Value);
    maxNrOfElements = Math.Max(Math.Min(maxNrOfElements, 5), 1);
    var termsAndValues = new[]
    {
        new TermAndValue("year", "years", extendedTimeSpan.Years),
        new TermAndValue("month", "months", extendedTimeSpan.Months),
        new TermAndValue("day", "days", extendedTimeSpan.Days),
        new TermAndValue("hour", "hours", extendedTimeSpan.Hours),
        new TermAndValue("minute", "minutes", extendedTimeSpan.Minutes)
    };

    var parts = termsAndValues.Where(i => i.Value != 0).Take(maxNrOfElements);

    return string.Join(", ", parts.Select(p => $"{p.Value} {p.Term}"));
}

internal class TimeSpanWithYearAndMonth
{
    internal TimeSpanWithYearAndMonth(DateTime startDate, DateTime endDate)
    {
        var span = endDate - startDate;

        Months = 12 * (endDate.Year - startDate.Year) + (endDate.Month - startDate.Month);
        Years = Months / 12;
        Months -= Years * 12;

        if (Months == 0 && Years == 0)
        {
            Days = span.Days;
        }
        else
        {
            var startDateExceptYearsAndMonths = startDate.AddYears(Years);
            startDateExceptYearsAndMonths = startDateExceptYearsAndMonths.AddMonths(Months);
            Days = (endDate - startDateExceptYearsAndMonths).Days;
        }

        Hours = span.Hours;
        Minutes = span.Minutes;
    }

    public int Minutes { get; }
    public int Hours { get; }
    public int Days { get; }
    public int Years { get; }
    public int Months { get; }
}
0 голосов
/ 26 мая 2015

Существует также проект Humanizer , который выглядит очень интересно, может сделать это и многое другое.

0 голосов
/ 16 июля 2009

См. How-Do-I-Calculate-относительное время , спросили ( по номеру пользователя 1 ) год назад, когда SO был молодым и не публичным. Это было (вероятно) основой для отображения текущего возраста для SO вопросов и ответов.

0 голосов
/ 16 июля 2009

Для форматирования любого периода, превышающего 1 день (например, месяц / год / десятилетие и т. Д.), Объекта Timespan недостаточно.

Предположим, ваш временной интервал составляет 35 дней, тогда с 1 апреля вы получите один месяц и пять дней, тогда как с 1 декабря вы получите один месяц и четыре дня.

...