você está aqui: Home  → Arquivo de Mensagens

Tratando as exceções da nova regra para o Horário de Verão

Colaboração: Miguel Angelo Rozsas

Data de Publicação: 20 de September de 2008

A partir de 2008 os dias em que começam e terminam o horário de verão no Brasil serão fixos, válido pelo menos até quando o "Grande Colisor de Hádrons" produza um buraco negro que saia do controle e "engula" o planeta ou talvez até o inicio do mandato do próximo presidente, o que ocorrer primeiro.

Segundo publicado no diário oficial da União, o horário de verão começará sempre no terceiro domingo de Outubro e terminará no terceiro domingo de fevereiro, exceto se tal domingo, for o domingo de carnaval. Nesse caso, termina no quarto domingo.

As datas fixas facilitam um pouco, pois é possivel uma configuração também fixa. Não levando em conta a exceção, o arquivo de timezone ficaria assim:

  #Rule   NAME  FROM  TO    TYPE  IN   ON         AT    SAVE      LETTER/S
  Rule    BR    2008  only   -    Feb  17         0:00  0:00      S
  Rule    BR    2008  MAX    -    Oct  Sun>=15    0:00  1:00      D
  Rule    BR    2009  MAX    -    Feb  Sun>=15    0:00  0:00      S
  
  #Zone   NAME            GMTOFF  RULES/SAVE      FORMAT  [UNTIL]
  Zone    Brazil/East      -3:00   BR             BR%s

e a compilação do arquivo e uma rápida verificação retornaria:

  [root@babylon5 ~]# zic -v /tmp/Brazil_East.zic
  [root@babylon5 ~]# zdump -v -c 2016 Brazil/East
  Brazil/East  Fri Dec 13 20:45:52 1901 UTC = Fri Dec 13 17:45:52 1901 BRS isdst=0 gmtoff=-10800
  Brazil/East  Sat Dec 14 20:45:52 1901 UTC = Sat Dec 14 17:45:52 1901 BRS isdst=0 gmtoff=-10800
  Brazil/East  Sun Oct 19 02:59:59 2008 UTC = Sat Oct 18 23:59:59 2008 BRS isdst=0 gmtoff=-10800
  Brazil/East  Sun Oct 19 03:00:00 2008 UTC = Sun Oct 19 01:00:00 2008 BRD isdst=1 gmtoff=-7200
  Brazil/East  Sun Feb 15 01:59:59 2009 UTC = Sat Feb 14 23:59:59 2009 BRD isdst=1 gmtoff=-7200
  Brazil/East  Sun Feb 15 02:00:00 2009 UTC = Sat Feb 14 23:00:00 2009 BRS isdst=0 gmtoff=-10800
  Brazil/East  Sun Oct 18 02:59:59 2009 UTC = Sat Oct 17 23:59:59 2009 BRS isdst=0 gmtoff=-10800
  Brazil/East  Sun Oct 18 03:00:00 2009 UTC = Sun Oct 18 01:00:00 2009 BRD isdst=1 gmtoff=-7200
  ...

Como podem ver, em 2008, o horário de verão começaria em 19 de Outubro e terminaria em 15 de fevereiro.

Mas a tal exceção me deixou intrigado. A exceção tirou a beleza de uma configuração fixa, o que me fez coçar os neurônios.

Já conhecia a man page do zic(1) há tempos. Lá fala de uma função yearistype que é chamada para cada ano, e se retornar verdadeiro (true) a respectiva linha no time zone é aplicada.

Humm...então essa função poderia então ser usada para tratar a exceção da regra do "Horário de Verão Brasileiro a partir de 2008". Para tanto, "basta" saber se o domingo que antecede o carnaval cai no terceiro domingo do mês. Se cair, aplica-se a regra do quarto domingo do mês. Simples não ?

Humm...o problema é que o carnaval, uma festa pagã, é data móvel, determinada pela data em que cai outro feriado, a páscoa, um feriado sagrado da igreja Católica Romana, outra data móvel, que é determinada, segundo regras estabelecidas pelo concilio de Nicea no longiguo ano de 325, como sendo o primeiro domingo depois da primeira lua cheia depois do equinócio vernal (equinócio da primavera no hemisfério norte).

Humm...complicado heim ?

