Executando verificação de segurança...
14
kht
9 min de leitura ·

Aritmética de datas e o horário de verão (ou "Um dia pode não ter 24 horas")

Este post é mais um da série sobre datas que estou escrevendo. Os anteriores são:

Hoje vamos ver que um dia nem sempre tem 24 horas. E nem estou falando do fato de que a rotação da Terra está cada vez mais lenta, e sim de algo que pode afetar diretamente os cálculos de data de um sistema, se não tomarmos os devidos cuidados: fusos horários e o famigerado horário de verão.


Neste exato momento, a data e hora atual, em cada lugar do mundo, pode ser diferente. Enquanto em São Paulo são duas da tarde de um domingo, no Japão já são duas da manhã de segunda-feira. Mas dependendo da época do ano, pode ser que duas da tarde em São Paulo seja equivalente a uma (e não duas) da manhã no Japão. Tudo por causa do horário de verão.

Neste texto vamos considerar o Horário de Brasília, mas o problema ocorrerá em qualquer lugar que adote o horário de verão, ou cujo fuso horário tenha sido mudado por qualquer motivo que seja. Então para entender porque somar 1 dia nem sempre é o mesmo que somar 24 horas, primeiro precisamos entender como funciona o horário de verão.

De forma resumida, o horário de verão consiste em atrasar ou adiantar o relógio uma determinada quantidade de tempo (na maioria dos lugares, essa quantidade é uma hora), em determinada data e horário. No caso do Horário de Brasília, por exemplo, em 14 de outubro de 2017, à meia-noite, os relógios foram adiantados em uma hora. Isso quer dizer que na prática, o relógio pulou de 23:59:59.999 do dia 14 direto para 01:00 do dia 15. Todos os minutos entre 00:00 e 00:59 não existiram no dia 14, para este fuso horário. Ou seja, o dia 15 começou na verdade às 01:00, e portanto neste fuso horário, este dia teve 23 horas.

Isso é chamado de DST Gap: "DST" é a sigla para Daylight Saving Time (o nome em inglês para Horário de Verão, que muitos também chamam de "Summer Time") e gap significa "vão", ou seja, há um "vazio" ali porque uma hora foi "pulada".

Isso é melhor ilustrado pela figura abaixo:

DST Gap

Repare que a "mágica de pular uma hora" é possível porque o que acontece na verdade é uma mudança de offset (a diferença com relação a UTC). Antes do DST Gap, o offset é -03:00 (3 horas antes de UTC), e quando há a virada de 23:59 para 00:00, o offset muda para -02:00 (duas horas antes de UTC). É essa mudança de offset que torna possível adiantar o relógio do ponto de vista local, sem quebrar a continuidade dos instantes referentes a antes e depois da mudança. Repare na figura acima que os instantes em UTC permanecem sendo contínuos.

Já quando o horário de verão termina, acontece algo tão ou mais estranho. Ainda usando como exemplo o Horário de Brasília: quando acaba o horário de verão, o relógio é atrasado em uma hora. Em 2017, por exemplo, o horário de verão acabou no dia 19 de fevereiro: à meia-noite, os relógios foram atrasados em uma hora e voltaram para 23:00 do dia 18.

Ou seja, os relógios pularam de 23:59:59.999 do dia 18 para 23:00 do dia 18. Isso quer dizer que todos os minutos entre 23:00 e 23:59 ocorreram duas vezes: uma horário de verão e outra no horário "normal". Ou seja, neste fuso horário, o dia 18 durou 25 horas.

Isso é chamado de DST Overlap: já vimos acima que "DST" é a sigla em inglês para o horário de verão, e overlap é "sobreposição", para indicar que um mesmo intervalo de tempo ocorreu duas vezes.

Para entender melhor, vejamos a figura abaixo:

DST Overlap

O que acontece é que o offset é restaurado para o valor "normal": deixa de ser -02:00 e volta a ser -03:00. Mas mais uma vez, repare que os instantes em UTC são contínuos, ou seja, apenas o horário local foi alterado graças à mudança de offset. Mas a continuidade da linha do tempo permaneceu intacta.


E como isso afeta a aritmética de datas?

Depende muito do que vc precisa fazer, e de como cada linguagem trata cada caso. Até porque não há uma norma oficial sobre como a aritmética de datas deve funcionar (como há na matemática, por exemplo), o que temos são decisões que cada linguagem ou biblioteca faz ao tratar diversos casos diferentes.

