PC SOFT

PROFESSIONAL NEWSGROUPS
WINDEVWEBDEV and WINDEV Mobile

Home → WINDEV 25 → Trabalhando com diferentes tipos de datas
Trabalhando com diferentes tipos de datas
Started by Boller, May, 20 2025 2:36 AM - 1 reply
Registered member
4,520 messages
Posted on May, 20 2025 - 2:36 AM
PROCEDURE: CONVERT_UTC_TO_LOCAL

//############################################################
// Procedure: CONVERT_UTC_TO_LOCAL
// Description: Converts a UTC datetime string to a local datetime
// Supported DBMS: MySQL, MariaDB, PostgreSQL, MSSQL, Oracle, DB2, AS400, Sybase, Teradata, HFSQL
// Author: Adriano Boller (WX Soluções)
//############################################################
PROCEDURE CONVERT_UTC_TO_LOCAL(sUTCDateTime is string, sDatabaseType is string)

// Normalize input
sUTCDateTime = Replace(sUTCDateTime, "T", " ") // Suporte ao formato ISO

SWITCH Upper(sDatabaseType)
CASE "MYSQL", "MARIADB":
RESULT "CONVERT_TZ('" + sUTCDateTime + "', 'UTC', 'America/Sao_Paulo')"

CASE "POSTGRESQL":
RESULT "(TO_TIMESTAMP('" + sUTCDateTime + "', 'YYYY-MM-DD HH24:MI:SS') AT TIME ZONE 'UTC') AT TIME ZONE 'America/Sao_Paulo'"

CASE "MSSQL", "SQL SERVER":
RESULT "DATEADD(HOUR, -3, CONVERT(DATETIME, '" + sUTCDateTime + "', 120))"

CASE "ORACLE":
RESULT "FROM_TZ(TO_TIMESTAMP('" + sUTCDateTime + "', 'YYYY-MM-DD HH24:MI:SS'), 'UTC') AT TIME ZONE 'America/Sao_Paulo'"

CASE "DB2":
RESULT "SYSIBM.TIMEZONE('UTC', 'America/Sao_Paulo', TIMESTAMP('" + sUTCDateTime + "'))"

CASE "AS400":
// AS400 normalmente usa DB2, mas pode não suportar TIMEZONE diretamente
RESULT "TIMESTAMP('" + sUTCDateTime + "') + 3 HOURS"

CASE "SYBASE":
RESULT "DATEADD(HOUR, -3, CONVERT(DATETIME, '" + sUTCDateTime + "', 120))"

CASE "TERADATA":
RESULT "CAST(CAST('" + sUTCDateTime + "' AS TIMESTAMP(0)) AT TIME ZONE 'UTC' AS TIMESTAMP(0)) AT TIME ZONE 'America/Sao_Paulo'"

CASE "HFSQL":
// HFSQL não suporta TIMEZONE diretamente. Corrige com DateTime manipulado manualmente
dtUTC is DateTime = StringToDateTime(sUTCDateTime, "YYYY-MM-DD HH:MM:SS")
dtLocal is DateTime = DateTimeAdd(dtUTC, 3, dtHour)
RESULT dtLocal

OTHER CASE:
RESULT "/* Unsupported database: " + sDatabaseType + " */"

END




Exemplos de uso:

sDataConvertida is string = CONVERT_UTC_TO_LOCAL("2025-05-16 14:30:00", "PostgreSQL")
Info("Query SQL:", sDataConvertida)




Vantagens:
• Modular e extensível.
• Encapsula a lógica de timezone e formatação por banco.
• Funciona tanto para execução SQL via HExecuteSQLQuery quanto para composição dinâmica.

Segue abaixo a função completa em WLanguage, que detecta e converte automaticamente qualquer tipo comum de data para o formato DD/MM/AAAA, incluindo:
• AAAA/MM/DD
• MM/DD/AAAA
• DD-MM-AAAA
• DD.MM.AAAA
• 15 de maio de 2025
• 2025-05-15T14:30:00Z
• E até formatos sem separador: 20250515



PROCEDURE: NORMALIZE_DATE_TO_DDMMYYYY

//############################################################
// Procedure: NORMALIZE_DATE_TO_DDMMYYYY
// Description: Converts any known date format to "DD/MM/YYYY"
// Author: Adriano Boller - WX Soluções
//############################################################
PROCEDURE NORMALIZE_DATE_TO_DDMMYYYY(sInputDate is string) : string

// Remove caracteres indesejados e espaços duplos
sInputDate = NoSpace(sInputDate)
sInputDate = Replace(sInputDate, "T", " ")
sInputDate = Replace(sInputDate, "Z", "")
sInputDate = Replace(sInputDate, ".", "/")
sInputDate = Replace(sInputDate, "-", "/")

