core: fix timezone

Signed-off-by: Rodrigo Bertoldo de Sousa Martins <rodrigo.sousa.577@gmail.com>
This commit is contained in:
rodrigo committed 2026-03-29 17:10:28 -03:00
1 parent 5193b2df08
commit c42202b727
3 files changed
+81 -22

No files matched your search

+1
View File
@@ -33,6 +33,7 @@ EXPOSE 3000
# Definir variáveis de ambiente padrão # Definir variáveis de ambiente padrão
ENV NODE_ENV=production ENV NODE_ENV=production
ENV BOT_DEBUG=false ENV BOT_DEBUG=false
ENV TZ=America/Araguaina
# Health check # Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
+3
View File
@@ -21,6 +21,9 @@ services:
# Usuários autorizados # Usuários autorizados
- ALLOWED_USERS=${ALLOWED_USERS} - ALLOWED_USERS=${ALLOWED_USERS}
# Fuso horário
- TZ=America/Araguaina
# Volumes para logs persistentes # Volumes para logs persistentes
volumes: volumes:
- ./logs:/app/logs - ./logs:/app/logs
+77 -22
View File
@@ -111,10 +111,13 @@ class DateProcessor {
if (this.diasSemana.hasOwnProperty(diaNome)) { if (this.diasSemana.hasOwnProperty(diaNome)) {
const diaSemanaTarget = this.diasSemana[diaNome]; const diaSemanaTarget = this.diasSemana[diaNome];
const data = this.obterProximoDiaSemana(diaSemanaTarget); const proxData = this.obterProximoDiaSemana(diaSemanaTarget);
data.setHours(hora, minuto, 0, 0);
if (this.isValidTime(hora, minuto)) { if (this.isValidTime(hora, minuto)) {
// Obter componentes da data no fuso Brasil
const partes = this.extrairPartesBrasil(proxData);
const data = this.criarDataBrasil(partes.ano, partes.mes, partes.dia, hora, minuto);
return { return {
success: true, success: true,
data, data,
@@ -153,11 +156,15 @@ class DateProcessor {
if (this.palavrasTemporais.hasOwnProperty(palavraTemporal)) { if (this.palavrasTemporais.hasOwnProperty(palavraTemporal)) {
const diasOffset = this.palavrasTemporais[palavraTemporal]; const diasOffset = this.palavrasTemporais[palavraTemporal];
const data = new Date();
data.setDate(data.getDate() + diasOffset);
data.setHours(hora, minuto, 0, 0);
if (this.isValidTime(hora, minuto)) { if (this.isValidTime(hora, minuto)) {
// Basear no dia atual no fuso Brasil e aplicar offset
const hoje = this.extrairPartesBrasil(new Date());
const base = this.criarDataBrasil(hoje.ano, hoje.mes, hoje.dia, 12, 0);
base.setDate(base.getDate() + diasOffset);
const partes = this.extrairPartesBrasil(base);
const data = this.criarDataBrasil(partes.ano, partes.mes, partes.dia, hora, minuto);
return { return {
success: true, success: true,
data, data,
@@ -213,7 +220,7 @@ class DateProcessor {
if (ano < 100) ano += 2000; // Converter anos de 2 dígitos if (ano < 100) ano += 2000; // Converter anos de 2 dígitos
if (mes !== undefined && this.isValidDate(dia, mes, ano) && this.isValidTime(hora, minuto)) { if (mes !== undefined && this.isValidDate(dia, mes, ano) && this.isValidTime(hora, minuto)) {
const data = new Date(ano, mes, dia, hora, minuto, 0, 0); const data = this.criarDataBrasil(ano, mes, dia, hora, minuto);
return { return {
success: true, success: true,
@@ -251,34 +258,55 @@ class DateProcessor {
const quantidade = parseInt(match[1]); const quantidade = parseInt(match[1]);
const unidade = match[2].toLowerCase(); const unidade = match[2].toLowerCase();
const data = new Date(); let data = new Date();
if (unidade.includes('dia')) { if (unidade.includes('dia')) {
data.setDate(data.getDate() + quantidade); const base = this.extrairPartesBrasil(new Date());
const tmpDate = this.criarDataBrasil(base.ano, base.mes, base.dia, 12, 0);
tmpDate.setDate(tmpDate.getDate() + quantidade);
if (match[3] && match[4] !== undefined) { if (match[3] && match[4] !== undefined) {
const hora = parseInt(match[3]); const hora = parseInt(match[3]);
const minuto = match[4] ? parseInt(match[4]) : 0; const minuto = match[4] ? parseInt(match[4]) : 0;
if (this.isValidTime(hora, minuto)) { if (this.isValidTime(hora, minuto)) {
data.setHours(hora, minuto, 0, 0); const partes = this.extrairPartesBrasil(tmpDate);
data = this.criarDataBrasil(partes.ano, partes.mes, partes.dia, hora, minuto);
} else {
data = tmpDate;
} }
} else {
data = tmpDate;
} }
} else if (unidade.includes('semana')) { } else if (unidade.includes('semana')) {
data.setDate(data.getDate() + (quantidade * 7)); const base = this.extrairPartesBrasil(new Date());
const tmpDate = this.criarDataBrasil(base.ano, base.mes, base.dia, 12, 0);
tmpDate.setDate(tmpDate.getDate() + (quantidade * 7));
if (match[3] && match[4] !== undefined) { if (match[3] && match[4] !== undefined) {
const hora = parseInt(match[3]); const hora = parseInt(match[3]);
const minuto = match[4] ? parseInt(match[4]) : 0; const minuto = match[4] ? parseInt(match[4]) : 0;
if (this.isValidTime(hora, minuto)) { if (this.isValidTime(hora, minuto)) {
data.setHours(hora, minuto, 0, 0); const partes = this.extrairPartesBrasil(tmpDate);
data = this.criarDataBrasil(partes.ano, partes.mes, partes.dia, hora, minuto);
} else {
data = tmpDate;
} }
} else {
data = tmpDate;
} }
} else if (unidade.includes('mes')) { } else if (unidade.includes('mes')) {
data.setMonth(data.getMonth() + quantidade); const base = this.extrairPartesBrasil(new Date());
const tmpDate = this.criarDataBrasil(base.ano, base.mes, base.dia, 12, 0);
tmpDate.setMonth(tmpDate.getMonth() + quantidade);
if (match[3] && match[4] !== undefined) { if (match[3] && match[4] !== undefined) {
const hora = parseInt(match[3]); const hora = parseInt(match[3]);
const minuto = match[4] ? parseInt(match[4]) : 0; const minuto = match[4] ? parseInt(match[4]) : 0;
if (this.isValidTime(hora, minuto)) { if (this.isValidTime(hora, minuto)) {
data.setHours(hora, minuto, 0, 0); const partes = this.extrairPartesBrasil(tmpDate);
data = this.criarDataBrasil(partes.ano, partes.mes, partes.dia, hora, minuto);
} else {
data = tmpDate;
} }
} else {
data = tmpDate;
} }
} else if (unidade.includes('hora')) { } else if (unidade.includes('hora')) {
data.setHours(data.getHours() + quantidade); data.setHours(data.getHours() + quantidade);
@@ -340,6 +368,31 @@ class DateProcessor {
return hora >= 0 && hora <= 23 && minuto >= 0 && minuto <= 59; return hora >= 0 && hora <= 23 && minuto >= 0 && minuto <= 59;
} }
/**
* Criar um Date com hora no fuso America/Araguaina,
* independente do TZ do servidor.
*/
criarDataBrasil(ano, mes, dia, hora, minuto) {
// Constrói uma string ISO no fuso -03:00 para garantir
// que a hora digitada pelo usuário seja tratada como BR.
const pad = (n) => String(n).padStart(2, '0');
const isoStr = `${ano}-${pad(mes + 1)}-${pad(dia)}T${pad(hora)}:${pad(minuto)}:00-03:00`;
return new Date(isoStr);
}
/**
* Extrair {ano, mes(0-11), dia} de um Date no fuso Brasil.
*/
extrairPartesBrasil(data) {
const fmt = new Intl.DateTimeFormat('en-CA', {
timeZone: 'America/Araguaina',
year: 'numeric', month: '2-digit', day: '2-digit'
});
// en-CA produz YYYY-MM-DD
const [ano, mes, dia] = fmt.format(data).split('-').map(Number);
return { ano, mes: mes - 1, dia };
}
/** /**
* Formatar data para string legível * Formatar data para string legível
*/ */
@@ -352,7 +405,7 @@ class DateProcessor {
day: 'numeric', day: 'numeric',
hour: '2-digit', hour: '2-digit',
minute: '2-digit', minute: '2-digit',
timeZone: 'America/Sao_Paulo' timeZone: 'America/Araguaina'
}; };
return data.toLocaleDateString('pt-BR', opcoes); return data.toLocaleDateString('pt-BR', opcoes);
@@ -360,18 +413,20 @@ class DateProcessor {
/** /**
* Formatar data para o formato do sistema (YYYY-MM-DD HH:mm:ss) * Formatar data para o formato do sistema (YYYY-MM-DD HH:mm:ss)
* Retorna sempre no horário de Brasília.
*/ */
formatarParaSistema(data) { formatarParaSistema(data) {
if (!(data instanceof Date)) return null; if (!(data instanceof Date)) return null;
const ano = data.getFullYear(); // Formata no fuso de Brasília para não depender do TZ do servidor
const mes = String(data.getMonth() + 1).padStart(2, '0'); const fmt = new Intl.DateTimeFormat('sv-SE', {
const dia = String(data.getDate()).padStart(2, '0'); timeZone: 'America/Araguaina',
const hora = String(data.getHours()).padStart(2, '0'); year: 'numeric', month: '2-digit', day: '2-digit',
const minuto = String(data.getMinutes()).padStart(2, '0'); hour: '2-digit', minute: '2-digit', second: '2-digit',
const segundo = String(data.getSeconds()).padStart(2, '0'); hour12: false
});
return `${ano}-${mes}-${dia} ${hora}:${minuto}:${segundo}`; // sv-SE produz YYYY-MM-DD HH:mm:ss
return fmt.format(data).replace('T', ' ');
} }
} }