Bem, como Google é pai e wikipedia é mãe, acabei encontrando o algoritmo elaborado por "Meeus/Jones/Butcher" válido para quem usa o calendário gregoriano (somos nós ! - Os católicos ortodoxos orientais usam o calendário Juliano) (http://en.wikipedia.org/wiki/Computus#Algorithms)

Humm...então basta eu implementar esse algoritmo para descobrir a data da páscoa de um determinado ano, obter dai a data do carnaval e testar se domingo que o antecede é o terceiro domingo e se for, aplica-se a exceção! Está ficando simples....

Implementei o algoritmo de "Meeus/Jones/Butcher" em dc(1). (Salve os comandos dc abaixo em /usr/local/lib/easter.dc)

  # y=year from cmd line
  sy
  0 k
  # a=y mod 19
  ly 19 % sa
  # b=y/100
  ly 100 / sb
  # c=y mod 100
  ly 100 % sc
  # d=b/4
  lb 4 / sd
  # e=b mod 4
  lb 4 % se
  # f=(b+8)/25
  lb 8 + 25 / sf
  # g = (b - f + 1) / 3
  lb lf - 1 + 3 / sg
  # h = (19 × a + b - d - g + 15) mod 30
  19 la * lb + ld - lg - 15 + 30 % sh
  # i = c / 4
  lc 4 / si
  # k = c mod 4
  lc 4 % sk
  # L = (32 + 2 × e + 2 × i - h - k) mod 7
  32 le 2 * + li 2 * + lh - lk - 7 % sl
  # m = (a + 11 × h + 22 × L) / 451
  la 11 lh * + 22 ll * + 451 / sm
  # n=(h + L - 7 × m + 114)
  lh ll + 7 lm * - 114 + sn
  # month= n/31
  ln 31 /
  4 k
  100 /
  # day=(n mod 31)+1
  0 k
  ln 31 % 1 +
  4 k
  10000 / + ly +
  p

...e use-o como:

  [miguel@babylon5 ~]$ dc -e 2008 -f /usr/local/lib/easter.dc
  2008.0323
  [miguel@babylon5 ~]$ dc -e 2009 -f /usr/local/lib/easter.dc
  2009.0412
  [miguel@babylon5 ~]$ dc -e 2010 -f /usr/local/lib/easter.dc
  2010.0404
  [miguel@babylon5 ~]$

(as datas da páscoa dos anos passados na CLI são retornadas no formato yyyy.mmdd)

humm...agora é necessário descobrir a data do carnaval. O tal conselho de Nicea decidiu que deve se haver pelo menos 40 dias entre o final do carnaval e a sexta-feira santa que antecede a páscoa, a fim de que os fieis se preparem e jejuem adequadamente depois daquela esbórnia da festa pagã em homenagem ao deus "Sol" Trocando em miúdos, entre a terça-feira do carnaval e o domingo de páscoa devem haver exatos 47 dias. Mas eu quero saber não quando é a terça feira de carnaval, mas sim, quando é o domingo que antecede tal terça-feira - ora, trivial, são 49 dias.

humm...como subtrair de uma data (a data da páscoa), um determinado número de dias (47) ? :scratch:

Os leitores talvez possam sugerir outras alternativas. Eu usei os comandos jday e j2d do pacote jday do fedora 9 (jday-2.4-3.fc9.i386) (http://sourceforge.net/projects/jday/)

"basta" então converter uma data (a data da páscoa) para um número Juliano (usando o comando jday), subtrair 49 (usando expr) e converter esse outro número juliano para uma data do calendário gregoriano (usando j2d) !

Usando o código abaixo vcs podem verificar que o domingo que antecede o carnaval no ano de 2009 sera o dia 22 de fevereiro, "simples" não ?

  year="2009"
  easter=$(dc -e $year -f /usr/local/lib/easter.dc | sed -e 's/\./-/; s/-\([0-9]\{2\}\)/-\1-/')
  jday=$(jday -d $easter 0:0:0 | cut -d. -f 1)
  
  # The sunday before carnival is 49 dias before easter (48, in jday account)
  jcar=$(expr $jday - 48)
  
  carnival=$(j2d $jcar | cut -d' ' -f 1)
  echo "Sunday of carnival is on $carnival"

Voilá...então se o tal dia for maior ou igual a 15 e menor que 22, então é o terceiro domingo ! Fim da coceira nos neuronios.

Então já temos tudo para o compilador de zonas criar um arquivo binário com todas a regra do "Horario Brasileiro de Verão", incluindo as exceções.

O arquivo de zonas fica assim:

  #Rule   NAME  FROM  TO    TYPE          IN   ON         AT    SAVE      LETTER/S
  Rule    BR    2008  MAX    -            Oct  Sun>=15    0:00  1:00      D
  Rule    BR    2008  MAX    regular      Feb  Sun>=15    0:00  0:00      S
  Rule    BR    2008  MAX    carnOn3rdSun Feb  Sun>=22    0:00  0:00      S
  
  
  #Zone   NAME            GMTOFF  RULES/SAVE      FORMAT  [UNTIL]
  Zone    Brazil/East      -3:00   BR             BR%s

O arquivo acima faz menção a dois tipos de anos: "regular" é o ano onde o horário de verão começa no terceiro domingo. "carnOn3rdSun" é o ano onde o horário de verão começa no quarto domingo porque o domingo que antecede o carnaval cai no terceiro domingo.

O programa zic(1) irá chamar o programa yearistype passando como primeiro argumento um ano, e como segundo argumento o conteúdo do campo TYPE ("regular" ou carnOn3rdSun)

humm...(é o último, eu prometo), mas cadê o tal programa yearistype? Basicamente é o código imediatamente acima, em "bash", com o valor de retorno correto. Não fiz a critica sobre os argumentos de entrada, uma vez que esse código é para ser chamado pelo zic, sempre da mesma maneira. Mas quem quiser, fique a vontade para fazer o tratamento dos argumentos de entrada.

Salve o código abaixo em /usr/local/bin ou outro lugar que o zic (como root) possa chamar o programa. Verifique se o seu programa zic aceita a especificação do path do comando yearistype. No Fedora 9 é possivel usar a chave -y para indicar o path do comando yearistype (zic -y ~miguel/bin/yearistype zone_file.zic)

  #!/bin/bash
  
  #
  # Usage: yearistype year type
  #
  
  year=$1
  type=$2
  
  function check3rdSun() {
         easter=$(dc -e $year -f /usr/local/lib/easter.dc | sed -e 's/\./-/; s/-\([0-9]\{2\}\)/-\1-/')
         jday=$(jday -d $easter 0:0:0 | cut -d. -f 1)
  
         # The sunday before carnival is 48 dias before easter
         jcar=$(expr $jday - 48)
  
         carnival=$(j2d $jcar | cut -d' ' -f 1)
         #echo "Sunday of carnival is on $carnival"
  
         # I want only the day of month
         carday=$(echo $carnival | cut -d- -f3)
  
         # return 0 if it is the 3rd Sunday, 1 otherwise
         [ "$carday" -ge "15" ] && [ "$carday" -lt "22" ] && return 0 || return 1
  }
  
  check3rdSun $year
  rc=$?
  
  case $type in
         carnOn3rdSun)
                 #echo "carnOn3rdSun: $carnival $rc"
                 exit $rc
                 ;;
         *)
                 #echo "regular: $carnival $rc"
                 [ "$rc" = "0" ] && exit 1 || exit 0
  esac

