Converta a duração em horas: minutos: segundos (ou similar) no Rails 3 ou Ruby

115

Tenho a sensação de que existe uma maneira simples / embutida de fazer isso, mas não consigo encontrar.

Tenho uma duração (em segundos) em um número inteiro e quero exibi-la em um formato amigável.

por exemplo, 3600 seria exibido como "01:00:00" ou "1 hora" ou algo assim.

Eu posso fazer isso com, time_ago_in_words(Time.zone.now+3600)mas parece um pouco um hack, não há razão para adicionar / subtrair do tempo atual apenas para formatar esse valor. Existe um duration_in_words()ou algo assim?

obrigado

cidonia
fonte

Respostas:

83

Veja: http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html

distance_of_time_in_words(3600)
 => "about 1 hour"
Allan
fonte
1
obrigado eu olhei para isso, mas pensei que tinha que fornecer duas vezes .. que nub!
cydonia
1
sim, esses exemplos são idiotas
allan
1
distance_of_time_in_words (from_time, to_time, ...)
boulder_ruby
1
Além disso, se você quiser que isso seja muito específico e não "arredondado" a duração, verifique a joia do Radar: github.com/radar/distance_of_time_in_words . Substitua por distance_of_time_in_wordse você pode obter o número arredondado passando vague: truecomo uma opção.
Joshua Pinter
194

Resumindo:

assumindo que total_seconds = 3600

Opção 1:

distance_of_time_in_words(total_seconds) #=> "about 1 hour"

Opção 2:

Time.at(total_seconds).utc.strftime("%H:%M:%S") #=> "01:00:00"

Opção 3:

seconds = total_seconds % 60
minutes = (total_seconds / 60) % 60
hours = total_seconds / (60 * 60)

format("%02d:%02d:%02d", hours, minutes, seconds) #=> "01:00:00"

use a Opção1 se quiser palavras, a Opção2 se quiser o formato H: M: S, a Opção3 se quiser o formato H: M: S e pode haver mais de 24 horas

Lev Lukomsky
fonte
1
O método distance_of_time_in_words é um ActionView Helper, portanto, precisa ser chamado a partir da View (não do controlador). api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html
msdundar
13
Observe que a opção strftime irá estourar em 24 horas. Se sua duração for de 25 horas, ele será exibido 01:00:00.
Gabe Martin-Dempesy
2
isso não funciona para tempos negativos, por exemplo -1800 retorna -1h 30m em vez de -30m
Arnold Roa
45

O %operador de string de Ruby é muito pouco apreciado e frequentemente esquecido.

"%02d:%02d:%02d:%02d" % [t/86400, t/3600%24, t/60%60, t%60]

Dado que t é uma duração em segundos, isso emite uma string separada por dois pontos com zeros, incluindo dias. Exemplo:

t = 123456
"%02d:%02d:%02d:%02d" % [t/86400, t/3600%24, t/60%60, t%60]
=> "01:10:17:36"

Encantador.

IAmNaN
fonte
24
Essa é uma definição estranha de "adorável".
Marc Bollinger
10
O %diz: "Pegue a matriz de parâmetros da minha direita e insira-os na string de formato à minha esquerda." Nesse aspecto, é semelhante ao printfC \ C ++, apenas mais conciso e pode ser usado em uma atribuição. Sim, eu chamo isso de adorável. E poderoso. Existem exemplos que demonstrariam melhor essa eloqüência, mas não responderiam à pergunta. Seu snark não é apreciado, aliás.
IAmNaN
26

Acho que você também poderia fazer algo como:

(Time.mktime(0)+3600).strftime("%H:%M:%S")

Para formatá-lo como desejar.

BTW, originalmente pensei em usar Time.at (), mas parece que o horário de EPOCH no meu Ubuntu é Thu Jan 01:00:00 +0100 1970 e não 00:00:00 horas como eu esperava e, portanto, se o fizer:

Time.at(3600).strftime("%H:%M:%S")

Me dá 1 hora a mais do que o desejado.

Cristobal Viedma
fonte
ahh, pode haver algo para fazer por causa do horário de verão?
Cristobal Viedma
7
O único problema é se você está lidando com tempos de mais de 24 horas. Nesse caso, as horas voltarão para um número baixo.
metavida
Para tempos maiores que 24 horas,% j fornecerá o número de dias (contanto que seja menor que 365)
BananaNeil
18

