LinuxQuestions.org
Share your knowledge at the LQ Wiki.
Go Back   LinuxQuestions.org > Blogs > marozsas
User Name
Password

Notices

Rate this Entry

tratando_as_excecoes_da_nova_regra_para_o_Horario_de_Verao_Brasileiro

Posted 09-15-2008 at 06:19 AM by marozsas

(This blog entry is in portuguese since it matters only for Brazil)

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:

Code:
#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:
Code:
[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)
Code:
# 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:
Code:
[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 ?
Code:
	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 coçeira 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:

Code:
#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)
Code:
#!/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:
Code:
[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:
Code:
[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.


Posted in Uncategorized
Views 978 Comments 0
« Prev     Main     Next »

  



All times are GMT -5. The time now is 05:46 PM.

Main Menu
Advertisement

My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
identi.ca: @linuxquestions
Facebook: linuxquestions Google+: linuxquestions
Open Source Consulting | Domain Registration