Somar e subtrair horas em JavaScript sem Date: o truque de converter tudo para segundos
Quase todo sistema de ponto, timesheet ou controle de turnos acaba precisando de uma função boba: somar 07:45 + 01:30, ou descobrir quanto tempo passou entre 22:10 e 05:40. E quase sempre alguém tenta resolver isso com new Date(), fuso horário entra no meio, o horário de verão de algum país aparece, e o bug vai parar em produção.
Para durações (e não datas), existe um jeito bem mais simples: trate tempo como um número inteiro de segundos e só formate no final.
1. Converter "HH:MM:SS" em segundos
function paraSegundos(texto) {
const partes = texto.trim().split(':').map(Number);
while (partes.length < 3) partes.push(0); // aceita "HH" e "HH:MM"
const [h, m, s] = partes;
if ([h, m, s].some(Number.isNaN) || m > 59 || s > 59) {
throw new Error(`Horário inválido: ${texto}`);
}
return h * 3600 + m * 60 + s;
}
Repare que as horas não têm limite: "40:30" é uma duração válida (uma semana de trabalho, por exemplo). Isso já elimina um monte de gambiarra com o "estouro" das 24h.
2. Formatar de volta
function formatar(totalSegundos) {
const sinal = totalSegundos < 0 ? '-' : '';
let t = Math.abs(totalSegundos);
const h = Math.floor(t / 3600); t %= 3600;
const m = Math.floor(t / 60);
const s = t % 60;
const dd = (n) => String(n).padStart(2, '0');
return `${sinal}${dd(h)}:${dd(m)}:${dd(s)}`;
}
Manter o sinal é importante: em banco de horas, saldo negativo é informação, não erro.
3. Somar uma lista de durações
const somar = (...tempos) => formatar(tempos.reduce((acc, t) => acc + paraSegundos(t), 0));
somar('07:45', '01:30', '00:50:20'); // "10:05:20"
4. Diferença entre horários que viram a meia-noite
Aqui está o caso que mais quebra código por aí. Se a saída é menor que a entrada, o turno atravessou a meia-noite, então basta somar 24h:
function diferenca(inicio, fim) {
let d = paraSegundos(fim) - paraSegundos(inicio);
if (d < 0) d += 24 * 3600;
return formatar(d);
}
diferenca('22:10', '05:40'); // "07:30:00"
Esse ajuste só vale para turnos menores que 24h. Se você precisa de plantões de 36h, aí sim guarde data + hora e trabalhe com timestamps em UTC.
5. Horas decimais (para folha de pagamento)
Muita planilha de RH quer "7,75 horas" em vez de "07:45". Com segundos isso é só uma divisão:
const decimal = (t) => +(paraSegundos(t) / 3600).toFixed(2);
decimal('07:45'); // 7.75
O erro clássico é fazer 7 + 45/100 e pagar 7,45 horas. São 30 minutos de diferença por dia que viram processo trabalhista no fim do ano.
Por que não usar Date?
Daterepresenta um instante, não uma duração;- conversões de fuso e horário de verão aparecem onde você não espera;
- durações acima de 24h não cabem naturalmente em um objeto de data.
Com inteiros você ganha testes triviais (expect(paraSegundos('01:00')).toBe(3600)), serialização fácil no banco e nenhum problema de fuso.
Conferindo os resultados
Quando estou escrevendo testes desse tipo de função, gosto de conferir alguns casos manualmente numa calculadora de tempo em horas, minutos e segundos antes de fixar o valor esperado no teste — principalmente os casos de borda, como somas que passam de 24h e diferenças negativas.
Se alguém tiver uma abordagem diferente para turnos acima de 24h sem recorrer a timestamps, deixa nos comentários que eu quero ver.
— Verse Media