Mas de forma geral, quando somamos um dia a uma data, o esperado (o "senso comum", entre muitas aspas) é que o resultado seja o mesmo horário do dia seguinte. E na grande maioria dos casos, isso será exatamente o mesmo que somar 24 horas. Mas graças ao horário de verão, isso nem sempre é verdade.

Vamos fazer um teste com JavaScript, usando uma data qualquer. Lembrando que os testes abaixo foram feitos em uma máquina cujo fuso horário configurado é o Horário de Brasília (se configurar em fusos diferentes, os resultados não serão os mesmos).

Enfim, para uma mesma data, somo um dia, e depois somo ao timestamp a quantidade de milissegundos equivalente a 24 horas:

var millisPerHour = 3600 * 1000;

var data = new Date(2017, 4, 1, 10, 0);
var antes = data.getTime();
console.log(' antes:', data.toString());
// somando 1 dia
data.setDate(data.getDate() + 1);
var depois = data.getTime();
console.log('depois:', data.toString());
console.log('diferença:', (depois - antes) / millisPerHour);

data = new Date(2017, 4, 1, 10, 0);
antes = data.getTime();
console.log(' antes:', data.toString());
// somando 24 horas
data.setTime(data.getTime() + (24 * millisPerHour));
depois = data.getTime();
console.log('depois:', data.toString());
console.log('diferença:', (depois - antes) / millisPerHour);

A data base é 1 de maio de 2017 (sim, o JavaScript tem esse problema irritante dos meses começarem em zero, por isso maio é 4 no código acima), às 10:00.

Primeiro eu somo 1 dia - como o JavaScript não tem (ainda) um jeito nativo de somar, esta é a forma de fazer. Depois somo a quantidade de milissegundos equivalente a 24 horas. Em ambos eu também pego a diferença entre antes e depois, e o resultado é o esperado, o mesmo horário do dia seguinte, com 24 de horas de diferença:

 antes: Mon May 01 2017 10:00:00 GMT-0300 (Brasilia Standard Time)
depois: Tue May 02 2017 10:00:00 GMT-0300 (Brasilia Standard Time)
diferença: 24
 antes: Mon May 01 2017 10:00:00 GMT-0300 (Brasilia Standard Time)
depois: Tue May 02 2017 10:00:00 GMT-0300 (Brasilia Standard Time)
diferença: 24

Agora o que acontece se mudarmos a data inicial para um dia antes do DST Gap?

var millisPerHour = 3600 * 1000;

var data = new Date(2017, 9, 14, 10, 0);
var antes = data.getTime();
console.log(' antes:', data.toString());
data.setDate(data.getDate() + 1);
var depois = data.getTime();
console.log('depois:', data.toString());
console.log('diferença:', (depois - antes) / millisPerHour);

data = new Date(2017, 9, 14, 10, 0);
antes = data.getTime();
console.log(' antes:', data.toString());
data.setTime(data.getTime() + (24 * millisPerHour));
depois = data.getTime();
console.log('depois:', data.toString());
console.log('diferença:', (depois - antes) / millisPerHour);

Agora a saída é:

 antes: Sat Oct 14 2017 10:00:00 GMT-0300 (Brasilia Standard Time)
depois: Sun Oct 15 2017 10:00:00 GMT-0200 (Brasilia Summer Time)
diferença: 23
 antes: Sat Oct 14 2017 10:00:00 GMT-0300 (Brasilia Standard Time)
depois: Sun Oct 15 2017 11:00:00 GMT-0200 (Brasilia Summer Time)
diferença: 24

No primeiro caso ele manteve o mesmo horário do dia seguinte. Mas por causa da mudança de offset, a diferença entre as datas é de 23 horas. No segundo caso, a diferença é de 24 horas, mas o horário não é o mesmo (repare também como mudou de "Standard Time" para "Summer Time", e o offset mudou de -0300 para -0200).

Para entender melhor: imagine que no dia 14 às 10:00 eu inicio a contagem em um cronômetro. Às 23:59 o cronômetro estará marcando 13 horas e 59 minutos. À meia-noite ocorre o DST Gap, então o horário local passa a ser 1 da manhã. Mas o cronômetro não faz esse salto (ele não vai contar uma hora a mais), então às 01:00 do dia 15 ele estará marcando que se passaram 14 horas. Isso quer dizer às 10:00 do dia 15, o cronômetro indicará que se passaram 23 horas. Somente às 11:00 é que terão se passado 24 horas.

