Módulo 14 - Datas e expressões regulares

Prática: validar e extrair

11 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026

O que você vai aprender

  • Validar um telefone brasileiro simples com re.fullmatch.
  • Ler os grupos capturados de um padrão com parênteses.
  • Extrair todas as datas de um texto com re.findall.
  • Converter cada data extraída em objeto com strptime.

Validar um telefone brasileiro simples

Vamos validar um celular brasileiro em um formato comum: (11) 91234-5678. Ele tem um parêntese, dois dígitos de DDD, outro parêntese, um espaço, cinco dígitos, um hífen e quatro dígitos. Para descrever isso, montamos o padrão pedaço por pedaço. Como parênteses são especiais em regex, o parêntese literal do DDD precisa de barra: \( e \). O DDD são dois dígitos, \d{2}; depois \) e um espaço; então \d{5}, um hífen literal e \d{4}. Para validar, usamos re.fullmatch, que exige que todo o texto case, sem sobra.

import re

padrao = r"\(\d{2}\) \d{5}-\d{4}"

def telefone_valido(numero):
    return re.fullmatch(padrao, numero) is not None

print(telefone_valido("(11) 91234-5678"))  # True
print(telefone_valido("11912345678"))       # False (sem formatacao)
print(telefone_valido("(11) 1234-5678"))     # False (so 4 digitos no meio)
print(telefone_valido("ligar (11) 91234-5678 hoje"))  # False (tem texto em volta)

re.fullmatch exige que o texto inteiro siga o formato; is not None vira um True/False limpo.

Repare por que fullmatch é a escolha certa aqui. Se usássemos re.search, o último teste passaria, porque o número aparece no meio da frase e o search se contenta com um pedaço que casa. Para validação, queremos o texto inteiro no formato, e é exatamente isso que fullmatch garante. Outra opção equivalente seria envolver o padrão com as âncoras ^ e $ e usar search. As duas abordagens dizem a mesma coisa: o formato tem que valer do começo ao fim.

Capturar o DDD e o número

Validar é só o começo. Muitas vezes você também quer separar as partes do que validou, por exemplo guardar o DDD e o número em campos diferentes. Para isso servem os grupos de captura: você envolve o pedaço que interessa entre parênteses no padrão, e a regex guarda cada um separadamente. Depois de casar, .group(1) devolve o primeiro grupo, .group(2) o segundo, e assim por diante. O .group(0), ou .group() sem número, devolve o casamento inteiro.

import re

# Parenteses de captura em volta do DDD e do numero
padrao = r"\((\d{2})\) (\d{5}-\d{4})"

m = re.fullmatch(padrao, "(11) 91234-5678")
if m:
    print(m.group(0))  # (11) 91234-5678  (tudo)
    print(m.group(1))  # 11              (DDD)
    print(m.group(2))  # 91234-5678      (numero)

    ddd, numero = m.group(1), m.group(2)
    print(f"DDD {ddd}, numero {numero}")
    # DDD 11, numero 91234-5678

Parênteses no padrão criam grupos; .group(1) e .group(2) recuperam cada parte capturada.

Extrair todas as datas de um texto

O segundo desafio junta as duas metades do módulo: encontrar todas as datas escritas em um texto solto e transformar cada uma em objeto de data para poder calcular. Uma data no formato brasileiro tem dois dígitos, uma barra, dois dígitos, outra barra e quatro dígitos: \d{2}/\d{2}/\d{4}. Com re.findall, pegamos todas as ocorrências desse formato de uma vez, numa lista de textos. Em seguida, passamos cada texto por datetime.strptime para virar um objeto, pronto para comparações e cálculos da aula de datas.

import re
from datetime import datetime

texto = (
    "Reuniao em 02/07/2026, entrega em 15/08/2026 "
    "e revisao em 30/09/2026."
)

padrao = r"\d{2}/\d{2}/\d{4}"
encontradas = re.findall(padrao, texto)
print(encontradas)
# ['02/07/2026', '15/08/2026', '30/09/2026']

