Assine a Lista Dicas-L
Receba diariamente por email as dicas
de informática publicadas neste site
Para se descadastrar, clique aqui.
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
Referências Adicionais
Referências adicionais sobre os assuntos abordados neste site podem ser encontradas em nossa Bibliografia.
Avalie esta dica
- Currently 3.15/5
- 1
- 2
- 3
- 4
- 5
Avaliação:
3.2 /5
(142 votos)
Opinião dos Leitores
rahxa
20 Set 2008, 23:21
Excelente dica. Parabéns.
Jose Geraldo
20 Set 2008, 23:14
Caro Miguel
Parabens pelo artigo.
Acho que voce poderia incluir mais um parágrafo para ensinar como apontar o Linux para trabalhar com a zona alterada, copiando o arquivo gerado para /etc/localtime ou fazendo um link simbolico para /usr/share/zoneinfo/Brazil/East.
Abraços
JGeraldo
João Marcos
20 Set 2008, 13:08
Eu sempre leio suas dicas, mas esta me surpreendeu, pela flexibilidade do linux e pelo seu trabalho que envolveu pesquisa e em resolver um problema e compartilhar a solução com todos, voce é um cara 10.
Dorian Bolivar
20 Set 2008, 12:21
Excelente artigo! Aproveitando o espaço para comentários, gostaria de opinar sobre esse decreto do governo para o horário de verão.
Sinceramente, se há uma exceção à regra (for carnaval no domingo), não adiantou nada! Continuarão sendo necessárias atualizações ou intervenções manuais para corrigir o horário de verão... Por que não fizeram um negócio decente e colocaram a data de término do horário de verão em um domingo que certamente nunca será carnaval?
Alvaro Figueiredo
20 Set 2008, 11:10
Perdão, vou colar novamente o código Python, por causa da perda de formatação :(
#!/usr/bin/env python
# yearistype
import mx.DateTime
import sys
year = int(sys.argv[1])
yearType = sys.argv[2]
carnivalSunday = mx.DateTime.Feasts.EasterSunday(year) - 49 * mx.DateTime.oneDay
returnCode = 15 <= carnivalSunday.day < 22
if yearType != 'regular': returnCode = not returnCode
returnCode = int(returnCode)
print carnivalSunday.Format('%Y-%m-%d'), returnCode
sys.exit(returnCode)
Alvaro Figueiredo
20 Set 2008, 11:02
Excelente artigo, parabéns e muito obrigado.
Gostaria de aproveitar a oportunidade e deixar uma contribuição para quem aprecia a linguagem de programação Python.
Se instalar o pacote abaixo da sua distribuição Linux (estou usando o Ubuntu 7.10),
python-egenix-mxdatetime
o programa yearistype pode ficar assim:
#!/usr/bin/env python
# yearistype
import mx.DateTime
import sys
year = int(sys.argv[1])
yearType = sys.argv[2]
carnivalSunday = mx.DateTime.Feasts.EasterSunday(year) - 49 * mx.DateTime.oneDay
returnCode = 15 <= carnivalSunday.day < 22
if yearType != 'regular':
returnCode = not returnCode
returnCode = int(returnCode)
print carnivalSunday.Format('%Y-%m-%d'), returnCode
sys.exit(returnCode)
Bruno V Magaldi
20 Set 2008, 09:10
Parabéns! Muito bom. Não faz parte de meu dia a dia profissional tal tipo de tarefa, que creio estar restrita mais aos administradores mas gostei do desenvolvimento do script. Parabéns cara!
felipe
19 Set 2008, 02:46
Bom artigo. Mas passar o dia só pra acertar o horário da máquina é versatilidade?
josé geraldo
18 Set 2008, 19:26
Os quarenta dias da quaresma iniciam-se na Quarta-feira de Cinzas e terminam 1 domingo antes da Páscoa , ou seja o Domingo de Ramos. É a partir daí que se contam reatroativamente os 40 dias, e não a partir da Páscoa. A Quaresma (40 dias) acabam antes da Semana Santa que culmina no Domingo de Páscoa.
thiagomz
18 Set 2008, 18:13
To de boca aberta ! Parabéns ! Vou mostrar para a turma de TI aqui o que é documentação !!!! ;-)