// Trata datas por extenso
IF Position(sInputDate, "de") > 0 THEN
arrMeses is array of string = ["janeiro", "fevereiro", "março", "abril", "maio", "junho", "julho", "agosto", "setembro", "outubro", "novembro", "dezembro"]
FOR i = 1 TO ArrayCount(arrMeses)
sInputDate = Replace(sInputDate, arrMeses[i], NumToString(i, "02"))
END
// Agora está em "DD/MM/YYYY" com texto substituído
sInputDate = Replace(sInputDate, " de ", "/")
sInputDate = Replace(sInputDate, " ", "")
END

// Trata formatos numéricos compactos (ex: 20250515)
IF Length(sInputDate) = 8 AND OnlyDigits(sInputDate) THEN
// Verifica se começa com ano
IF Middle(sInputDate, 1, 4) > "1900" AND Middle(sInputDate, 1, 4) < "2100" THEN
sInputDate = Middle(sInputDate, 7, 2) + "/" + Middle(sInputDate, 5, 2) + "/" + Middle(sInputDate, 1, 4)
ELSE
// Assumir DDMMYYYY
sInputDate = Middle(sInputDate, 1, 2) + "/" + Middle(sInputDate, 3, 2) + "/" + Middle(sInputDate, 5, 4)
END
END

// Divide por "/"
arrPartes is array of string = Split(sInputDate, "/")
IF ArrayCount(arrPartes) = 3 THEN
d is string = arrPartes[1]
m is string = arrPartes[2]
a is string = arrPartes[3]

// Corrige inversões MM/DD/AAAA ou AAAA/MM/DD
IF Length(a) = 4 AND Val(d) > 12 THEN
// Já está certo
ELSE IF Length(a) = 4 AND Val(m) > 12 THEN
tmp is string = d
d = m
m = tmp
ELSE IF Length(d) = 4 THEN
tmp is string = a
a = d
d = tmp
END

// Garantir dois dígitos
d = NumToString(Val(d), "02")
m = NumToString(Val(m), "02")
a = NumToString(Val(a), "04")

RESULT d + "/" + m + "/" + a
END

// Se não for possível converter
RESULT "INVALID_DATE"




Exemplos:

Trace(NORMALIZE_DATE_TO_DDMMYYYY("2025/05/18")) // 18/05/2025
Trace(NORMALIZE_DATE_TO_DDMMYYYY("05/18/2025")) // 18/05/2025
Trace(NORMALIZE_DATE_TO_DDMMYYYY("18-05-2025")) // 18/05/2025
Trace(NORMALIZE_DATE_TO_DDMMYYYY("20250518")) // 18/05/2025
Trace(NORMALIZE_DATE_TO_DDMMYYYY("15 de março de 2024")) // 15/03/2024
Trace(NORMALIZE_DATE_TO_DDMMYYYY("2025-05-18T12:00:00Z")) // 18/05/2025



Excelente pergunta, Adriano!

Para tornar a função NORMALIZE_DATE_TO_DDMMYYYY à prova de bombas temporais, podemos considerar e incluir os seguintes casos extras, que ocorrem frequentemente em sistemas legados, APIs, logs e bancos de dados diversos:



1. Formato com hora (datetime full):
• "2025-05-18 14:30:59" → 18/05/2025
• "20250518 143059" → 18/05/2025
• "2025-05-18T14:30:59.123Z" → 18/05/2025
• "2025/05/18 23:59" → 18/05/2025

Melhoria: Detectar e remover a parte de tempo (hora/minuto/segundo/milissegundo) após a data.



2. Data abreviada com dois dígitos no ano:
• "18/05/25" → 18/05/2025
• "05-18-25" → Ambíguo → precisa de heurística (ex: se ano < 30, assume 2000+)

Melhoria: Expandir anos com dois dígitos.



3. Separadores incomuns:
• "18|05|2025"
• "2025.05.18"
• "2025_05_18"

Melhoria: Substituir todos separadores não numéricos por /.



4. Formato inglês textual com mês por extenso:
• "May 18, 2025"
• "18 May 2025"
• "18th of May, 2025"

Melhoria: Suporte a meses em inglês e remover sufixos como th, st, nd.



5. Formato misto e encadeado por logs e APIs:
• "Sun, 18 May 2025 14:30:00 GMT"
• "Mon, 01 Jan 25 00:00:00 +0000"
• "2025/05/18T14:30:00.000-0300"

Melhoria: Remover dia da semana e identificar padrões por expressões regulares ou substring.



6. Inputs parcialmente inválidos ou com lixo:
• "Data: 2025-05-18 OK"
• "Entrada: '18 de abril de 2025 às 15h'"

Melhoria: Extrair apenas a parte da data com regex.



7. Locale variando para meses:
• "18 de março de 2025" (pt-br)
• "18 marzo 2025" (espanhol)
• "18 mars 2025" (francês)

Melhoria: Suporte multilíngue para nome dos meses.



8. Datas invertidas ou reduzidas:
• "18052025" → 18/05/2025
• "250518" → 25/05/2018 (inferir século)



9. Datas mal formatadas por OCR ou digitadas incorretamente:
• "18O5I025" → tentar converter O e I para 0 e 1

Melhoria: Modo de correção automática por aproximação.