É um corner case que só vai acontecer duas vezes por ano, e se o horário de verão voltar? Sim, mas é algo que pode afetar seu sistema. Por exemplo, se vc tiver que medir quanto tempo determinada operação demorou, e levar em conta apenas a data e hora e desconsiderar o fuso (e consequentemente o offset), obterá resultados errados. Se tiver que lidar com datas antigas, tem que considerar se na época o horário de verão estava ou não em vigor, etc.

E caso tenha que somar datas por qualquer motivo ("O prazo para X é de N dias a partir da data D"), pode ser que faça diferença saber se "somar 1 dia" quer dizer "considerar o mesmo horário do dia seguinte", ou "24 horas do cronômetro" (ou ainda, se o sistema vai desconsiderar o horário, situação na qual nada disso se aplica).


Agora usando o mesmo exemplo para quando ocorre o DST Overlap:

var millisPerHour = 3600 * 1000;

var data = new Date(2017, 1, 18, 10, 0);
var antes = data.getTime();
console.log(' antes:', data.toString());
data.setDate(data.getDate() + 1);
var depois = data.getTime();
console.log('depois:', data.toString());
console.log('diferença:', (depois - antes) / millisPerHour);

data = new Date(2017, 1, 18, 10, 0);
antes = data.getTime();
console.log(' antes:', data.toString());
data.setTime(data.getTime() + (24 * millisPerHour));
depois = data.getTime();
console.log('depois:', data.toString());
console.log('diferença:', (depois - antes) / millisPerHour);

Saída:

 antes: Sat Feb 18 2017 10:00:00 GMT-0200 (Brasilia Summer Time)
depois: Sun Feb 19 2017 10:00:00 GMT-0300 (Brasilia Standard Time)
diferença: 25
 antes: Sat Feb 18 2017 10:00:00 GMT-0200 (Brasilia Summer Time)
depois: Sun Feb 19 2017 09:00:00 GMT-0300 (Brasilia Standard Time)
diferença: 24

Novamente, o primeiro caso manteve o mesmo horário, mas por causa da mudança de offset (de -0200 para -0300, ou seja, do horário de verão para o "horário normal"), a diferença é de 25 horas. Já somando 24 horas, o resultado foi um horário diferente.

Aqui vale a mesma explicação: imagine que no dia 18 às 10h eu inicio o cronômetro. Às 23:59 ele estará marcando 13 horas e 59 minutos. À meia-noite, graças ao DST Overlap, o relógio volta para 23:00 - isso quer dizer que todos os minutos entre 23:00 e 23:59 serão contados duas vezes pelo cronômetro: uma no horário de verão, e outra no horário "normal". Por isso que às 10:00 do dia 19, o cronômetro estará marcando que se passaram 25 horas.


Vale lembrar que gaps e overlaps nem sempre são de uma hora (há regiões da Austrália que durante o horário de verão adiantam o relógio em meia hora), e nem sempre é por causa do horário de verão (em 2018 a Coreia do Norte adiantou seu fuso em meia-hora para alinhá-lo com o horário da Coreia do Sul).

Lembre-se que quem define as regras de qualquer fuso horário (se vai ou não ter horário de verão, quando começa e termina, qual offset será usado, etc) é o governo local de cada região, muitas vezes sem justificativa técnica (é comum dizer que "o povo terá mais horas de sol" e coisas do tipo), então mesmo se hoje não tiver horário de verão na sua região, nada garante que amanhã não haverá. Ao somar datas ou calcular a diferença entre elas, esses fatores sempre devem ser levados em consideração, pois pode dar diferença dependendo da forma como você calcula.


Este texto é baseado neste post do meu blog. Lá os exemplos são em Java, usando o java.time (nova API de datas do Java 8), mas a ideia principal é a mesma.

As imagens do DST Gap e DST Overlap foram retiradas deste livro, com a devida permissão do autor - que sou eu mesmo :-)

Carregando publicação patrocinada...
5
3

Vou dar minha leiga opinião sobre o assunto.

