Se você já precisou montar um controle de ponto, uma planilha de freelas ou um relatório de horas, provavelmente esbarrou neste bug clássico:
1.30 + 0.45 // 1.75 ... mas 1h30 + 0h45 = 2h15!
Horas não são base 10. Neste post vamos escrever, em JavaScript puro, um pequeno conjunto de funções para:
- converter
"HH:MM"em minutos (e de volta); - calcular horas trabalhadas num dia com intervalo;
- converter minutos em horas decimais (para multiplicar por valor/hora);
- somar uma semana e calcular o saldo.
A regra de ouro: trabalhe em minutos inteiros
Inteiros não têm problema de ponto flutuante. Converta tudo para minutos na entrada, faça as contas, e só formate na saída.
function toMinutes(hhmm) {
const match = /^(-)?(\d{1,3}):([0-5]\d)$/.exec(hhmm.trim());
if (!match) throw new Error(`Horário inválido: "${hhmm}"`);
const [, sign, h, m] = match;
const total = Number(h) * 60 + Number(m);
return sign ? -total : total;
}
function fromMinutes(total) {
const sign = total < 0 ? '-' : '';
const abs = Math.abs(total);
const h = Math.floor(abs / 60);
const m = abs % 60;
return `${sign}${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
}
console.log(toMinutes('07:45')); // 465
console.log(fromMinutes(135)); // "02:15"
console.log(fromMinutes(-20)); // "-00:20"
Horas trabalhadas no dia (com virada de meia-noite)
Um dia típico tem quatro marcações: entrada, saída para o almoço, retorno e saída. Se a saída for menor que a entrada, o turno atravessou a meia-noite — somamos 24h.
const DAY = 24 * 60;
function interval(start, end) {
let diff = toMinutes(end) - toMinutes(start);
if (diff < 0) diff += DAY; // virou o dia
return diff;
}
function workedMinutes(entrada, saidaAlmoco, volta, saida) {
return interval(entrada, saidaAlmoco) + interval(volta, saida);
}
const dia = workedMinutes('08:00', '12:00', '13:00', '17:48');
console.log(fromMinutes(dia)); // "08:48"
Minutos para horas decimais
Só converta para decimal quando for multiplicar por dinheiro. A fórmula é minutos / 60; arredonde apenas no resultado final.
function toDecimalHours(minutes, digits = 2) {
return Number((minutes / 60).toFixed(digits));
}
console.log(toDecimalHours(465)); // 7.75 (7h45)
console.log(toDecimalHours(20)); // 0.33 (20 min)
console.log(toDecimalHours(750)); // 12.5 (12h30)
// Valor de 12h30 a R$ 18,00/h — multiplique antes de arredondar
const valor = (750 / 60) * 18;
console.log(valor.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' }));
// "R$ 225,00"
Repare: se alguém digitar 12.30 achando que é 12h30, o valor sairia R$ 221,40. É exatamente esse erro que estamos evitando.
Saldo da semana (estilo banco de horas)
function weeklyBalance(days, jornada = '08:00') {
const meta = toMinutes(jornada);
return days.reduce((acc, [e, sa, v, s]) => {
const trabalhado = workedMinutes(e, sa, v, s);
return acc + (trabalhado - meta);
}, 0);
}
const semana = [
['08:00', '12:00', '13:00', '18:15'], // 9h15 → +75
['08:10', '12:00', '13:00', '16:50'], // 7h40 → -20
['07:50', '12:00', '13:00', '17:40'], // 8h50 → +50
['08:00', '12:00', '13:00', '17:00'], // 8h00 → 0
['08:30', '12:00', '13:00', '17:00'], // 7h30 → -30
];
const saldo = weeklyBalance(semana);
console.log(saldo, fromMinutes(saldo), toDecimalHours(saldo));
// 75 "01:15" 1.25
Testando rapidinho com node:assert
import assert from 'node:assert/strict';
assert.equal(toMinutes('01:30') + toMinutes('00:45'), 135);
assert.equal(fromMinutes(135), '02:15');
assert.equal(interval('22:00', '05:00'), 420); // turno noturno
assert.equal(toDecimalHours(45), 0.75);
assert.equal(weeklyBalance(semana), 75);
console.log('ok ✅');
Salve tudo num arquivo horas.mjs e rode com node horas.mjs.
Para ir além
-
Hora noturna reduzida: na CLT (art. 73, § 1º), a hora noturna urbana (22h–5h) conta como 52min30s. Para converter,
minutosNoturnos / 52.5dá o número de horas noturnas — 420 minutos viram 8 horas. -
Fuso e horário de verão: se você usar
Date, cuidado com fusos. Para marcações de ponto no mesmo dia, stringsHH:MMe minutos inteiros costumam ser mais seguros. -
Validação: rejeite
"25:00"ou"08:60"na entrada — a regex acima já barra minutos ≥ 60.
Se quiser conferir seus resultados contra uma ferramenta pronta, a Calculadora de Horas faz essas contas no navegador e foi útil para validar os casos de teste deste post.
E você, já foi mordido pelo bug do 1.30 + 0.45? Conta nos comentários 👇
Top comments (0)