Excel e documentos de trabalho

Padronize formatos de data misturados em um CSV e liste os valores não interpretados

Converta vários formatos de data conhecidos em valores no padrão ISO YYYY-MM-DD sem alterar o CSV original. Valores que não correspondem a um formato aprovado ou que não representam datas válidas do calendário são gravados em um arquivo separado para revisão.

Ver o sumário

A tradução foi feita com IA. Confira o código, as unidades e os valores junto com o original. A revisão por falantes nativos de cada idioma ainda não foi concluída. English

Para quem éEste guia é voltado a pessoas que recebem arquivos CSV com vários formatos de data e precisam de um processo de limpeza repetível que não tente adivinhar silenciosamente valores ambíguos.

Preparação
  • Python 3.12 e um comando de terminal que inicie essa versão.
  • Um editor de texto capaz de salvar arquivos CSV em UTF-8.
  • Uma pasta de trabalho em que o script possa criar uma nova pasta dentro de outputs.
  • É necessária apenas a biblioteca padrão do Python: csv, datetime e pathlib.

01Defina os formatos de data aceitos antes da interpretação

A limpeza de datas é mais segura quando os formatos aceitos são definidos explicitamente. Este exemplo reconhece seis formatos numéricos e converte todos os valores interpretados com sucesso para YYYY-MM-DD. Ele não usa detecção automática de datas.

Exemplo de entrada aceitaSignificadoFormato do Python
2026-09-01ano-mês-dia%Y-%m-%d
09/02/2026mês/dia/ano%m/%d/%Y
2026/09/03ano/mês/dia%Y/%m/%d
04-09-2026dia-mês-ano%d-%m-%Y
2026.09.05ano.mês.dia%Y.%m.%d
20260906anomêsdia%Y%m%d

A interpretação de formatos com barras e hífens é uma decisão de política. Neste tutorial, 09/02/2026 significa 2 de setembro de 2026, enquanto 04-09-2026 significa 4 de setembro de 2026. Se sua fonte usar convenções diferentes, altere os formatos aceitos antes de processar dados reais.

02Crie um pequeno CSV sintético

O conjunto de dados a seguir é sintético e foi criado especificamente para este artigo. Salve-o como mixed_dates.csv na pasta de trabalho. Ele contém seis datas válidas em seis formatos aceitos diferentes e dois valores que devem falhar.

csv
record_id,event_date,note
R001,2026-09-01,ISO format
R002,09/02/2026,US slash format
R003,2026/09/03,year first with slashes
R004,04-09-2026,day first with hyphens
R005,2026.09.05,dot separated
R006,20260906,compact numeric
R007,2026-02-30,invalid calendar date
R008,Sep 7 2026,unsupported text format

Há 8 registros de dados. Os primeiros 6 devem ser interpretados com sucesso. R007 usa um padrão reconhecido, mas 30 de fevereiro não é uma data real. R008 usa um nome de mês em texto que foi intencionalmente excluído da lista de formatos aceitos.

03Calcule manualmente os valores limpos esperados

Os primeiros seis valores representam datas consecutivas de 1 a 6 de setembro de 2026. Portanto, eles devem produzir seis valores padronizados. Os dois registros restantes devem manter o texto original no arquivo de revisão.

record_idValor originalValor padronizado esperadoStatus
R0012026-09-012026-09-01PARSED
R00209/02/20262026-09-02PARSED
R0032026/09/032026-09-03PARSED
R00404-09-20262026-09-04PARSED
R0052026.09.052026-09-05PARSED
R006202609062026-09-06PARSED
R0072026-02-30UNPARSED
R008Sep 7 2026UNPARSED

Portanto, os totais esperados são 8 registros, 6 registros interpretados e 2 registros não interpretados. O CSV limpo ainda conterá todos os 8 registros; as linhas não interpretadas receberão um valor standardized_date vazio e o status UNPARSED.

04Interprete os formatos aprovados e grave novos arquivos

Salve o script a seguir como date_format_cleanup.py. Ele lê o CSV original, grava uma cópia limpa completa e também cria um CSV de revisão menor contendo apenas os registros cuja data não pôde ser padronizada.

python
import csv
from datetime import datetime
from pathlib import Path