# Converter cada texto em objeto de data:
datas = [datetime.strptime(d, "%d/%m/%Y") for d in encontradas]
for d in datas:
    print(d.strftime("%d/%m/%Y"), "->", d.year)
# 02/07/2026 -> 2026
# 15/08/2026 -> 2026
# 30/09/2026 -> 2026

findall extrai os textos das datas; strptime, dentro da compreensão, converte cada um em objeto.

Esse pequeno programa mostra o encaixe perfeito dos dois temas do módulo. A regex reconhece a forma da data dentro de um texto bagunçado, algo que seria trabalhoso na mão. O tipo datetime transforma o texto em um valor com o qual dá para calcular: você poderia agora achar a data mais próxima, contar os dias até cada entrega ou ordenar as datas. Repare também no uso da compreensão de lista, do módulo 2, para converter todas as datas em uma linha. É o intermediário aparecendo: recursos diferentes se combinam para resolver um problema real com pouco código e muita clareza.

import re
from datetime import datetime, date

texto = "Prazos: 10/07/2026 e 05/07/2026."
datas = [
    datetime.strptime(d, "%d/%m/%Y").date()
    for d in re.findall(r"\d{2}/\d{2}/\d{4}", texto)
]

mais_cedo = min(datas)
hoje = date(2026, 7, 2)
print("Proximo prazo:", mais_cedo.strftime("%d/%m/%Y"))  # 05/07/2026
print("Faltam", (mais_cedo - hoje).days, "dias")          # Faltam 3 dias

Extrair, converter e calcular: encontrar o prazo mais próximo e contar os dias até ele.

Teste rápido

Você usou re.findall(r"\d{2}/\d{2}/\d{4}", texto) e obteve uma lista de textos. Como transformar cada um em objeto de data?

Perguntas frequentes

Por que usar re.fullmatch em vez de re.search para validar?
Porque validar significa conferir o texto inteiro. re.search aceita que o formato apareça só em um pedaço, então validaria uma frase que contém o número no meio. re.fullmatch só casa se todo o texto seguir o padrão, que é o que você quer numa validação. Envolver o padrão com ^ e $ e usar search dá o mesmo efeito.
Como pego só o DDD de um telefone validado?
Coloque parênteses em volta do trecho do DDD no padrão, criando um grupo de captura, e leia com .group(1) depois do casamento. No padrão r"\((\d{2})\) ...", o grupo 1 é os dois dígitos do DDD. O .group(0) devolve o número inteiro que casou.
Por que os parênteses do DDD aparecem com barra invertida?
Porque o parêntese é um símbolo especial de regex (ele abre um grupo). Para casar com o caractere parêntese de verdade, que aparece no telefone, você escapa com barra: \( e \). Já os parênteses sem barra, mais adentro, criam os grupos de captura. São coisas diferentes no mesmo padrão.
O padrão de data também casaria com uma data inválida, tipo 99/99/9999?
Sim. O padrão \d{2}/\d{2}/\d{4} só confere a forma, dois dígitos, barra e assim por diante, não se o dia e o mês existem. A validação real do calendário acontece no strptime, que levanta ValueError ao tentar converter uma data impossível. Por isso vale converter dentro de um try.
Preciso do módulo datetime além do re para extrair datas?
Para só encontrar os textos das datas, o re basta com findall. Mas para trabalhar com as datas, comparar, contar dias, ordenar, você importa datetime e converte cada texto com strptime. Os dois módulos se completam: um reconhece a forma, o outro dá o valor calculável.
Dá para converter as datas em uma linha só?
Dá, com uma compreensão de lista: [datetime.strptime(d, "%d/%m/%Y") for d in re.findall(padrao, texto)]. Isso percorre cada texto encontrado e o converte, montando a lista de objetos de uma vez. É um bom exemplo de como a compreensão do módulo 2 deixa o código mais enxuto.

Fontes

Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.