Cómo convertir múltiples formatos de fecha en Apache Spark con Java y evitar errores comunes

 Cuando trabajamos con Dataset<Row> en Apache Spark, un desafío común es el formato inconsistente de fechas. Algunas columnas pueden traer la fecha como "1987-12-05", otras como "2007-06-15 00:00:00 +0100 +01:00", y algunas incluso como texto plano. Este problema puede causar errores de parseo y valores null inesperados.

En este artículo te mostraré cómo resolver esto de forma robusta y escalable con Java, renombrando columnas, manejando múltiples formatos y evitando valores nulos.


🎯 Objetivo

Queremos transformar varias columnas de un Dataset<Row> que contienen fechas en diferentes formatos, para:

  1. Renombrar las columnas con nombres estándar.

  2. Convertir el contenido a tipo Date si es necesario.

  3. Agregar una columna formateada en estilo dd/MM/yy para persistencia o exportación.


📦 ¿Por qué puede fallar el parseo?

Spark utiliza to_date() con un patrón de formato específico. Si le pasas un valor que no se ajusta exactamente, te devolverá null.

Por ejemplo:

to_date("2007-06-15", "yyyy-MM-dd") ✅ to_date("2007-06-15 00:00:00", "yyyy-MM-dd") ❌

Solución: usar substring() y validar el tipo de dato antes de convertir.


🛠️ Código completo


import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.types.StructType;
import static org.apache.spark.sql.functions.*;
import java.util.*;

public Dataset<Row> transformarFechas(Dataset<Row> dataset) {
    Map<String, String> columnas = new LinkedHashMap<>();
    columnas.put("fecha_nac", "fechaNacimiento");
    columnas.put("fecha_ing", "fechaIngreso");
    columnas.put("fecha_egre", "fechaEgreso");
    columnas.put("fecha_doc", "fechaDocumento");
    columnas.put("fecha_val", "fechaValidacion");
    columnas.put("fecha_act", "fechaActualizacion");
    columnas.put("fecha_baja", "fechaBaja");
    columnas.put("fecha_crea", "fechaCreacion");

    Dataset<Row> df = dataset;
    StructType schema = df.schema();

    for (Map.Entry<String, String> entry : columnas.entrySet()) {
        String original = entry.getKey();
        String nuevo = entry.getValue();

        if (Arrays.asList(schema.fieldNames()).contains(original)) {
            String tipo = schema.apply(original).dataType().typeName();

            // Renombrar la columna
            df = df.withColumnRenamed(original, nuevo);

            // Si ya es tipo Date o Timestamp, solo dar formato
            if (tipo.equals("date") || tipo.equals("timestamp")) {
                df = df.withColumn(
                    nuevo + "Texto",
                    date_format(col(nuevo), "dd/MM/yy")
                );
            } else {
                // Si es texto u otro tipo, recortar y convertir
                df = df
                    .withColumn(
                        nuevo,
                        to_date(substring(col(nuevo).cast("string"), 0, 10), "yyyy-MM-dd")
                    )
                    .withColumn(
                        nuevo + "Texto",
                        date_format(col(nuevo), "dd/MM/yy")
                    );
            }
        }
    }

    return df;
}




public Dataset<Row> transformarFechas(Dataset<Row> dataset) {
    Map<String, String> columnas = new LinkedHashMap<>();
    columnas.put("fecha_nac", "fechaNacimiento");
    columnas.put("fecha_ing", "fechaIngreso");
    columnas.put("fecha_egre", "fechaEgreso");
    columnas.put("fecha_doc", "fechaDocumento");
    columnas.put("fecha_val", "fechaValidacion");
    columnas.put("fecha_act", "fechaActualizacion");
    columnas.put("fecha_baja", "fechaBaja");
    columnas.put("fecha_crea", "fechaCreacion");

    Dataset<Row> df = dataset;
    StructType schema = df.schema();

    for (Map.Entry<String, String> entry : columnas.entrySet()) {
        String original = entry.getKey();
        String nuevo = entry.getValue();

        if (Arrays.asList(schema.fieldNames()).contains(original)) {
            String tipo = schema.apply(original).dataType().typeName();

            if (tipo.equals("date") || tipo.equals("timestamp")) {
                // Si ya es tipo Date o Timestamp
                df = df.withColumn(nuevo, col(original))
                      .withColumn(nuevo + "Texto", 
                           date_format(col(original), "dd/MM/yy"));
            } else {
                // Para otros tipos (string, etc.)
                df = df.withColumn(nuevo, 
                        coalesce(
                            to_date(col(original).cast("string"), "yyyy-MM-dd"),
                            to_date(col(original).cast("string"), "dd/MM/yyyy"),
                            to_date(col(original).cast("string"), "MM/dd/yyyy")
                        ))
                      .withColumn(nuevo + "Texto", 
                           when(col(nuevo).isNotNull(), 
                               date_format(col(nuevo), "dd/MM/yy"))
                           .otherwise(lit(null)));
            }
            
            // Eliminar la columna original si es diferente del nuevo nombre
            if (!original.equals(nuevo)) {
                df = df.drop(original);
            }
        }
    }

    return df;
}



public Dataset<Row> transformarFechas(Dataset<Row> dataset) {
    // Mapa de columnas (igual que antes)
    
    Dataset<Row> df = dataset;

    for (Map.Entry<String, String> entry : columnas.entrySet()) {
        String original = entry.getKey();
        String nuevo = entry.getValue();

        if (Arrays.asList(df.columns()).contains(original)) {
            // Intentar con formato yyyy-MM-dd primero
            Column fechaConvertida = to_date(col(original).cast("string"), "yyyy-MM-dd");
            
            // Si falla, intentar con dd/MM/yyyy
            fechaConvertida = when(fechaConvertida.isNull(), 
                                 to_date(col(original).cast("string"), "dd/MM/yyyy"))
                             .otherwise(fechaConvertida);
            
            // Si aún falla, intentar con MM/dd/yyyy
            fechaConvertida = when(fechaConvertida.isNull(),
                                 to_date(col(original).cast("string"), "MM/dd/yyyy"))
                             .otherwise(fechaConvertida);
            
            // Aplicar la conversión
            df = df.withColumn(nuevo, fechaConvertida)
                  .withColumn(nuevo + "Texto", date_format(col(nuevo), "dd/MM/yy"))
                  .drop(original);
        }
    }

    return df;
}

Comentarios

Entradas populares de este blog

Pequeño server local

CobolParser Para Spark

iteratorSeguro