Como calcular em Python as horas semanais de uma escala 6×1?

Por BuildBase

19 de outubro de 2026

O pedido parece simples: “me diga quantas horas por semana a Ana trabalha”. A Ana cumpre 6×1, entra às 8h, sai às 16h20 e tem uma hora de almoço. Alguém abre uma planilha, faz 16h20 menos 8h, subtrai a hora do almoço, multiplica por seis e chega a 44 horas. Na semana seguinte, um turno vira a meia-noite, a conta quebra e ninguém sabe por quê.

Calcular horas semanais em Python é mais curto do que parece, desde que você evite as armadilhas de sempre: horas decimais, turnos que cruzam a meia-noite e semanas que não começam onde você pensa.

Resposta rápida: converta entrada e saída em datetime, some um dia quando a saída for menor que a entrada, subtraia o intervalo e trabalhe em minutos inteiros. Some os dias da semana e compare com o limite (44, 42 ou 40 horas). Uma escala 6×1 de 44 horas dá 7h20 por dia durante seis dias; uma 5×2 de 44 horas dá 8h48 por dia durante cinco; uma 5×2 de 40 horas dá exatamente 8h por dia.

 

Passo 1: as horas de um único dia

O módulo datetime da biblioteca padrão faz o trabalho pesado. A documentação oficial do módulo datetime do Python descreve strptime (texto para data) e timedelta (diferença entre datas). A função abaixo devolve os minutos trabalhados em um dia:

from datetime import date, datetime, timedelta

FMT = "%H:%M"

