Pergunta

Alguém sabe de uma biblioteca boa (ou trecho de código) para converter um objeto TimeSpan para uma string "amigável", tais como:

  • Dois anos, três meses e quatro dias
  • Uma semana e dois dias

(É para um sistema de documento de validade, onde o termo poderia ser qualquer coisa de poucos dias a várias décadas)

Só para esclarecer, dizer que eu tinha um TimeSpan com 7 dias, que deve imprimir "uma semana", 14 dias "2 semanas", 366 dias "1 ano e um dia", etc etc.

Foi útil?

Solução

Nem uma implementação inteiramente caracterizado, mas deve chegar perto o suficiente.

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();

No final, você realmente precisa de deixar alguém saber um documento vai expirar exatamente 1 ano, 5 meses, 2 semanas e 3 dias a partir de agora? você não pode conviver com dizendo-lhes o documento irá expirar mais de 1 ano a partir de agora, ou mais de 5 meses de agora? Basta ter a maior unidade e dizer sobre n daquela unidade.

Outras dicas

Eu só tropeçou em esta pergunta, porque eu queria fazer uma coisa semelhante. Depois de algum googling eu ainda não encontrou o que eu queria: exibir um período de tempo em uma espécie de forma "arredondada". Quero dizer: quando algum evento levou vários dias, nem sempre fazem sentido para exibir os milissegundos. No entanto, quando ele levou minutos, ele provavelmente faz. E, nesse caso, eu não quero 0 dias e 0 horas para ser exibido. Então, eu quero parametrizar o número de peças TimeSpan relevantes a serem exibidos. Isto resultou em este pedaço de código:

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

Exemplo (LinqPad):

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

Apresenta:

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

Suits me, veja se ele combina com você.

O objeto TimeSpan tem dias, horas, minutos e segundos propriedades sobre ele, por isso não seria muito difícil fazer um trecho que formatos esses valores para uma string amigável.

Infelizmente Dias é o maior valor. Nada mais do que isso e você vai ter que começar a se preocupar dias em um mês por cada ano ... etc. Você é melhor fora parar em dias em minha opinião (o esforço adicional não parece vale o ganho).

Atualizar

... eu percebi que eu trago este acima do meu próprio comentário:

compreensível, mas é "Este documento expira em 10 anos, 3 meses, 21 dias, 2 horas e 30 minutos" muito mais útil ou menos bobo? Se fosse por mim, uma vez que nem representação parece muito útil ...
eu deixaria fora do período de tempo de validade até a data tem razoavelmente perto (30 ou 60 dias, talvez, se você está preocupado sobre a obtenção de o documento atualizado).
Parece uma escolha muito melhor UX para mim.

Provavelmente não vai fazer tudo o que você está procurando, mas em v4 Microsoft será implementação IFormattable em TimeSpan .

Existe agora também o projeto Humanizer que se parece muito interessantes que podem fazer isso e de maneira mais.

Aqui está a minha solução para isso. Baseia-se em outras respostas neste tópico, com suporte adicional para o ano e mês como que foi solicitado na pergunta original (e era o que eu precisava).

Como para a discussão ou não Isso faz sentido, eu diria que há casos em que ele faz isso. No meu caso queríamos mostrar a duração dos acordos que em alguns casos são apenas alguns dias, e em outros casos vários anos.

testes;

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

Implementação;

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

Para formatar qualquer período superior a um dia (ou seja, mês / ano / década etc.) um objeto Timespan não é suficiente.

Suponha que o seu período de tempo é de 35 dias, em seguida, a partir de 01 de abril você iria ficar um mês e cinco dias, enquanto que a partir de 01 de dezembro que você iria ficar um mês e quatro dias.

em tempo how-do-i-calcular relativa, perguntou ( por número de usuário 1 ) um ano atrás, quando SO era jovem e não público. Foi (provavelmente) a base para a apresentação da idade atual para SO perguntas e respostas.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top