Eu uso isso para mostrar durações de tempo em meu projeto Rails:

  1. Adicione um método personalizado à Integerclasse. Você pode criar um novo arquivo chamado pretty_duration.rbna initializerspasta:

    class Integer
        def pretty_duration
            parse_string = 
                if self < 3600
                    '%M:%S'
                else
                    '%H:%M:%S'
                end
    
            Time.at(self).utc.strftime(parse_string)
        end
    end
  2. Ligue para seconds.pretty_durationqualquer lugar em seu projeto:

    275.pretty_duration     # => "04:35"
    9823.pretty_duration    # => "02:43:43"

Esta resposta baseia-se no Código de Lev Lukomsky

Sheharyar
fonte
Funcionou muito bem. Obrigado por isso.
Donn Felker
13

Este usa o divmodmétodo obscuro para dividir e módulo ao mesmo tempo, de modo que lida com os Floatsegundos corretamente:

def duration(seconds)
  minutes, seconds = seconds.divmod(60)
  hours, minutes = minutes.divmod(60)
  days, hours = hours.divmod(24)

  "#{days.to_s.rjust(3)}d #{hours.to_s.rjust(2)}h #{minutes.to_s.rjust(2)}m #{seconds}s"
end
Brent Royal-Gordon
fonte
6

O uso Time.utc.strftimefunciona apenas para valores quando o número total de horas é menor que 24 :

2.2.2 :004 > Time.at(60 * 60).utc.strftime('%H h %M m')
=> "01 h 00 m"

Para valores maiores, ele retorna resultados incorretos:

2.2.2 :006 > Time.at(60 * 60 * 24).utc.strftime('%H h %M m')
 => "00 h 00 m"

Eu sugiro usar o método mais simples que encontrei para este problema:

  def formatted_duration total_seconds
    hours = total_seconds / (60 * 60)
    minutes = (total_seconds / 60) % 60
    seconds = total_seconds % 60
    "#{ hours } h #{ minutes } m #{ seconds } s"
  end

Você sempre pode ajustar o valor retornado às suas necessidades.

Igor Springer
fonte
5

Tenha cuidado com a duração superior a um dia.

(timing/3600).to_i.to_s.rjust(2,'0') + ":"+Time.at(timing).utc.strftime("%M:%S")
Xiao Bin
fonte
Dê um exemplo de entrada / saída destacando como isso resolve o caso
difícil
4

Uma resposta inspirada na resposta de Lev Lukomsky tirando proveito de ActiveSupport :: Duration e lidando com milissegundos (útil para código de benchmark)

# duration in ms modulus number of ms in one second
milliseconds = duration.in_milliseconds % 1.second.in_milliseconds

# duration in seconds modulus number of seconds in one minute
seconds = (duration / 1.second) % (1.minute / 1.second)

# duration in minutes modulus number of minutes in one hour
minutes = (duration / 1.minute) % (1.hour / 1.minute)

# duration in hours modulus number of hours in one day
hours = (duration / 1.hour) % (1.day / 1.hour)

format("%02d:%02d:%02d:%03d", hours, minutes, seconds, milliseconds) #=> "12:05:00:001"

Claro que você pode estender isso facilmente com dias, meses, anos, etc, usando métodos ActiveSupport relacionados e repetindo a mesma estrutura.

Lembre-se de que para durações muito longas, isso pode ser impreciso, pois a duração de 1 mês não é fixada em número de dias, e não tenho certeza de como AS: Duration lida com isso.

Cyril Duchon-Doris
fonte
2

Só para jogar meus 2 centavos:

Time.at(i).utc.strftime((i < 3600) ? '%-M minutes and %-S seconds' : '%-H hours, %-M minutes, and %-S seconds')

Baseado na resposta de Xiao Bin.

Daniel
fonte
2

Grite para @joshuapinter quem deu a melhor resposta (na forma de um comentário ).

Use o substituto drop-in dotiw gema de para obter mais controle sobre a precisão da saída para atender às diferentes necessidades:

https://github.com/radar/distance_of_time_in_words

Código de visualização de amostra:

%label
  Logoff after:
  - expire_in = distance_of_time_in_words(Time.now, Time.now + user.custom_timeout.minutes, :only => [:minutes, :hours, :days])
  = expire_in

Resultando em algo assim:

Logoff after: 1 day, 13 hours, and 20 minutes
Jon Kern
fonte
2

ActiveSupport::Duration.build+ inspectdá resultados válidos

 >> ActiveSupport::Duration.build(125557).inspect
 => "1 day, 10 hours, 52 minutes, and 37 seconds"
Sathish
fonte
1
    hours = 3.5456
    value = (hours*60).divmod(60).map{ |a| "%02d"%[a.floor] }.join(":")
    => "03:32"
Luciano Ribas
fonte
5
Adicione algum contexto à resposta.
Paul Dawson