O próximo passo é a compilação do arquivo de zonas e teste:

  [root@babylon5 ~]# zic -v -y ~miguel/bin/yearistype ~miguel/src/Brazil_East.zic
  [root@babylon5 ~]# zdump -v -c 2016 Brazil/East
  ...
  Brazil/East  Sun Feb 17 01:59:59 2008 UTC = Sat Feb 16 23:59:59 2008 BRD isdst=1 gmtoff=-7200
  Brazil/East  Sun Feb 17 02:00:00 2008 UTC = Sat Feb 16 23:00:00 2008 BRS isdst=0 gmtoff=-10800
  Brazil/East  Sun Oct 19 02:59:59 2008 UTC = Sat Oct 18 23:59:59 2008 BRS isdst=0 gmtoff=-10800
  Brazil/East  Sun Oct 19 03:00:00 2008 UTC = Sun Oct 19 01:00:00 2008 BRD isdst=1 gmtoff=-7200
  Brazil/East  Sun Feb 15 01:59:59 2009 UTC = Sat Feb 14 23:59:59 2009 BRD isdst=1 gmtoff=-7200
  Brazil/East  Sun Feb 15 02:00:00 2009 UTC = Sat Feb 14 23:00:00 2009 BRS isdst=0 gmtoff=-10800
  Brazil/East  Sun Oct 18 02:59:59 2009 UTC = Sat Oct 17 23:59:59 2009 BRS isdst=0 gmtoff=-10800
  Brazil/East  Sun Oct 18 03:00:00 2009 UTC = Sun Oct 18 01:00:00 2009 BRD isdst=1 gmtoff=-7200
  ...
  Brazil/East  Sun Feb 26 01:59:59 2012 UTC = Sat Feb 25 23:59:59 2012 BRD isdst=1 gmtoff=-7200
  Brazil/East  Sun Feb 26 02:00:00 2012 UTC = Sat Feb 25 23:00:00 2012 BRS isdst=0 gmtoff=-10800
  ...
  Brazil/East  Sun Feb 22 01:59:59 2015 UTC = Sat Feb 21 23:59:59 2015 BRD isdst=1 gmtoff=-7200
  Brazil/East  Sun Feb 22 02:00:00 2015 UTC = Sat Feb 21 23:00:00 2015 BRS isdst=0 gmtoff=-10800
  ...

Em 2009 o horário de verão termina às 0:00hs do dia 15 (voltando a ser 23:00hs do dia 14), seguindo a regra "regular". Já em 2012 ocorre uma das exceções: O horário de verão termina em 26 de fevereiro, que é o quarto domingo, porque o carnaval em 2012 ocorre em 21, sendo o domingo que o antecede, o terceiro domingo do mês. Outra excessão ocorre em 2015, com o horário de verão terminando no quarto domingo, dia 22.

