Excel y documentos de trabajo

Estandarizar formatos de fecha mezclados en un CSV y listar los valores no interpretados

Convierte varios formatos de fecha conocidos a valores con formato ISO YYYY-MM-DD sin modificar el CSV original. Los valores que no coinciden con un formato autorizado o que no representan fechas válidas del calendario se escriben en un archivo de revisión separado.

Ver el índice

La traducción se ha realizado con IA. Comprueba el código, las unidades y los valores junto con el original. La revisión por hablantes nativos de cada idioma aún no se ha completado. English

Para quién esEsta guía está dirigida a personas que reciben archivos CSV con varios formatos de fecha y necesitan un proceso de limpieza repetible que no interprete silenciosamente valores ambiguos.

Preparación
  • Python 3.12 y un comando de terminal que inicie esa versión.
  • Un editor de texto que pueda guardar archivos CSV en UTF-8.
  • Una carpeta de trabajo donde el script pueda crear una nueva carpeta dentro de outputs.
  • Solo se requiere la biblioteca estándar de Python: csv, datetime y pathlib.

01Definir los formatos de fecha aceptados antes de analizarlos

La limpieza de fechas es más segura cuando los formatos aceptados se definen explícitamente. Este ejemplo reconoce seis formatos numéricos y convierte todos los valores interpretados correctamente a YYYY-MM-DD. No utiliza detección automática de fechas.

Ejemplo de entrada aceptadaSignificadoFormato de Python
2026-09-01año-mes-día%Y-%m-%d
09/02/2026mes/día/año%m/%d/%Y
2026/09/03año/mes/día%Y/%m/%d
04-09-2026día-mes-año%d-%m-%Y
2026.09.05año.mes.día%Y.%m.%d
20260906añomesdía%Y%m%d

La interpretación de los formatos con barras y guiones es una decisión de política. En este tutorial, 09/02/2026 significa 2 de septiembre de 2026, mientras que 04-09-2026 significa 4 de septiembre de 2026. Si tu fuente utiliza otras convenciones, cambia los formatos aceptados antes de procesar datos reales.

02Crear un pequeño CSV sintético

El siguiente conjunto de datos es sintético y fue creado específicamente para este artículo. Guárdalo como mixed_dates.csv en tu carpeta de trabajo. Contiene seis fechas válidas en seis formatos aceptados diferentes y dos valores que deberían fallar.

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

Hay 8 registros de datos. Los primeros 6 deberían interpretarse correctamente. R007 utiliza un patrón reconocido, pero el 30 de febrero no es una fecha real. R008 utiliza un nombre de mes en texto que se ha excluido intencionadamente de la lista de formatos aceptados.

03Calcular manualmente los valores limpios esperados

Los primeros seis valores representan fechas consecutivas desde el 1 hasta el 6 de septiembre de 2026. Por tanto, deberían producir seis valores estandarizados. Los dos registros restantes deberían conservar el texto original en el archivo de revisión.

record_idValor originalValor estandarizado esperadoEstado
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

Por tanto, los recuentos esperados son 8 registros totales, 6 registros interpretados y 2 registros no interpretados. El CSV limpio seguirá conteniendo los 8 registros; las filas no interpretadas tendrán un valor standardized_date vacío y el estado UNPARSED.

04Analizar los formatos autorizados y escribir nuevos archivos

Guarda el siguiente script como date_format_cleanup.py. Lee el CSV original, escribe una copia completa y limpia y también crea un CSV de revisión más pequeño que contiene únicamente los registros cuya fecha no pudo estandarizarse.

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

05Comparar los archivos de salida esperados

cleaned_dates.csv debería contener las tres columnas originales más standardized_date y date_status. Los 8 registros originales deben seguir presentes. Las dos filas fallidas no se eliminan.

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 debería contener exactamente 2 filas de datos. Como el encabezado del CSV original corresponde físicamente a la línea 1, R007 está en la fila 8 del CSV y R008 en la fila 9.

row_numberrecord_idevent_date
8R0072026-02-30
9R008Sep 7 2026

06Comprobar los recuentos y la salida de consola