SOURCE = Path("mixed_dates.csv")
OUTPUT_DIR = Path("outputs") / "date_cleanup_result"
CLEANED = OUTPUT_DIR / "cleaned_dates.csv"
UNPARSED = OUTPUT_DIR / "unparsed_dates.csv"
DATE_COLUMN = "event_date"

FORMATS = (
    "%Y-%m-%d",
    "%m/%d/%Y",
    "%Y/%m/%d",
    "%d-%m-%Y",
    "%Y.%m.%d",
    "%Y%m%d",
)


def parse_date(value: str) -> str | None:
    text = value.strip()
    for format_string in FORMATS:
        try:
            parsed = datetime.strptime(text, format_string)
        except ValueError:
            continue
        return parsed.strftime("%Y-%m-%d")
    return None


def main() -> None:
    if not SOURCE.is_file():
        raise FileNotFoundError(f"Source CSV not found: {SOURCE}")

    OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
    OUTPUT_DIR.mkdir()  # Stop rather than reuse an existing result folder.

    with SOURCE.open("r", encoding="utf-8-sig", newline="") as source:
        reader = csv.DictReader(source)
        if reader.fieldnames is None:
            raise ValueError("CSV has no header row.")
        if DATE_COLUMN not in reader.fieldnames:
            raise ValueError(f"Missing required column: {DATE_COLUMN}")
        if "standardized_date" in reader.fieldnames or "date_status" in reader.fieldnames:
            raise ValueError("Output column name already exists in source CSV.")

        cleaned_fields = [*reader.fieldnames, "standardized_date", "date_status"]
        review_fields = ["row_number", *reader.fieldnames]

        with CLEANED.open("x", encoding="utf-8", newline="") as cleaned_stream, \
             UNPARSED.open("x", encoding="utf-8", newline="") as review_stream:
            cleaned_writer = csv.DictWriter(cleaned_stream, fieldnames=cleaned_fields)
            review_writer = csv.DictWriter(review_stream, fieldnames=review_fields)
            cleaned_writer.writeheader()
            review_writer.writeheader()

            total = 0
            parsed_count = 0
            unparsed_count = 0

            for row_number, row in enumerate(reader, start=2):
                total += 1
                original_value = row[DATE_COLUMN]
                standardized = parse_date(original_value)

                output_row = dict(row)
                if standardized is None:
                    output_row["standardized_date"] = ""
                    output_row["date_status"] = "UNPARSED"
                    review_writer.writerow({"row_number": row_number, **row})
                    unparsed_count += 1
                else:
                    output_row["standardized_date"] = standardized
                    output_row["date_status"] = "PARSED"
                    parsed_count += 1

                cleaned_writer.writerow(output_row)

    if parsed_count + unparsed_count != total:
        raise RuntimeError("Record counts do not balance.")

    print(f"Processed {total} records.")
    print(f"Parsed: {parsed_count}; unparsed: {unparsed_count}.")
    print(f"Cleaned CSV: {CLEANED.as_posix()}")
    print(f"Review CSV: {UNPARSED.as_posix()}")


if __name__ == "__main__":
    main()
text
python date_format_cleanup.py

05Compare os arquivos de saída esperados

cleaned_dates.csv deve conter as três colunas originais mais standardized_date e date_status. Todos os 8 registros originais continuam presentes. As duas linhas com falha não são excluídas.

record_idevent_datestandardized_datedate_status
R0012026-09-012026-09-01PARSED
R00209/02/20262026-09-02PARSED
R0032026/09/032026-09-03PARSED
R00404-09-20262026-09-04PARSED
R0052026.09.052026-09-05PARSED
R006202609062026-09-06PARSED
R0072026-02-30UNPARSED
R008Sep 7 2026UNPARSED

unparsed_dates.csv deve conter exatamente 2 linhas de dados. Como o cabeçalho do CSV original é fisicamente a linha 1, R007 corresponde à linha 8 do CSV e R008 à linha 9.

row_numberrecord_idevent_date
8R0072026-02-30
9R008Sep 7 2026

06Verifique as contagens e a saída do console

A saída esperada do console abaixo foi derivada manualmente a partir da entrada sintética e do script. Não é um log capturado de execução.

