TimeSpan для дружественной библиотеки строк (С#)
Кто-нибудь знает о хорошей библиотеке (или фрагменте кода) для преобразования объекта TimeSpan в "дружественную" строку, например:
- Два года, три месяца и четыре дня.
- Неделя и два дня
(Это для системы истечения срока действия документа, где срок действия может составлять от нескольких дней до нескольких десятилетий)
Чтобы уточнить, скажем, у меня был TimeSpan с 7 днями, который должен печатать "1 неделя", 14 дней "2 недели", 366 дней "1 год и 1 день" и т.д. и т.д.
Ответы
Ответ 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.
Ответ 4
Вероятно, он не будет делать все, что вы ищете, но в v4 Microsoft будет внедрить IFormattable
в TimeSpan
.
Ответ 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();
Ответ 9
См. how-do-i-calculate-relative-time, спросил (пользователем номер 1) год назад, когда SO была молода и не публично.
Это было (вероятно) основой для отображения текущего возраста для вопросов и ответов SO.