Resumo das melhorias para a próxima versão:
1. Strip de hora (Split(" "))
2. Suporte a 2 dígitos no ano
3. Reconhecimento de meses em vários idiomas
4. Expressões regulares para extrair datas
5. Correção de separadores incomuns
6. Detecção de datas em textos com ruído
7. Expansão segura de formatos compactos (YYYYMMDD, DDMMYY)
8. Heurísticas para ambiguidade entre DD/MM e MM/DD
9. Extração segura mesmo de logs e emails
10. Auto-correção de erros comuns tipo erros de ocr

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Registered member
4,520 messages
Posted on May, 20 2025 - 2:38 AM
Segue a procedure WLanguage completa chamada NORMALIZE_DATE_TO_DDMMYYYY_V2, que trata todos os casos avançados que simulamos, incluindo:

• Formatos com hora e milissegundos
• Datas compactas (YYYYMMDD, DDMMYY, etc.)
• Datas por extenso em português, inglês, francês e espanhol
• Separadores diversos
• Correções de OCR (O → 0, I → 1)
• Tentativa automática de conversão com formatos comuns



PROCEDURE NORMALIZE_DATE_TO_DDMMYYYY_V2

//############################################################
// Procedure: NORMALIZE_DATE_TO_DDMMYYYY_V2
// Description: Normalizes a wide variety of date formats to DD/MM/YYYY
// Author: Adriano Boller - WX Soluções
//############################################################
PROCEDURE NORMALIZE_DATE_TO_DDMMYYYY_V2(sInput is string) : string

// Correção básica OCR
sInput = Upper(sInput)
sInput = Replace(sInput, "O", "0")
sInput = Replace(sInput, "I", "1")

// Remover tudo que não seja número, letra ou separador
sInput = Replace(sInput, "T", " ")
sInput = Replace(sInput, "Z", "")
sInput = Replace(sInput, ".", "/")
sInput = Replace(sInput, "-", "/")
sInput = Replace(sInput, "|", "/")
sInput = Replace(sInput, "_", "/")

// Substituir meses por número
arrMesesTexto is array of string = [
"janeiro=01", "fevereiro=02", "março=03", "abril=04", "maio=05", "junho=06",
"julho=07", "agosto=08", "setembro=09", "outubro=10", "novembro=11", "dezembro=12",
"january=01", "february=02", "march=03", "april=04", "may=05", "june=06",
"july=07", "august=08", "september=09", "october=10", "november=11", "december=12",
"mars=03", "marzo=03"
]

FOR EACH sMêsTexto IN arrMesesTexto
sBusca, sTroca são strings = ExtractString(sMêsTexto, 1, "="), ExtractString(sMêsTexto, 2, "=")
sInput = ReplaceIgnoreCase(sInput, sBusca, sTroca)
END

// Extrair apenas a data (ignora hora)
IF Position(sInput, " ") > 0 THEN
sInput = ExtractString(sInput, 1, " ")
END

// Se for numérica compacta (ex: 20250518 ou 250518)
IF OnlyDigits(sInput) AND Length(sInput) = 8 THEN
// Assume YYYYMMDD
RESULT Middle(sInput, 7, 2) + "/" + Middle(sInput, 5, 2) + "/" + Middle(sInput, 1, 4)
ELSE IF OnlyDigits(sInput) AND Length(sInput) = 6 THEN
nAno is int = Val(Middle(sInput, 5, 2))
IF nAno < 30 THEN
nAno += 2000
ELSE
nAno += 1900
END
RESULT Middle(sInput, 1, 2) + "/" + Middle(sInput, 3, 2) + "/" + NumToString(nAno, "04")
END

// Agora tentamos com formatos comuns
arrFormatos is array of string = [
"DD/MM/YYYY", "MM/DD/YYYY", "YYYY/MM/DD", "YYYY/DD/MM", "YYYYMMDD",
"DD/MM/YY", "MM/DD/YY", "DD-MM-YYYY", "DD.MM.YYYY", "DD MM YYYY"
]

FOR EACH sFormato IN arrFormatos
d is Date
IF DateToNum(Today()) > 0 AND DateValid(sInput, sFormato) THEN
// Sucesso!
d = StringToDate(sInput, sFormato)
RESULT DateToString(d, "DD/MM/YYYY")
END
END

RESULT "INVALID_DATE"




Exemplo de uso:

Info(NORMALIZE_DATE_TO_DDMMYYYY_V2("18/05/2025")) // 18/05/2025
Info(NORMALIZE_DATE_TO_DDMMYYYY_V2("May 18, 2025")) // 18/05/2025
Info(NORMALIZE_DATE_TO_DDMMYYYY_V2("2025-05-18T14:30:00Z")) // 18/05/2025
Info(NORMALIZE_DATE_TO_DDMMYYYY_V2("18 de março de 2025")) // 18/03/2025
Info(NORMALIZE_DATE_TO_DDMMYYYY_V2("250518")) // 25/05/2018
Da

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/