text
Processed 8 records.
Parsed: 6; unparsed: 2.
Cleaned CSV: outputs/date_cleanup_result/cleaned_dates.csv
Review CSV: outputs/date_cleanup_result/unparsed_dates.csv
  1. Confirme que cleaned_dates.csv contém 8 linhas de dados, e não apenas as 6 linhas processadas com sucesso.
  2. Confirme que as datas padronizadas vão de 2026-09-01 a 2026-09-06 para R001 a R006.
  3. Confirme que R007 e R008 têm valores standardized_date vazios e status UNPARSED.
  4. Confirme que unparsed_dates.csv contém exatamente R007 e R008.
  5. Execute o script novamente sem alterar OUTPUT_DIR. Ele deve parar com FileExistsError em vez de sobrescrever o resultado anterior.

07Reconheça erros comuns

SintomaO que verificar
FileNotFoundErrorVerifique SOURCE e o diretório de trabalho atual do terminal.
Coluna obrigatória ausenteConfirme que o cabeçalho contém exatamente event_date ou altere DATE_COLUMN deliberadamente.
Muitos valores não interpretadosInspecione as convenções da fonte e adicione apenas formatos documentados e não ambíguos para esse conjunto de dados.
Mês e dia aparecem invertidos inesperadamenteVerifique se datas com barras ou hífens usam primeiro o mês ou o dia antes de adicionar o formato correspondente.
FileExistsErrorA pasta de resultados já existe. Revise-a e selecione um novo destino de saída em vez de sobrescrevê-la.
Uma data aparentemente válida ainda falhaOs caracteres podem conter espaços, usar um formato não listado em FORMATS ou representar uma data de calendário inválida.

Não responda a uma alta taxa de falhas adicionando muitos formatos especulativos. Isso pode transformar dados inválidos detectáveis em datas interpretadas incorretamente sem aviso.

08Entenda os limites antes de ampliar o uso

Este exemplo trata apenas de datas de calendário. Ele não interpreta horários, fusos horários, datas seriais do Excel, nomes de meses localizados, anos com dois dígitos nem valores que contenham texto descritivo adicional. Esses casos precisam de regras explícitas.

datetime.strptime valida datas do calendário, portanto um valor estruturalmente plausível como 2026-02-30 é rejeitado. Isso é útil, mas uma data interpretada com sucesso não prova que o valor de origem esteja factualmente correto. Um erro de digitação como 2026-09-12 em vez de 2026-09-21 ainda é uma data válida do calendário.

A ordem de FORMATS importa quando dois padrões aceitos podem interpretar a mesma string de formas diferentes. Um processo de limpeza em produção deve documentar a convenção da fonte, preservar o valor original, relatar falhas e revisar formatos ambíguos em vez de depender apenas da interpretação automática.

Registro de execução e verificação

2026-09-20 · exemplo verificado manualmente · alvo: Python 3.12 · biblioteca padrão: csv, datetime, pathlib · sem execução

  • Foram contados manualmente 8 registros de origem, com 6 interpretações bem-sucedidas esperadas e 2 falhas esperadas.
  • As seis entradas aceitas foram convertidas manualmente em datas padronizadas consecutivas de 2026-09-01 a 2026-09-06.
  • Foi confirmado por raciocínio de calendário que 2026-02-30 é inválida e que Sep 7 2026 não corresponde a nenhum formato listado em FORMATS.
  • Os números das linhas de revisão 8 e 9 foram derivados manualmente a partir das posições das linhas no CSV de origem.
  • O código foi inspecionado para confirmar que o CSV original é apenas lido, que os resultados são gravados em uma pasta separada e que uma pasta de resultados já existente faz a execução parar.
  • As contagens esperadas de registros processados, interpretados e não interpretados, bem como as mensagens do console, foram derivadas manualmente.
Limites da verificação
  • O código não foi executado pelo autor desta resposta; nenhum arquivo CSV de saída foi criado.
  • Nomes de meses dependentes de localidade, valores de horário, fusos horários, datas seriais do Excel, anos com dois dígitos e arquivos muito grandes não foram testados.
  • As convenções de data aceitas são políticas de exemplo e devem ser alteradas se a fonte real usar significados diferentes.
  • As URLs da documentação oficial foram fornecidas com base em locais de documentação conhecidos, mas não foram verificadas ao vivo.

Princípios de redação e verificação de todo o site

Fontes de referência

As explicações e os exemplos são de elaboração própria. Os comportamentos e conceitos relacionados podem ser consultados nas fontes oficiais abaixo.