La salida de consola esperada que aparece a continuación se obtuvo manualmente a partir de la entrada sintética y del script. No es un registro capturado de una ejecución.

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. Confirma que cleaned_dates.csv contenga 8 filas de datos y no solamente las 6 filas procesadas correctamente.
  2. Confirma que las fechas estandarizadas vayan desde 2026-09-01 hasta 2026-09-06 para R001 a R006.
  3. Confirma que R007 y R008 tengan valores standardized_date vacíos y estado UNPARSED.
  4. Confirma que unparsed_dates.csv contenga exactamente R007 y R008.
  5. Ejecuta el script de nuevo sin cambiar OUTPUT_DIR. Debería detenerse con FileExistsError en lugar de sobrescribir el resultado anterior.

07Reconocer errores comunes

SíntomaQué comprobar
FileNotFoundErrorComprueba SOURCE y el directorio de trabajo actual del terminal.
Falta una columna obligatoriaConfirma que el encabezado contenga exactamente event_date o cambia DATE_COLUMN de forma deliberada.
Demasiados valores no interpretadosRevisa las convenciones de la fuente y añade únicamente formatos que estén documentados y no sean ambiguos para ese conjunto de datos.
Mes y día aparecen invertidos inesperadamenteComprueba si las fechas con barras o guiones utilizan primero el mes o el día antes de añadir su formato.
FileExistsErrorLa carpeta de resultados ya existe. Revísala y selecciona un nuevo destino de salida en lugar de sobrescribirla.
Una fecha aparentemente válida sigue fallandoLos caracteres pueden contener espacios, utilizar un formato no incluido en FORMATS o representar una fecha de calendario inválida.

No respondas a una tasa elevada de fallos añadiendo numerosos formatos especulativos. Eso puede convertir datos incorrectos detectables en fechas interpretadas erróneamente sin aviso.

08Comprender los límites antes de escalar el proceso

Este ejemplo solo maneja fechas de calendario. No interpreta horas, zonas horarias, fechas seriales de Excel, nombres de meses localizados, años de dos dígitos ni valores que contengan texto descriptivo adicional. Esos casos necesitan reglas explícitas.

datetime.strptime valida las fechas del calendario, por lo que se rechaza un valor estructuralmente plausible como 2026-02-30. Esto es útil, pero una fecha interpretada correctamente no demuestra que el valor de origen sea objetivamente correcto. Un error tipográfico como 2026-09-12 en lugar de 2026-09-21 sigue siendo una fecha válida del calendario.

El orden de FORMATS importa cuando dos patrones aceptados pueden interpretar una misma cadena de forma diferente. Un proceso de limpieza de producción debería documentar la convención de la fuente, conservar el valor original, informar de los fallos y revisar los formatos ambiguos en lugar de depender únicamente del análisis automático.

Registro de ejecución y verificación

2026-09-20 · ejemplo revisado manualmente · objetivo: Python 3.12 · biblioteca estándar: csv, datetime, pathlib · sin ejecución

  • Se contaron manualmente 8 registros de origen, con 6 interpretaciones correctas esperadas y 2 fallos esperados.
  • Se convirtieron manualmente las seis entradas aceptadas a fechas estandarizadas consecutivas desde 2026-09-01 hasta 2026-09-06.
  • Se confirmó mediante razonamiento de calendario que 2026-02-30 es inválida y que Sep 7 2026 no coincide con ningún formato incluido en FORMATS.
  • Se obtuvieron manualmente los números de fila 8 y 9 del archivo de revisión a partir de las posiciones de línea en el CSV de origen.
  • Se revisó el código para confirmar que el CSV original solo se lee, que los resultados se escriben en una carpeta separada y que una carpeta de resultados ya existente hace que la ejecución se detenga.
  • Se obtuvieron manualmente los recuentos esperados de registros procesados, interpretados y no interpretados, así como los mensajes de consola.
Límites de la verificación
  • El código no fue ejecutado por el autor de esta respuesta; no se crearon archivos CSV de salida.
  • No se probaron nombres de meses dependientes de la configuración regional, valores de hora, zonas horarias, fechas seriales de Excel, años de dos dígitos ni archivos muy grandes.
  • Las convenciones de fecha aceptadas son políticas de ejemplo y deben cambiarse si la fuente real utiliza significados diferentes.
  • Las URL de la documentación oficial se proporcionaron a partir de ubicaciones de documentación conocidas, pero no se comprobaron en línea.

Criterios de redacción y verificación de todo el sitio

Fuentes de referencia

Las explicaciones y los ejemplos son de elaboración propia. Puedes consultar los comportamientos y conceptos relacionados en las siguientes fuentes oficiales.