Para ver todas as excessões, edite o arquivo yearistype des-comentando os dois comandos echo que existem dentro do case (echo ``carnOn3rdSun: $carnival $rc) e (echo ``regular: $carnival $rc).

Depois disso, execute o comando dentro de um laço for do bash:

  [miguel@babylon5 ~]$ for y in $(seq 2008 2100 ) ; do ~/bin/yearistype $y carnOn3rdSun; done
  carnOn3rdSun: 2008-02-03 1
  carnOn3rdSun: 2009-02-22 1
  carnOn3rdSun: 2010-02-14 1
  carnOn3rdSun: 2011-03-06 1
  carnOn3rdSun: 2012-02-19 0
  carnOn3rdSun: 2013-02-10 1
  carnOn3rdSun: 2014-03-02 1
  carnOn3rdSun: 2015-02-15 0
  ...

Todas as linhas que terminam com 0 indicam os anos cujo domingo de carnaval cai no terceiro domingo do mes. São as excessões da regra geral.

Vocè pode até compilar o arquivo de zonas com o zic deixando o arquivo yearistype com os echos des-comentados. Você irá ver a invocação, pelo zic, do comando yearistype e a respectiva saida. Experimente !

Há espaço para otimizações.

Eu suspeito que o uso do comando jday/j2c pode ser eliminado, calculando-se diretamente a data do carnaval, não a data da pascóa.

Para isso, é necessário adaptar o algoritmo de "Meeus/Jones/Butcher" para obter uma data (47+2) dias antes, mas é preciso investigar o algoritmo para verificar se isso é realmente possivel e mais fácil do que usar j2c/jday. Fica a sugestão para os mais aventurosos.

Foi um dia produtivo. Me diverti resolvendo o problema e depois mais ainda em escrever esse blog que espero ser útil para demonstrar a flexibilidade do unix e o poder dos scripts e utilitários.

  # y=year from cmd line
  sy
  0 k
  # a=y mod 19
  ly 19 % sa
  # b=y/100
  ly 100 / sb
  # c=y mod 100
  ly 100 % sc
  # d=b/4
  lb 4 / sd
  # e=b mod 4
  lb 4 % se
  # f=(b+8)/25
  lb 8 + 25 / sf
  # g = (b - f + 1) / 3
  lb lf - 1 + 3 / sg
  # h = (19 × a + b - d - g + 15) mod 30
  19 la * lb + ld - lg - 15 + 30 % sh
  # i = c / 4
  lc 4 / si
  # k = c mod 4
  lc 4 % sk
  # L = (32 + 2 × e + 2 × i - h - k) mod 7
  32 le 2 * + li 2 * + lh - lk - 7 % sl
  # m = (a + 11 × h + 22 × L) / 451
  la 11 lh * + 22 ll * + 451 / sm
  # n=(h + L - 7 × m + 114)
  lh ll + 7 lm * - 114 + sn
  # month= n/31
  ln 31 /
  4 k
  100 /
  # day=(n mod 31)+1
  0 k
  ln 31 % 1 +
  4 k
  10000 / + ly +
  p
  
  #!/bin/bash
  
  #
  # Usage: yearistype year type
  #
  
  year=$1
  type=$2
  
  function check3rdSun() {
         easter=$(dc -e $year -f /usr/local/lib/easter.dc | sed -e 's/\./-/; s/-\([0-9]\{2\}\)/-\1-/')
         jday=$(jday -d $easter 0:0:0 | cut -d. -f 1)
  
         # The sunday before carnival is 48 dias before easter
         jcar=$(expr $jday - 48)
  
         carnival=$(j2d $jcar | cut -d' ' -f 1)
         #echo "Sunday of carnival is on $carnival"
  
         # I want only the day of month
         carday=$(echo $carnival | cut -d- -f3)
  
         # return 0 if it is the 3rd Sunday, 1 otherwise
         [ "$carday" -ge "15" ] && [ "$carday" -lt "22" ] && return 0 || return 1
  }
  
  check3rdSun $year
  rc=$?
  
  case $type in
         carnOn3rdSun)
                 #echo "carnOn3rdSun: $carnival $rc"
                 exit $rc
                 ;;
         *)
                 #echo "regular: $carnival $rc"
                 [ "$rc" = "0" ] && exit 1 || exit 0
  esac


 

 

Veja a relação completa dos artigos de Miguel Angelo Rozsas

Opinião dos Leitores

Seja o primeiro a comentar este artigo
*Nome:
Email:
Me notifique sobre novos comentários nessa página
Oculte meu email
*Texto:
 
  Para publicar seu comentário, digite o código contido na imagem acima
 


Powered by Scriptsmill Comments Script