def minutos_dia(entrada, saida, intervalo_min=60):
    """Minutos efetivamente trabalhados em um dia."""
    ini = datetime.strptime(entrada, FMT)
    fim = datetime.strptime(saida, FMT)
    if fim <= ini:                      # turno que vira a meia-noite
        fim += timedelta(days=1)
    return int((fim - ini).total_seconds() // 60) - intervalo_min

def hhmm(minutos):
    return f"{minutos // 60}h{minutos % 60:02d}"

Duas decisões merecem explicação. A primeira é trabalhar em minutos inteiros, e não em horas decimais: 7h20 em decimal é 7,3333…, e somar seis dias disso gera erros de arredondamento que fazem a semana “fechar” em 43,99 horas. A segunda é o teste fim <= ini, que trata o turno de 22h às 6h como um período que termina no dia seguinte.

 

Passo 2: somar a semana

Cada dia é uma tupla com data, entrada, saída e intervalo. A semana é uma lista dessas tuplas, e a soma é direta:

def minutos_semana(dias):
    """dias: lista de (data, entrada, saida, intervalo_min)."""
    return sum(minutos_dia(e, s, i) for _, e, s, i in dias)

def semana(inicio, entrada, saida, intervalo, n_dias):
    return [(inicio + timedelta(days=k), entrada, saida, intervalo)
            for k in range(n_dias)]

A função semana gera uma escala regular para testar, sem digitar dia por dia. Em produção, as tuplas viriam de um CSV, de um banco de dados ou do espelho do ponto.

 

Passo 3: comparar seis dias com cinco

Agora vem a parte que costuma surpreender. Executando as três escalas com a mesma função, o resultado foi:

seg = date(2026, 12, 14)
a = semana(seg, "08:00", "16:20", 60, 6)   # 6x1
b = semana(seg, "08:00", "17:48", 60, 5)   # 5x2 de 44 horas
c = semana(seg, "08:00", "17:00", 60, 5)   # 5x2 de 40 horas
for nome, x, lim in (("6x1", a, 44), ("5x2/44h", b, 44), ("5x2/40h", c, 40)):
    m = minutos_semana(x)
    print(nome, hhmm(m), "saldo (min):", m - lim * 60)
EscalaTurnoHoras por diaTotal semanalSaldo contra o limite
6×1 de 44 horas8h às 16h207h2044h000 min
5×2 de 44 horas8h às 17h488h4844h000 min
5×2 de 40 horas8h às 17h8h0040h000 min

Os três fecham, mas só um respeita o limite diário de 8 horas do art. 58 da CLT sem nenhum arranjo especial: o 5×2 de 40 horas. O 5×2 de 44 horas, com 8h48 por dia, depende de compensação do sábado. Na prática, o código da semana de 44 horas precisa de uma regra que aceite dias acima de 8 horas quando houver acordo de compensação, e o limite mais baixo de 40 horas dispensa essa exceção.

Notebook com código aberto sobre uma mesa, ao lado de livros
Um módulo pequeno e testado vale mais do que uma planilha que ninguém sabe auditar. Foto: Unsplash

 

Passo 4: transformar as regras da CLT em verificações

Somar horas é só o começo. O que evita problema é conferir as regras. A função a seguir devolve uma lista de avisos para uma semana de dias consecutivos:

def verificar(dias, limite_dia=8 * 60):
    avisos = []
    for d, e, s, i in dias:                       # limite diário e intervalo
        m = minutos_dia(e, s, i)
        if m > limite_dia:
            avisos.append(f"{d}: {hhmm(m)} acima de 8h")
        if m > 6 * 60 and i < 60:
            avisos.append(f"{d}: intervalo de {i} min em jornada acima de 6h")
    for (d1, e1, s1, _), (d2, e2, s2, _) in zip(dias, dias[1:]):   # 11h entre jornadas
        fim = datetime.combine(d1, datetime.strptime(s1, FMT).time())
        if s1 <= e1:
            fim += timedelta(days=1)
        ini = datetime.combine(d2, datetime.strptime(e2, FMT).time())
        if ini - fim < timedelta(hours=11):
            avisos.append(f"{d2}: só {(ini - fim).total_seconds() / 3600:.1f}h desde o turno anterior")
    seq = 1                                        # mais de seis dias seguidos
    for (d1, *_), (d2, *_) in zip(dias, dias[1:]):
        seq = seq + 1 if (d2 - d1).days == 1 else 1
        if seq > 6:
            avisos.append(f"{d2}: {seq}º dia seguido de trabalho")
    return avisos

Para testar, montei uma semana de sete dias seguidos, com um turno de 14h às 22h30 numa quarta e outro de 7h às 16h, com 30 minutos de intervalo, na quinta. A saída do programa foi:

2026-12-17: 8h30 acima de 8h
2026-12-17: intervalo de 30 min em jornada acima de 6h
2026-12-17: só 8.5h desde o turno anterior
2026-12-20: 7º dia seguido de trabalho

Os quatro avisos cobrem o que a CLT exige: limite diário (art. 58), intervalo de pelo menos uma hora em jornadas acima de seis horas (art. 71), descanso de 11 horas entre jornadas (art. 66) e descanso semanal (art. 67), todos disponíveis no texto da CLT.

 

O adicional noturno muda a conta das horas

Se o turno passa pelo período noturno (das 22h às 5h), a hora legal tem 52 minutos e 30 segundos, e o adicional é de 20% (art. 73). Em código, conta-se os minutos dentro da janela e converte-se:

def minutos_noturnos(entrada, saida):
    ini = datetime.strptime(entrada, FMT)
    fim = datetime.strptime(saida, FMT)
    if fim <= ini:
        fim += timedelta(days=1)
    total, t = 0, ini
    while t < fim:
        if t.hour >= 22 or t.hour < 5:
            total += 1
        t += timedelta(minutes=1)
    return total, total * 60 / 52.5

Para um turno das 22h às 6h, a função retornou 420 minutos de relógio dentro da janela noturna, que equivalem a 480 minutos de hora noturna, ou seja, 8 horas fictas para 7 horas de relógio. Essa conversão influencia o pagamento, mas não muda o limite semanal de presença.

 

Armadilhas que aparecem em escalas reais

  • Semana ISO versus semana contratual: date.isocalendar() considera a semana de segunda a domingo. Se o contrato conta de domingo a sábado, agrupe pela data inicial do contrato, e não pelo número da semana.
  • Intervalo fora da jornada: os intervalos do art. 71 (de uma a duas horas acima de seis horas de trabalho, ou 15 minutos entre quatro e seis horas) não entram na duração do trabalho (§2º). Parametrize o desconto por regra, e não por padrão.
  • Tolerância de marcação: o art. 58, §1º, não computa variações de até 5 minutos por marcação, no limite de 10 minutos por dia. Implemente como regra separada, para poder desligar.
  • Dados faltantes: entrada sem saída deve gerar um aviso, e não uma jornada de zero minuto.
  • Histórico antigo: o Brasil não tem horário de verão desde 2019, mas dados anteriores a isso trazem a mudança de fuso. Se precisar reconstruir períodos antigos, use zoneinfo.

Se o resultado precisa ser consumido por outros sistemas internos, vale expor a função como serviço, como mostra o texto sobre API REST simples em Python.

 

Perguntas que aparecem quando o código vai para produção

 

Posso usar pandas em vez de datetime puro?

Pode, e para milhares de registros é o melhor caminho: pd.to_datetime converte as colunas, a subtração de colunas gera durações e groupby soma por pessoa e semana. A lógica de regras (11 horas, seis dias seguidos) continua sendo a mesma. Para aprender e testar, a biblioteca padrão basta.

 

Como tratar a escala 12×36 no mesmo código?

Os turnos de 12 horas funcionam com a mesma função, e a média semanal gira em torno de 42 horas. A diferença está nas regras: o limite diário passa a ser de 12 horas, e o descanso é de 36 horas. Parametrize limite_dia e o intervalo mínimo entre jornadas por tipo de escala.

 

Esse código substitui o sistema de ponto?

Não. Ele calcula e confere a escala planejada. O registro oficial de ponto tem requisitos legais próprios, e o código pode consumir os dados desse registro para conferência.

 

Como testar sem dados reais?

Gere semanas sintéticas com semana(), provoque erros de propósito (um turno de 22h seguido de outro às 7h, sete dias seguidos) e confira se os avisos aparecem. Transforme esses casos em testes automáticos com assert.

 

O que fazer, conforme o seu projeto

Se você está começando, copie as funções minutos_dia e minutos_semana, rode os três cenários e confira se os números batem com os da tabela. Se já tem uma planilha em produção, converta as regras dela em testes e compare os resultados com os do código, para achar divergências. E se vai aplicar isso a uma escala real, leve o resultado a quem conhece a convenção coletiva da sua categoria, porque adicionais, intervalos e acordos de compensação mudam o cálculo.

Leia também: