Подтвердить что ты не робот

TimeSpan для дружественной библиотеки строк (С#)

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

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

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

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

4b9b3361

Ответ 1

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

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 дня? Не могли бы вы сказать, что срок действия документа истекает через год или через 5 месяцев? Просто возьмите самую большую единицу и скажите над n этой единицы.

Ответ 2

Я просто наткнулся на этот вопрос, потому что хотел сделать подобное. После некоторого googling я все еще не нашел то, что хотел: отобразить временную шкалу в виде "закругленной" моды. Я имею в виду: когда какое-то событие занимало несколько дней, не всегда имеет смысл отображать миллисекунды. Однако, когда это заняло несколько минут, возможно. И в этом случае я не хочу показывать 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

Костюм, посмотрим, подходит ли он вам.

Ответ 3

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

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

UPDATE

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

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

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

Кажется, для меня намного лучший выбор UX.

Ответ 5

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

Ответ 6

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

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

Тесты;

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

Ответ 7

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

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

Ответ 8

Да! Мне нужно было то же самое много раз, и теперь я создаю свой пакет onw и публикую его в Nuget. Вы можете использовать его. Имя пакета EstecheAssemblies

Это легко реализовать:

using EstecheAssemblies;  

var date = new DateTime("2019-08-08 01:03:21");  
var text = date.ToFriendlyTimeSpan();