DEV Community

Verse Media
Verse Media

Posted on

Como calcular horas trabalhadas e converter minutos em decimal em JavaScript

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!
Enter fullscreen mode Exit fullscreen mode

Horas não são base 10. Neste post vamos escrever, em JavaScript puro, um pequeno conjunto de funções para:

  1. converter "HH:MM" em minutos (e de volta);
  2. calcular horas trabalhadas num dia com intervalo;
  3. converter minutos em horas decimais (para multiplicar por valor/hora);
  4. 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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 ✅');
Enter fullscreen mode Exit fullscreen mode

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.5 dá 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, strings HH:MM e 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)