Sempre acho interessante pontuar que data e hora são duas unidades distintas e com propósitos diferentes, mas que uma é ligada à outra. O conceito de "momento" é muito menosprezado por muitos programadores, deixando que o usuário advinhe a data a partir do que acontece no sistema.

Algo bacana que vejo alguns sistemas fazerem é armazenar a data e hora sempre relativa ao do servidor, e ao exibir no cliente é transformado na localidade do mesmo. É interessante ter uma API ou biblioteca dedicada à isso, pois é impossível advinhar quando um computador tá no horário de verão ou não somente com códigos.

Recentemente, o governo brasileiro aboliu o horário de verão. Não sei como vai ser esse ano. Não tem como prever, vai de uma circustância que está além de cálculos, física e etc.

Gostei do seu ponto que "não existe uma aritmética universal de datas", porque realmente não existe. Não existe uma unidade primitiva para tempo. Existe o conceito numérico para segundos, milisegundos, minutos, horas, etc, e você pode somar eles, mas o resultado sempre será uma representação de duranção ou ponto no tempo.

Dependendo da finalidade, você ajusta a precisão, descendo do milissegundo, microsegundo, nanosegundo, picosegundo, etc... vai da finalidade.

1

É interessante ter uma API ou biblioteca dedicada à isso, pois é impossível advinhar quando um computador tá no horário de verão ou não somente com códigos.

Eu diria para sempre usar uma API de datas, quando disponível. Claro que muitas linguagens não ajudam por não terem uma API decente, mas ainda sim é melhor do que tentar fazer tudo manualmente. Existem muitos corner cases e "pegadinhas" difíceis de tratar (muitas são até difíceis de sequer imaginar que podem acontecer).

No caso dos fusos horários e do histórico de alterações do horário de verão, é um trabalho hercúleo e infrutífero (e muito propenso a erros) tentar rastrear tudo manualmente. Hoje a maioria dos sistemas usa os dados do IANA Time Zone Database, e bem, veja na lista de discussão deles como é um trabalho insano manter todas essas informações em sincronia com as constantes alterações feitas no mundo todo.

Datas é um caso em que vale muito a pena ter uma API dedicada. Tem detalhes demais pra tratar, e mesmo as melhores bibliotecas não conseguem cobrir todos os aspectos.

2

Que conteúdo bacana! bem completo. Nunca considerei isso nos meus trabalhos.

Trabalho com análise de dados e eventualmente com séries temporais, isso é algo que pode afetar meu trabalho em algum momento inoportuno. Obrigado!

2

Artigo em boa hora, estava esta semana estudando sobre o java.time, isto sobre fuso-horários acabou me tomando boa parcela do meu tempo, mas acho que consegui compreender as nuances (não completamente, óbvio) da abordagem da linguagem Java para as datas, segue as minhas anotações e contribuições para complementar às suas de Javascript:

  • TemporalAcessor: Uma interface implementada por todas abaixo referente a Date-Time, permitindo tratar dados de forma genérica sem precisar converter numa ou outra específica para atender a algum parâmetro, etc.
  • Temporal: Implementa TemporalAcessor e fornece os métodos básicos implado por todas abaixo referente a Date-Time
  • LocalDate: Apenas as datas, dispensando as horas;
  • LocalTime: Apenas o horário, dispensando as datas;
  • LocalDateTime: Utiliza ambos, tanto a hora quanto a data. Mas não tem fuso-horário;
  • OffsetDateTime: Utiliza ambos com fuso-horário, mas sem sensibilidade ou mudanças a longo prazo (horário de verão);
  • ZonedDateTime: Utiliza ambos com fuso-horário sensível à mudanças como horário de verão, mas maior complexidade;
  • ZoneOffset: Apenas o fuso-horário sem nome ou sensibilidade a mudanças sazonais (usado por OffsetDateTime);
  • ZoneId: Um identificador de fuso-horário completo e todas as regras aplicadas a estes fusos por cada região;
  • Instant: Um momento específico da linha do tempo em milisegundos (UTC+0), mas sem considerar datas;
  • Clock: Similar ao System.currentTimeMillis(), mas permite importar relógios externos como API;
  • ChronoField: Enumeração que implementa TemporalField;
  • ChronoUnit: Enumeração que implementa TemporalUnit;
  • Chronology: Implementa diferentes tipos de calendários junto do ChronoLocalDate (menos utilizado).

