Files
Temp_MSSPLASHPage/TIMEZONE_VALIDATION_SCRIPT.md

6.1 KiB

Timezone Validation Script

🔍 Problema Detectado

Datos actuales:

  • BD: 2025-10-15 01:55:00+00 (UTC)
  • API retorna: 2025-10-14T19:55:00Z (UTC)
  • Frontend muestra: 14/10/2025, 13:55:00 (America/Mexico_City = UTC-6)

Análisis:

  • BD → API: Diferencia de 6 horas (Oct 15 01:55 → Oct 14 19:55)
  • API → Frontend: Correcto (Oct 14 19:55 UTC → Oct 14 13:55 CST)

🐛 Causa Probable

El ValueConverter en el DbContext está causando una conversión incorrecta:

// SplashPageDbContext.cs línea 66-69
property.SetValueConverter(new ValueConverter<DateTime, DateTime>(
    v => v.ToUniversalTime(),                    // Al guardar: siempre UTC
    v => DateTime.SpecifyKind(v, DateTimeKind.Utc) // Al leer: marcar como UTC ❌
));

Problema:

  1. PostgreSQL almacena: 2025-10-15 01:55:00+00 (UTC)
  2. Npgsql lee y convierte al timezone local del servidor (probablemente UTC-6)
  3. El ValueConverter solo hace SpecifyKind(v, Utc) sin reconvertir
  4. Resultado: La fecha está en timezone local pero marcada como UTC

Pasos de Validación

1. Verificar Timezone del Servidor

Ejecuta en el servidor donde corre el backend:

# Linux/Mac
date
timedatectl

# Windows
tzutil /g

2. Verificar Configuración de PostgreSQL

Ejecuta en PostgreSQL:

-- Ver timezone de la sesión
SHOW timezone;

-- Ver la fecha tal como está almacenada
SELECT
    "ScheduledDateTime",
    "ScheduledDateTime" AT TIME ZONE 'UTC' as utc_time,
    "ScheduledDateTime" AT TIME ZONE 'America/Mexico_City' as mexico_time
FROM "SplashScheduledEmails"
WHERE "Id" = 'tu-guid-aqui';

3. Crear Endpoint de Diagnóstico Temporal

Agrega este controlador temporal para diagnosticar:

Archivo: src/SplashPage.Web.Host/Controllers/DiagnosticController.cs

using Microsoft.AspNetCore.Mvc;
using Abp.Domain.Repositories;
using SplashPage.Splash;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace SplashPage.Web.Host.Controllers
{
    [Route("api/[controller]")]
    public class DiagnosticController : SplashPageControllerBase
    {
        private readonly IRepository<SplashScheduledEmail, Guid> _repository;

        public DiagnosticController(IRepository<SplashScheduledEmail, Guid> repository)
        {
            _repository = repository;
        }

        [HttpGet("timezone-test/{id}")]
        public async Task<object> TimezoneTest(Guid id)
        {
            var entity = await _repository.FirstOrDefaultAsync(id);
            if (entity == null) return NotFound();

            var scheduledDateTime = entity.ScheduledDateTime;

            return new
            {
                // Información del servidor
                serverTimezone = TimeZoneInfo.Local.DisplayName,
                serverTimezoneId = TimeZoneInfo.Local.Id,
                serverCurrentTime = DateTime.Now,
                serverCurrentTimeUtc = DateTime.UtcNow,

                // Fecha desde la BD
                fromDatabase = new
                {
                    value = scheduledDateTime,
                    kind = scheduledDateTime.Kind.ToString(),
                    ticks = scheduledDateTime.Ticks,

                    // Diferentes representaciones
                    asString = scheduledDateTime.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"),
                    asIso = scheduledDateTime.ToString("o"),
                    toUtc = scheduledDateTime.ToUniversalTime(),
                    toLocal = scheduledDateTime.ToLocalTime(),
                },

                // Lo que se serializa en JSON
                serializedValue = scheduledDateTime // ASP.NET lo serializa automáticamente
            };
        }
    }
}

4. Llamar al Endpoint de Diagnóstico

# Reemplaza {id} con un GUID real de tu BD
curl https://localhost:44311/api/Diagnostic/timezone-test/{id}

5. Verificar la Respuesta

Compara:

  • fromDatabase.value vs serializedValue
  • fromDatabase.kind - Debería ser "Utc"
  • fromDatabase.toUtc vs valor en BD

🔧 Solución

Opción 1: Corregir ValueConverter (Recomendado)

Archivo: src/SplashPage.EntityFrameworkCore/EntityFrameworkCore/SplashPageDbContext.cs

// Reemplazar líneas 66-69 con:
property.SetValueConverter(new ValueConverter<DateTime, DateTime>(
    v => DateTime.SpecifyKind(v, DateTimeKind.Utc), // Al guardar: asegurar que es UTC
    v => DateTime.SpecifyKind(v, DateTimeKind.Utc)  // Al leer: marcar como UTC (sin conversión)
));

Nota: PostgreSQL con Npgsql ya maneja la conversión correctamente si le indicamos que use UTC.

Opción 2: Configurar Npgsql para usar UTC

Archivo: src/SplashPage.EntityFrameworkCore/EntityFrameworkCore/SplashPageEntityFrameworkModule.cs

Agregar en la configuración de DbContext:

AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", false);
AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);

Opción 3: Remover ValueConverter Completamente

Si PostgreSQL + Npgsql ya están manejando UTC correctamente:

// Comentar o eliminar el ValueConverter
// foreach (var property in entityType.GetProperties())
// {
//     if (property.ClrType == typeof(DateTime) || property.ClrType == typeof(DateTime?))
//     {
//         property.SetValueConverter(...);
//     }
// }

🧪 Test Esperado

Después de la corrección:

  • BD: 2025-10-15 01:55:00+00 (UTC)
  • API retorna: 2025-10-15T01:55:00Z (UTC)
  • Frontend muestra: 14/10/2025, 19:55:00 (America/Mexico_City = UTC-6)

📝 Notas Importantes

  1. Restart requerido: Después de cambiar el ValueConverter, reinicia la aplicación
  2. Migración NO requerida: Este es solo un cambio de configuración
  3. Datos existentes: No se requiere actualizar datos en BD
  4. Compatibilidad: Verifica que otros DateTime fields funcionen correctamente

🚀 Ejecución de Validación

  1. Ejecutar endpoint de diagnóstico
  2. Aplicar la corrección elegida
  3. Reiniciar aplicación
  4. Re-ejecutar endpoint de diagnóstico
  5. Verificar que fechas coincidan
  6. Probar en frontend que fechas se muestren correctamente