Observação: Enumeração, para quem não manja de Java é uma lista truncada de opções que DEVEM ser selecionadas.


O ideal para contornar a situação é o ZonedDateTime, pois nele você consegue passar não só o idioma, mas o local que a data será baseada, sensíveis à estas alterações. Entretanto, num sistema internacional o Instant é melhor, pois sempre estará junto ao UTC e consegue converter ZonedDateTime para o Instant. Entretanto, ele não é uma data (pode ser convertido para uma, no entanto), é um momento no tempo contado a partir do epoch (1 de janeiro de 1970, às 00:00), podendo ser tanto negativo quanto positivo.

Ou seja, na minha cabeça o ideal seria armazenar ZonedDateTime -> Converta para Instant -> Faz as operações -> Retorna a resposta convertendo de volta para ZonedDateTime (se algum veterano tiver experiência na utilização, feedback seria bom)


Por último, acho que discordo do seu ponto do "não há uma medida universal de datas", pois o UTC veio justamente para fazer isso, é como se fosse um contrato inerente e consensual da humanidade entre si para: "independente da sua opinião ou localização que você utiliza na sua casa, aqui usamos X", é como se fosse... "independente do seu comportamento na sua casa, o ideal é X!" e isso é o que define algo universal, de comum acordo entre as partes.

A dificuldade técnica apontada seria em nos adaptarmos à "casa" de todos, mas é muito mais fácil fazer a "casa" de todos se adaptarem a nós, na medida universal da coisa.

3

Sobre ZoneOffset e ZoneId: o primeiro representa um offset, e o segundo, um timezone. Estes conceitos estão relacionados, mas muitas vezes são confundidos e tratados como se fossem a mesma coisa.

Um offset é a diferença em relação a UTC. É um valor fixo, por exemplo, -03:00 é 3 horas antes do UTC. Um timezone é um identificador que representa uma região geográfica, e possui o histórico de todos os offsets que esta região teve ao longo da história. Por exemplo, America/Sao_Paulo possui a lista de offsets que uma região específica do Brasil teve ao longo do tempo (isso inclui as mudanças de horário de verão).

Vale notar que muitos timezones podem ter o mesmo offset em determinadas épocas. Por exemplo, America/Sao_Paulo e America/Recife estão com o offset -03:00, mas São Paulo teve horário de verão em épocas nas quais Recife não teve (um estava em -02:00 enquanto o outro permaneceu em -03:00). Como o histórico é diferente, eles não são o mesmo timezone (mesmo que eles coincidam na maior parte do tempo).

Enfim, ZoneOffset representa apenas um offset específico, e ZoneId representa um timezone, com todo o histórico de offsets.

O mesmo vale para OffsetDateTime (data e hora com um offset fixo) e ZonedDateTime (data e hora em um timezone, com o offset podendo variar de acordo com o histórico do timezone). No meu blog tem exemplos em Java que explicam melhor.

Quanto a "sempre usar UTC" (ou seja, sempre usar Instant), varia muito, nem sempre será a melhor solução. Veja aqui, principalmente a seção "UTC e eventos futuros" e também aqui para ver um caso em que somente converter para UTC não é suficiente.


não há uma medida universal de datas

Em nenhum ponto do texto eu afirmo isso. Eu só disse que não há uma norma oficial sobre como a aritmética de datas deve funcionar, e que em outro comentário foi citado como "aritmética universal".

2

Em nenhum ponto do texto eu afirmo isso. Eu só disse que não há uma norma oficial sobre como a aritmética de datas deve funcionar, e que em outro comentário foi citado como "aritmética universal".

Ah, sim. É que eu tinha visto noutro comentário, mas peço perdão pelo equívoco. Neste ponto não há realmente consenso pelo que estudei então há convergência nas informações. (Não quero me sobressair, apenas em caso de divergência é uma oportunidade para eu aprender.)

Enfim, ZoneOffset representa apenas um offset específico, e ZoneId representa um timezone, com todo o histórico de offsets.

Realmente tinha sido este o entendimento que eu tive, mas acho que na minha própria anotação não deixei tão claro, isso iria me atrapalhar em possiveis revisões (eu entendo as anotações porque ainda está fresco na mente). E sobre o ZoneId guardar o histórico de Offset das regiões eu não fazia ideia que as "informações completas" incluiam isso.

Por último, obrigado pelos artigos, tô com um acumulado pra ler, mas logo mais darei uma atenção a eles.

3

Complementando mais um pouco...

A ideia do TemporalAccessor é ser uma interface bem básica que define uma maneira genérica de obter os valores numéricos correspondentes aos campos de data. Tanto que os únicos métodos que vc precisa implementar recebem um TemporalField, e um indica se o campo é suportado, enquanto outro retorna seu valor.

É a funcionalidade mais básica, que permite que se obtenha qualquer campo de uma classe que represente datas/horas, quando suportado.

Já a interface Temporal adiciona métodos para modificar campos (na verdade, para retornar outra instância modificada, já que as classes do java.time são imutáveis) e somar/subtrair uma unidade de tempo.

Mas na documentação de ambas diz para evitar usá-las diretamente - salvo raras exceções, e de fato na prática eu acabo usando tipos específicos para cada caso (LocalDate, ZonedDateTime, Instant, etc). Inclusive, os getters de cada classe são mais fáceis de usar do que o get genérico de TemporalAccessor (que também quase não uso na prática).


Sobre o histórico de offsets, podemos fazer um pequeno código para ver a diferença. Primeiro com um timezone:

// obtém a lista de mudanças de offset
List<ZoneOffsetTransition> mudancas = ZoneId.of("America/Sao_Paulo").getRules().getTransitions();
System.out.println(mudancas.size()); // 91
for (ZoneOffsetTransition tr : mudancas) {
    System.out.println(tr);
}

A quantidade exata de mudanças vai variar conforme a versão do Java e do TZDB que estiver instalada, já que isso é sempre atualizado. Mas enfim, no timezone America/Sao_Paulo tem uma lista bem grande de alterações.

Agora se usarmos um offset:

List<ZoneOffsetTransition> mudancas = ZoneOffset.ofHours(-3).getRules().getTransitions();
System.out.println(mudancas.size()); // 0
for (ZoneOffsetTransition tr : mudancas) {
    System.out.println(tr);
}

A lista terá zero mudanças, e o for não vai imprimir nada. Isso porque o offset é fixo, nunca muda.


Aliás, essa é uma das pouquíssimas API's que fazem esta distinção. A maioria simplesmente assume que offset é o mesmo que timezone e acabam ficando incompletas e até mesmo incorretas. Não é raro ter apenas uma única classe para ambos (isso quando tem, pois há API's que nem sequer permitem manipular o timezone).

Eu particularmente considero o java.time uma das mais completas e bem pensadas API's de data existentes, por esse e vários outros detalhes.

2

Concordo que seja bem pensada, ainda mais pelo fato dela contemplar tudo o que existe em datas. E por eu ter dedicado fucking 2 ou 3 dias estudando todas as suas variações, estranhei a complicação excessiva nesta coisa de data (menos Chronology, pois acho que será beem raro ao ponto de ser possível de eu nunca precisar utilizar um calendário diferente do ISO). O que me pegou mesmo foi saber a diferença de data e um ponto específico no tempo, mas agora já tenho uma ideia do que é.

Entretanto, ainda assim, quanto mais se estuda sobre esta documentação, pior fica. Parece um conteúdo infinito de camada abaixo de camada kkkkk e isso tem me apavorado e estou quase saindo deste método de estudo de ir na documentação e se aprofundar em seus métodos e voltar a fazer os cursos que só passa uma explicação rasa por cima do que faz e já era. Desta forma me sinto mais aprofundado, mas come muuuito tempo que eu poderia estar estudando coisas e frameworks que o mercado pede para entrar logo na área. O que acha?

2

Como vc já pegou os conceitos básicos (entende que data e ponto no tempo são diferentes, timezone e offset não são a mesma coisa, etc), já está na frente de muita gente :-)

Eu sei como é se perder na documentação, a API é bem grande e completa, e não dá pra pegar todos os conceitos de uma vez. Data/hora é um assunto complicado e muito mais extenso do que as pessoas imaginam.

Minha sugestão é que agora vc pode ir estudando sob demanda: quando surgir a necessidade de fazer algo com datas, pesquise como é nesta API. Os casos de uso mais comuns estão implementados lá, e vários outros não tão comuns assim também.