require
5.010000;
our
$VERSION
;
$VERSION
=
'6.42'
;
END {
undef
$VERSION
; }
our
(
$Verbose
,
@StdFiles
,
$dmb
);
END {
undef
$Verbose
;
undef
@StdFiles
;
undef
$dmb
;
}
$dmb
= new Date::Manip::Base;
$Verbose
= 0;
@StdFiles
=
qw(africa
antarctica
asia
australasia
europe
northamerica
pacificnew
southamerica
etcetera
backward
)
;
our
(
$TZ_DOM
,
$TZ_LAST
,
$TZ_GE
,
$TZ_LE
);
END {
undef
$TZ_DOM
;
undef
$TZ_LAST
;
undef
$TZ_GE
;
undef
$TZ_LE
;
}
$TZ_DOM
= 1;
$TZ_LAST
= 2;
$TZ_GE
= 3;
$TZ_LE
= 4;
our
(
$TZ_STANDARD
,
$TZ_RULE
,
$TZ_OFFSET
);
END {
undef
$TZ_STANDARD
;
undef
$TZ_RULE
;
undef
$TZ_OFFSET
;
}
$TZ_STANDARD
= 1;
$TZ_RULE
= 2;
$TZ_OFFSET
= 3;
sub
new {
my
(
$class
,
$dir
) =
@_
;
$dir
=
'.'
if
(!
$dir
);
if
(! -d
"$dir/tzdata"
) {
die
"ERROR: no tzdata directory found\n"
;
}
my
$self
= {
'dir'
=>
$dir
,
'zone'
=> {},
'ruleinfo'
=> {},
'zoneinfo'
=> {},
'zonelines'
=> {},
};
bless
$self
,
$class
;
$self
->_tzd_ParseFiles();
return
$self
;
}
my
(
$Error
);
sub
_ruleInfo {
my
(
$self
,
$rule
,
$info
,
@args
) =
@_
;
my
$year
=
shift
(
@args
);
if
(
exists
$$self
{
'ruleinfo'
}{
$info
} &&
exists
$$self
{
'ruleinfo'
}{
$info
}{
$rule
} &&
exists
$$self
{
'ruleinfo'
}{
$info
}{
$rule
}{
$year
}) {
if
(
ref
$$self
{
'ruleinfo'
}{
$info
}{
$rule
}{
$year
}) {
return
@{
$$self
{
'ruleinfo'
}{
$info
}{
$rule
}{
$year
} };
}
else
{
return
$$self
{
'ruleinfo'
}{
$info
}{
$rule
}{
$year
};
}
}
if
(
$info
eq
'rules'
) {
my
@ret
;
foreach
my
$r
(
$self
->_tzd_Rule(
$rule
)) {
my
(
$y0
,
$y1
,
$ytype
,
$mon
,
$flag
,
$dow
,
$num
,
$timetype
,
$time
,
$offset
,
$lett
) =
@$r
;
next
if
(
$y0
>
$year
||
$y1
<
$year
);
push
(
@ret
,
$r
)
if
(
$ytype
eq
"-"
||
$year
== 9999 ||
(
$ytype
eq
'even'
&&
$year
=~ /[02468]$/) ||
(
$ytype
eq
'odd'
&&
$year
=~ /[13579]$/));
}
@ret
=
sort
{
$$a
[3] <=>
$$b
[3] }
@ret
;
$$self
{
'ruleinfo'
}{
$info
}{
$rule
}{
$year
} = [
@ret
];
return
@ret
;
}
elsif
(
$info
eq
'stdlett'
||
$info
eq
'savlett'
) {
my
@rules
=
$self
->_ruleInfo(
$rule
,
'rules'
,
$year
);
my
%lett
= ();
foreach
my
$r
(
@rules
) {
my
(
$y0
,
$y1
,
$ytype
,
$mon
,
$flag
,
$dow
,
$num
,
$timetype
,
$time
,
$offset
,
$lett
) =
@$r
;
$lett
{
$lett
} = 1
if
( (
$info
eq
'stdlett'
&&
$offset
eq
'00:00:00'
) ||
(
$info
eq
'savlett'
&&
$offset
ne
'00:00:00'
) );
}
my
$ret
;
if
(!
%lett
) {
$ret
=
''
;
}
else
{
$ret
=
join
(
","
,
sort
keys
%lett
);
}
$$self
{
'ruleinfo'
}{
$info
}{
$rule
}{
$year
} =
$ret
;
return
$ret
;
}
elsif
(
$info
eq
'lastoff'
) {
my
$ret
;
my
@rules
=
$self
->_ruleInfo(
$rule
,
'rules'
,
$year
);
return
'00:00:00'
if
(!
@rules
);
my
$r
=
pop
(
@rules
);
my
(
$y0
,
$y1
,
$ytype
,
$mon
,
$flag
,
$dow
,
$num
,
$timetype
,
$time
,
$offset
,
$lett
) =
@$r
;
$$self
{
'ruleinfo'
}{
$info
}{
$rule
}{
$year
} =
$offset
;
return
$offset
;
}
elsif
(
$info
eq
'rdates'
) {
my
@ret
;
my
@rules
=
$self
->_ruleInfo(
$rule
,
'rules'
,
$year
);
foreach
my
$r
(
@rules
) {
my
(
$y0
,
$y1
,
$ytype
,
$mon
,
$flag
,
$dow
,
$num
,
$timetype
,
$time
,
$offset
,
$lett
) =
@$r
;
my
(
$date
) =
$self
->_tzd_ParseRuleDate(
$year
,
$mon
,
$dow
,
$num
,
$flag
,
$time
);
push
(
@ret
,
$date
,
$offset
,
$timetype
,
$lett
);
}
$$self
{
'ruleinfo'
}{
$info
}{
$rule
}{
$year
} = [
@ret
];
return
@ret
;
}
}
sub
_zoneInfo {
my
(
$self
,
$zone
,
$info
,
@args
) =
@_
;
if
(!
exists
$$self
{
'zonelines'
}{
$zone
}) {
$self
->_tzd_ZoneLines(
$zone
);
}
my
@z
=
$self
->_tzd_Zone(
$zone
);
shift
(
@z
);
my
$ret
;
my
$y
=
shift
(
@args
);
if
(
exists
$$self
{
'zoneinfo'
}{
$info
} &&
exists
$$self
{
'zoneinfo'
}{
$info
}{
$zone
} &&
exists
$$self
{
'zoneinfo'
}{
$info
}{
$zone
}{
$y
}) {
if
(
ref
(
$$self
{
'zoneinfo'
}{
$info
}{
$zone
}{
$y
})) {
return
@{
$$self
{
'zoneinfo'
}{
$info
}{
$zone
}{
$y
} };
}
else
{
return
$$self
{
'zoneinfo'
}{
$info
}{
$zone
}{
$y
};
}
}
if
(
$info
eq
'zonelines'
) {
my
(
@ret
);
while
(
@z
) {
my
$z
=
shift
(
@z
);
my
(
$offset
,
$ruletype
,
$rule
,
$abbrev
,
$yr
,
$mon
,
$dow
,
$num
,
$flag
,
$time
,
$timetype
,
$start
,
$end
) =
@$z
;
next
if
(
$yr
<
$y
);
next
if
(
$yr
==
$y
&&
$flag
==
$TZ_DOM
&&
$mon
== 1 &&
$num
== 1 &&
$time
eq
'00:00:00'
);
push
(
@ret
,
$z
);
last
if
(
$yr
>
$y
);
}
$$self
{
'zoneinfo'
}{
$info
}{
$zone
}{
$y
} = [
@ret
];
return
@ret
;
}
elsif
(
$info
eq
'rules'
) {
my
(
@ret
);
@z
=
$self
->_zoneInfo(
$zone
,
'zonelines'
,
$y
);
foreach
my
$z
(
@z
) {
my
(
$offset
,
$ruletype
,
$rule
,
$abbrev
,
$yr
,
$mon
,
$dow
,
$num
,
$flag
,
$time
,
$timetype
,
$start
,
$end
) =
@$z
;
push
(
@ret
,
$rule
,
$ruletype
);
}
$$self
{
'zoneinfo'
}{
$info
}{
$zone
}{
$y
} = [
@ret
];
return
@ret
;
}
}
sub
_tzd_ParseFiles {
my
(
$self
) =
@_
;
print
"PARSING FILES...\n"
if
(
$Verbose
);
foreach
my
$file
(
@StdFiles
) {
$self
->_tzd_ParseFile(
$file
);
}
$self
->_tzd_CheckData();
}
sub
_tzd_ParseFile {
my
(
$self
,
$file
) =
@_
;
my
$in
= new IO::File;
my
$dir
=
$$self
{
'dir'
};
print
"... $file\n"
if
(
$Verbose
);
if
(!
$in
->
open
(
"$dir/tzdata/$file"
)) {
warn
"WARNING: [parse_file] unable to open file: $file: $!\n"
;
return
;
}
my
@in
= <
$in
>;
$in
->
close
;
chomp
(
@in
);
foreach
my
$line
(
@in
) {
$line
=~ s/^\s+//;
$line
=~ s/
$line
=~ s/\s+$//;
}
my
$n
= 0;
my
$zone
=
''
;
while
(
@in
) {
if
(!
$in
[0]) {
$n
++;
shift
(
@in
);
}
elsif
(
$in
[0] =~ /^Zone/) {
$self
->_tzd_ParseZone(
$file
,\
$n
,\
@in
);
}
elsif
(
$in
[0] =~ /^Link/) {
$self
->_tzd_ParseLink(
$file
,\
$n
,\
@in
);
}
elsif
(
$in
[0] =~ /^Rule/) {
$self
->_tzd_ParseRule(
$file
,\
$n
,\
@in
);
}
else
{
$n
++;
my
$line
=
shift
(
@in
);
warn
"WARNING: [parse_file] unknown line: $n\n"
.
" $line\n"
;
}
}
}
sub
_tzd_ParseLink {
my
(
$self
,
$file
,
$n
,
$lines
) =
@_
;
$$n
++;
my
$line
=
shift
(
@$lines
);
my
(
@tmp
) =
split
(/\s+/,
$line
);
if
(
$#tmp
!= 2 ||
lc
(
$tmp
[0]) ne
'link'
) {
warn
"ERROR: [parse_file] invalid Link line: $file: $$n\n"
.
" $line\n"
;
return
;
}
my
(
$tmp
,
$zone
,
$alias
) =
@tmp
;
if
(
$self
->_tzd_Alias(
$alias
)) {
warn
"WARNING: [parse_file] alias redefined: $file: $$n: $alias\n"
;
}
$self
->_tzd_Alias(
$alias
,
$zone
);
}
sub
_tzd_ParseRule {
my
(
$self
,
$file
,
$n
,
$lines
) =
@_
;
$$n
++;
my
$line
=
shift
(
@$lines
);
my
(
@tmp
) =
split
(/\s+/,
$line
);
if
(
$#tmp
!= 9 ||
lc
(
$tmp
[0]) ne
'rule'
) {
warn
"ERROR: [parse_file] invalid Rule line: $file: $$n:\n"
.
" $line\n"
;
return
;
}
my
(
$tmp
,
$name
,
$from
,
$to
,
$type
,
$in
,
$on
,
$at
,
$save
,
$letters
) =
@tmp
;
$self
->_tzd_Rule(
$name
,[
$from
,
$to
,
$type
,
$in
,
$on
,
$at
,
$save
,
$letters
]);
}
sub
_tzd_ParseZone {
my
(
$self
,
$file
,
$n
,
$lines
) =
@_
;
$$n
++;
my
$line
=
shift
(
@$lines
);
my
@tmp
=
split
(/\s+/,
$line
);
if
(
$#tmp
< 4 ||
lc
(
$tmp
[0]) ne
'zone'
) {
warn
"ERROR: [parse_file] invalid Zone line: $file :$$n\n"
.
" $line\n"
;
return
;
}
shift
(
@tmp
);
my
$zone
=
shift
(
@tmp
);
$line
=
join
(
' '
,
@tmp
);
unshift
(
@$lines
,
$line
);
if
(
$self
->_tzd_Zone(
$zone
)) {
warn
"ERROR: [parse_file] zone redefined: $file: $$n: $zone\n"
;
$self
->_tzd_DeleteZone(
$zone
);
}
$self
->_tzd_Alias(
$zone
,
$zone
);
while
(1) {
last
if
(!
@$lines
);
$line
=
$$lines
[0];
return
if
(
$line
=~ /^(zone|
link
|rule)/i);
$$n
++;
shift
(
@$lines
);
next
if
(!
$line
);
@tmp
=
split
(/\s+/,
$line
);
if
(
$#tmp
< 2) {
warn
"ERROR: [parse_file] invalid Zone line: $file: $$n\n"
.
" $line\n"
;
return
;
}
my
(
$gmt
,
$rule
,
$format
,
@until
) =
@tmp
;
$self
->_tzd_Zone(
$zone
,[
$gmt
,
$rule
,
$format
,
@until
]);
}
}
sub
_tzd_CheckData {
my
(
$self
) =
@_
;
print
"CHECKING DATA...\n"
if
(
$Verbose
);
$self
->_tzd_CheckRules();
$self
->_tzd_CheckZones();
$self
->_tzd_CheckAliases();
}
sub
_tzd_Alias {
my
(
$self
,
$alias
,
$zone
) =
@_
;
if
(
defined
$zone
) {
$$self
{
'alias'
}{
$alias
} =
$zone
;
return
;
}
elsif
(
exists
$$self
{
'alias'
}{
$alias
}) {
return
$$self
{
'alias'
}{
$alias
};
}
else
{
return
''
;
}
}
sub
_tzd_DeleteAlias {
my
(
$self
,
$alias
) =
@_
;
delete
$$self
{
'alias'
}{
$alias
};
}
sub
_tzd_AliasKeys {
my
(
$self
) =
@_
;
return
keys
%{
$$self
{
'alias'
} };
}
sub
_tzd_CheckAliases {
my
(
$self
) =
@_
;
print
"... aliases\n"
if
(
$Verbose
);
ALIAS:
foreach
my
$alias
(
$self
->_tzd_AliasKeys()) {
my
$zone
=
$self
->_tzd_Alias(
$alias
);
my
%tmp
;
$tmp
{
$alias
} = 1;
while
(1) {
if
(
$self
->_tzd_Zone(
$zone
)) {
$self
->_tzd_Alias(
$alias
,
$zone
);
next
ALIAS;
}
elsif
(
exists
$tmp
{
$zone
}) {
warn
"ERROR: [check_aliases] circular alias definition: $alias\n"
;
$self
->_tzd_DeleteAlias(
$alias
);
next
ALIAS;
}
elsif
(
$self
->_tzd_Alias(
$zone
)) {
$tmp
{
$zone
} = 1;
$zone
=
$self
->_tzd_Alias(
$zone
);
next
;
}
warn
"ERROR: [check_aliases] unresolved alias definition: $alias\n"
;
$self
->_tzd_DeleteAlias(
$alias
);
next
ALIAS;
}
}
}
sub
_tzd_Rule {
my
(
$self
,
$rule
,
$listref
) =
@_
;
if
(
defined
$listref
) {
if
(!
exists
$$self
{
'rule'
}{
$rule
}) {
$$self
{
'rule'
}{
$rule
} = [];
}
push
@{
$$self
{
'rule'
}{
$rule
} }, [
@$listref
];
}
elsif
(
exists
$$self
{
'rule'
}{
$rule
}) {
return
@{
$$self
{
'rule'
}{
$rule
} };
}
else
{
return
();
}
}
sub
_tzd_DeleteRule {
my
(
$self
,
$rule
) =
@_
;
delete
$$self
{
'rule'
}{
$rule
};
}
sub
_tzd_RuleNames {
my
(
$self
) =
@_
;
return
keys
%{
$$self
{
'rule'
} };
}
sub
_tzd_CheckRules {
my
(
$self
) =
@_
;
print
"... rules\n"
if
(
$Verbose
);
foreach
my
$rule
(
$self
->_tzd_RuleNames()) {
$Error
= 0;
my
@rule
=
$self
->_tzd_Rule(
$rule
);
$self
->_tzd_DeleteRule(
$rule
);
while
(
@rule
) {
my
(
$from
,
$to
,
$type
,
$in
,
$on
,
$at
,
$save
,
$letters
) =
@{
shift
(
@rule
) };
my
(
$dow
,
$num
,
$attype
);
$from
=
$self
->_rule_From (
$rule
,
$from
);
$to
=
$self
->_rule_To (
$rule
,
$to
,
$from
);
$type
=
$self
->_rule_Type (
$rule
,
$type
);
$in
=
$self
->_rule_In (
$rule
,
$in
);
(
$on
,
$dow
,
$num
) =
$self
->_rule_On (
$rule
,
$on
);
(
$attype
,
$at
) =
$self
->_rule_At (
$rule
,
$at
);
$save
=
$self
->_rule_Save (
$rule
,
$save
);
$letters
=
$self
->_rule_Letters(
$rule
,
$letters
);
if
(!
$Error
) {
$self
->_tzd_Rule(
$rule
,[
$from
,
$to
,
$type
,
$in
,
$on
,
$dow
,
$num
,
$attype
,
$at
,
$save
,
$letters
]);
}
}
$self
->_tzd_DeleteRule(
$rule
)
if
(
$Error
);
}
}
sub
_rule_DOM {
my
(
$self
,
$dom
) =
@_
;
my
%days
=
qw(mon 1 tue 2 wed 3 thu 4 fri 5 sat 6 sun 7)
;
my
(
$dow
,
$num
,
$flag
,
$err
) = (0,0,0,0);
my
(
$i
);
if
(
$dom
=~ /^(\d\d?)$/) {
(
$dow
,
$num
,
$flag
)=(0,$1,
$TZ_DOM
);
}
elsif
(
$dom
=~ /^
last
(.+)$/) {
(
$dow
,
$num
,
$flag
)=($1,0,
$TZ_LAST
);
}
elsif
(
$dom
=~ /^(.+)>=(\d\d?)$/) {
(
$dow
,
$num
,
$flag
)=($1,$2,
$TZ_GE
);
}
elsif
(
$dom
=~ /^(.+)<=(\d\d?)$/) {
(
$dow
,
$num
,
$flag
)=($1,$2,
$TZ_LE
);
}
else
{
$err
= 1;
}
if
(
$dow
) {
if
(
exists
$days
{
lc
(
$dow
) }) {
$dow
=
$days
{
lc
(
$dow
) };
}
else
{
$err
= 1;
}
}
$err
= 1
if
(
$num
>31);
return
(
$dow
,
$num
,
$flag
,
$err
);
}
sub
_rule_Month {
my
(
$self
,
$mmm
) =
@_
;
my
%months
=
qw(jan 1 feb 2 mar 3 apr 4 may 5 jun 6
jul 7 aug 8 sep 9 oct 10 nov 11 dec 12)
;
if
(
exists
$months
{
lc
(
$mmm
) }) {
return
$months
{
lc
(
$mmm
) };
}
else
{
return
0;
}
}
sub
_rule_Time {
my
(
$self
,
$time
,
$sign
,
$type
) =
@_
;
my
(
$s
,
$t
);
if
(
$type
) {
$t
=
'w'
;
if
(
$type
&&
$time
=~ s/(w|u|s)$//i) {
$t
=
lc
($1);
}
}
if
(
$sign
) {
if
(
$time
=~ s/^-//) {
$s
=
"-"
;
}
else
{
$s
=
''
;
$time
=~ s/^\+//;
}
}
else
{
$s
=
''
;
}
return
''
if
(
$time
!~ /^(\d\d?)(?::(\d\d))?(?::(\d\d))?$/);
my
(
$hr
,
$mn
,
$se
)=($1,$2,$3);
$hr
=
'00'
if
(!
$hr
);
$mn
=
'00'
if
(!
$mn
);
$se
=
'00'
if
(!
$se
);
$hr
=
"0$hr"
if
(
length
(
$hr
)<2);
$mn
=
"0$mn"
if
(
length
(
$mn
)<2);
$se
=
"0$se"
if
(
length
(
$se
)<2);
$time
=
"$s$hr:$mn:$se"
;
if
(
$type
) {
return
(
$time
,
$t
);
}
else
{
return
$time
;
}
}
sub
_rule_From {
my
(
$self
,
$rule
,
$from
) =
@_
;
$from
=
lc
(
$from
);
if
(
$from
=~ /^\d\d\d\d$/) {
return
$from
;
}
elsif
(
$from
eq
'minimum'
||
$from
eq
'min'
) {
return
'0001'
;
}
warn
"ERROR: [rule_from] invalid: $rule: $from\n"
;
$Error
= 1;
return
''
;
}
sub
_rule_To {
my
(
$self
,
$rule
,
$to
,
$from
) =
@_
;
$to
=
lc
(
$to
);
if
(
$to
=~ /^\d\d\d\d$/) {
return
$to
;
}
elsif
(
$to
eq
'maximum'
||
$to
eq
'max'
) {
return
'9999'
;
}
elsif
(
lc
(
$to
) eq
'only'
) {
return
$from
;
}
warn
"ERROR: [rule_to] invalid: $rule: $to\n"
;
$Error
= 1;
return
''
;
}
sub
_rule_Type {
my
(
$self
,
$rule
,
$type
) =
@_
;
return
lc
(
$type
)
if
(
lc
(
$type
) eq
"-"
||
lc
(
$type
) eq
'even'
||
lc
(
$type
) eq
'odd'
);
warn
"ERROR: [rule_type] invalid: $rule: $type\n"
;
$Error
= 1;
return
''
;
}
sub
_rule_In {
my
(
$self
,
$rule
,
$in
) =
@_
;
my
(
$i
) =
$self
->_rule_Month(
$in
);
if
(!
$i
) {
warn
"ERROR: [rule_in] invalid: $rule: $in\n"
;
$Error
= 1;
}
return
$i
;
}
sub
_rule_On {
my
(
$self
,
$rule
,
$on
) =
@_
;
my
(
$dow
,
$num
,
$flag
,
$err
) =
$self
->_rule_DOM(
$on
);
if
(
$err
) {
warn
"ERROR: [rule_on] invalid: $rule: $on\n"
;
$Error
= 1;
}
return
(
$flag
,
$dow
,
$num
);
}
sub
_rule_At {
my
(
$self
,
$rule
,
$at
) =
@_
;
my
(
$ret
,
$attype
) =
$self
->_rule_Time(
$at
,0,1);
if
(!
$ret
) {
warn
"ERROR: [rule_at] invalid: $rule: $at\n"
;
$Error
= 1;
}
return
(
$attype
,
$ret
);
}
sub
_rule_Save {
my
(
$self
,
$rule
,
$save
) =
@_
;
$save
=
'00:00:00'
if
(
$save
eq
"-"
);
my
(
$ret
) =
$self
->_rule_Time(
$save
,1);
if
(!
$ret
) {
warn
"ERROR: [rule_save] invalid: $rule: $save\n"
;
$Error
=1;
}
return
$ret
;
}
sub
_rule_Letters {
my
(
$self
,
$rule
,
$letters
)=
@_
;
return
''
if
(
$letters
eq
"-"
);
return
$letters
;
}
my
(
$TZ_START
) =
$dmb
->
join
(
'date'
,[
'0001'
,1,2,0,0,0]);
my
(
$TZ_END
) =
$dmb
->
join
(
'date'
,[
'9999'
,12,30,23,59,59]);
sub
_tzd_Zone {
my
(
$self
,
$zone
,
$listref
) =
@_
;
if
(
defined
$listref
) {
if
(!
exists
$$self
{
'zone'
}{
$zone
}) {
$$self
{
'zone'
}{
$zone
} = [
$zone
];
}
push
@{
$$self
{
'zone'
}{
$zone
} }, [
@$listref
];
}
elsif
(
exists
$$self
{
'zone'
}{
$zone
}) {
return
@{
$$self
{
'zone'
}{
$zone
} };
}
else
{
return
();
}
}
sub
_tzd_DeleteZone {
my
(
$self
,
$zone
) =
@_
;
delete
$$self
{
'zone'
}{
$zone
};
}
sub
_tzd_ZoneKeys {
my
(
$self
) =
@_
;
return
keys
%{
$$self
{
'zone'
} };
}
sub
_tzd_CheckZones {
my
(
$self
) =
@_
;
print
"... zones\n"
if
(
$Verbose
);
foreach
my
$zone
(
$self
->_tzd_ZoneKeys()) {
my
(
$start
) =
$TZ_START
;
$Error
= 0;
my
(
$name
,
@zone
) =
$self
->_tzd_Zone(
$zone
);
$self
->_tzd_DeleteZone(
$zone
);
while
(
@zone
) {
my
(
$gmt
,
$rule
,
$format
,
@until
) = @{
shift
(
@zone
) };
my
(
$ruletype
);
$gmt
=
$self
->_zone_GMTOff(
$zone
,
$gmt
);
(
$ruletype
,
$rule
) =
$self
->_zone_Rule (
$zone
,
$rule
);
$format
=
$self
->_zone_Format(
$zone
,
$format
);
my
(
$y
,
$m
,
$dow
,
$num
,
$flag
,
$t
,
$type
,
$end
,
$nextstart
)
=
$self
->_zone_Until (
$zone
,
@until
);
if
(!
$Error
) {
$self
->_tzd_Zone(
$zone
,[
$gmt
,
$ruletype
,
$rule
,
$format
,
$y
,
$m
,
$dow
,
$num
,
$flag
,
$t
,
$type
,
$start
,
$end
]);
$start
=
$nextstart
;
}
}
$self
->_tzd_DeleteZone(
$zone
)
if
(
$Error
);
}
}
sub
_zone_GMTOff {
my
(
$self
,
$zone
,
$gmt
) =
@_
;
my
(
$ret
) =
$self
->_rule_Time(
$gmt
,1);
if
(!
$ret
) {
warn
"ERROR: [zone_gmtoff] invalid: $zone: $gmt\n"
;
$Error
= 1;
}
return
$ret
;
}
sub
_zone_Rule {
my
(
$self
,
$zone
,
$rule
) =
@_
;
return
(
$TZ_STANDARD
,
$rule
)
if
(
$rule
eq
"-"
);
my
(
$ret
) =
$self
->_rule_Time(
$rule
,1);
return
(
$TZ_OFFSET
,
$ret
)
if
(
$ret
);
if
(!
$self
->_tzd_Rule(
$rule
)) {
warn
"ERROR: [zone_rule] rule undefined: $zone: $rule\n"
;
$Error
= 1;
}
return
(
$TZ_RULE
,
$rule
);
}
sub
_zone_Format {
my
(
$self
,
$zone
,
$format
)=
@_
;
return
$format
;
}
sub
_zone_Until {
my
(
$self
,
$zone
,
$y
,
$m
,
$d
,
$t
) =
@_
;
my
(
$tmp
,
$type
,
$dow
,
$num
,
$flag
,
$err
);
if
(!
$y
) {
$y
= 9999;
$m
= 12;
$d
= 31;
$t
=
'00:00:00'
;
}
else
{
if
(
$y
!~ /^\d\d\d\d$/) {
warn
"ERROR: [zone_until] invalid year: $zone: $y\n"
;
$Error
= 1;
return
();
}
if
(!
$m
) {
$m
= 1;
$d
= 1;
$t
=
'00:00:00'
;
}
else
{
$tmp
=
$self
->_rule_Month(
$m
);
if
(!
$tmp
) {
warn
"ERROR: [zone_until] invalid month: $zone: $m\n"
;
$Error
= 1;
return
();
}
$m
=
$tmp
;
if
(!
$d
) {
$d
= 1;
$t
=
'00:00:00'
;
}
elsif
(
$d
=~ /^
last
(.*)/) {
my
(
@tmp
) =
$self
->_rule_DOM(
$d
);
my
(
$dow
) =
$tmp
[0];
my
$ymd
=
$dmb
->nth_day_of_week(
$y
,-1,
$dow
,
$m
);
$d
=
$$ymd
[2];
}
elsif
(
$d
=~ />=/) {
my
(
@tmp
) =
$self
->_rule_DOM(
$d
);
my
$dow
=
$tmp
[0];
$d
=
$tmp
[1];
my
$ddow
=
$dmb
->day_of_week([
$y
,
$m
,
$d
]);
if
(
$dow
>
$ddow
) {
my
$ymd
=
$dmb
->calc_date_days([
$y
,
$m
,
$d
],
$dow
-
$ddow
);
$d
=
$$ymd
[2];
}
elsif
(
$dow
<
$ddow
) {
my
$ymd
=
$dmb
->calc_date_days([
$y
,
$m
,
$d
],7-(
$ddow
-
$dow
));
$d
=
$$ymd
[2];
}
}
elsif
(
$d
=~ /<=/) {
my
(
@tmp
) =
$self
->_rule_DOM(
$d
);
my
$dow
=
$tmp
[0];
$d
=
$tmp
[1];
my
$ddow
=
$dmb
->day_of_week([
$y
,
$m
,
$d
]);
if
(
$dow
<
$ddow
) {
my
$ymd
=
$dmb
->calc_date_days([
$y
,
$m
,
$d
],
$ddow
-
$dow
,1);
$d
=
$$ymd
[2];
}
elsif
(
$dow
>
$ddow
) {
my
$ymd
=
$dmb
->calc_date_days([
$y
,
$m
,
$d
],7-(
$dow
-
$ddow
),1);
$d
=
$$ymd
[2];
}
}
else
{
}
if
(!
$t
) {
$t
=
'00:00:00'
;
}
}
}
(
$dow
,
$num
,
$flag
,
$err
) =
$self
->_rule_DOM(
$d
);
if
(
$err
) {
warn
"ERROR: [zone_until] invalid day: $zone: $d\n"
;
$Error
= 1;
return
();
}
$m
=
"0$m"
if
(
length
(
$m
)<2);
if
(
$y
== 9999) {
$type
=
'w'
;
}
else
{
(
$tmp
,
$type
) =
$self
->_rule_Time(
$t
,0,1);
if
(!
$tmp
) {
warn
"ERROR: [zone_until] invalid time: $zone: $t\n"
;
$Error
= 1;
return
();
}
$t
=
$tmp
;
}
my
(
$start
,
$end
) = (
''
,
''
);
if
(
$type
eq
'w'
) {
$start
=
$dmb
->
join
(
'date'
,[
$y
,
$m
,
$d
,@{
$dmb
->
split
(
'hms'
,
$t
) }]);
$end
=
$dmb
->_calc_date_time_strings(
$start
,
'0:0:1'
,1);
}
return
(
$y
,
$m
,
$dow
,
$num
,
$flag
,
$t
,
$type
,
$end
,
$start
);
}
sub
_tzd_ZoneLines {
my
(
$self
,
$zone
) =
@_
;
my
@z
=
$self
->_tzd_Zone(
$zone
);
shift
(
@z
);
my
$i
= 0;
my
(
$lastend
,
$lastdstend
) = (
''
,
'00:00:00'
);
foreach
my
$z
(
@z
) {
my
(
$offset
,
$ruletype
,
$rule
,
$abbrev
,
$yr
,
$mon
,
$dow
,
$num
,
$flag
,
$time
,
$timetype
,
$start
,
$end
) =
@$z
;
if
(!
$start
) {
$start
=
$dmb
->_calc_date_time_strings(
$lastend
,
'0:0:1'
,0);
}
my
$fixend
= 0;
if
(!
$end
) {
$end
=
$self
->_tzd_ParseRuleDate(
$yr
,
$mon
,
$dow
,
$num
,
$flag
,
$time
);
$fixend
= 1;
}
my
(
$dstbeg
,
$dstend
,
$letbeg
,
$letend
);
if
(
$ruletype
==
$TZ_RULE
) {
$dstbeg
=
$lastdstend
;
my
%lett
= ();
my
$tmp
=
$dmb
->
split
(
'date'
,
$end
);
my
$y
=
$$tmp
[0];
my
(
@rdate
) =
$self
->_ruleInfo(
$rule
,
'rdates'
,
$y
);
my
$d
=
$start
;
while
(
@rdate
) {
my
(
$date
,
$off
,
$type
,
$lett
,
@tmp
) =
@rdate
;
$lett
{
$off
} =
$lett
;
@rdate
=
@tmp
;
next
if
(
$date
lt
$d
||
$date
gt
$end
);
$d
=
$date
;
$dstend
=
$off
;
}
if
(!
$dstend
) {
my
(
$yrbeg
) =
$dmb
->
join
(
'date'
,[
$y
,1,1,0,0,0]);
if
(
$start
ge
$yrbeg
) {
$dstend
=
$dstbeg
;
}
else
{
$dstend
=
$self
->_ruleInfo(
$rule
,
'lastoff'
,
$y
);
}
}
$letbeg
=
$lett
{
$dstbeg
};
$letend
=
$lett
{
$dstend
};
}
elsif
(
$ruletype
==
$TZ_STANDARD
) {
$dstbeg
=
'00:00:00'
;
$dstend
=
$dstbeg
;
$letbeg
=
''
;
$letend
=
''
;
}
else
{
$dstbeg
=
$rule
;
$dstend
=
$dstbeg
;
$letbeg
=
''
;
$letend
=
''
;
}
if
(
$fixend
) {
if
(
$timetype
eq
'u'
) {
$end
=
$dmb
->_calc_date_time_strings(
$end
,
$offset
,0);
}
$end
=
$dmb
->_calc_date_time_strings(
$end
,
$dstend
,1);
}
$i
++;
$$self
{
'zonelines'
}{
$zone
}{
$i
}{
'start'
} =
$start
;
$$self
{
'zonelines'
}{
$zone
}{
$i
}{
'end'
} =
$end
;
$$self
{
'zonelines'
}{
$zone
}{
$i
}{
'stdoff'
} =
$offset
;
$$self
{
'zonelines'
}{
$zone
}{
$i
}{
'dstbeg'
} =
$dstbeg
;
$$self
{
'zonelines'
}{
$zone
}{
$i
}{
'dstend'
} =
$dstend
;
$$self
{
'zonelines'
}{
$zone
}{
$i
}{
'letbeg'
} =
$letbeg
;
$$self
{
'zonelines'
}{
$zone
}{
$i
}{
'letend'
} =
$letend
;
$$self
{
'zonelines'
}{
$zone
}{
$i
}{
'abbrev'
} =
$abbrev
;
$$self
{
'zonelines'
}{
$zone
}{
$i
}{
'rule'
} = (
$ruletype
==
$TZ_RULE
?
$rule
:
''
);
$lastend
=
$end
;
$lastdstend
=
$dstend
;
}
$$self
{
'zonelines'
}{
$zone
}{
'numlines'
} =
$i
;
}
sub
_tzd_ParseRuleDate {
my
(
$self
,
$year
,
$mon
,
$dow
,
$num
,
$flag
,
$time
) =
@_
;
my
(
$dom
);
if
(
$flag
==
$TZ_DOM
) {
$dom
=
$num
;
}
elsif
(
$flag
==
$TZ_LAST
) {
(
$year
,
$mon
,
$dom
) = @{
$dmb
->nth_day_of_week(
$year
,-1,
$dow
,
$mon
) };
}
elsif
(
$flag
==
$TZ_GE
) {
(
$year
,
$mon
,
$dom
) = @{
$dmb
->nth_day_of_week(
$year
,1,
$dow
,
$mon
) };
while
(
$dom
<
$num
) {
$dom
+= 7;
}
}
elsif
(
$flag
==
$TZ_LE
) {
(
$year
,
$mon
,
$dom
) = @{
$dmb
->nth_day_of_week(
$year
,-1,
$dow
,
$mon
) };
while
(
$dom
>
$num
) {
$dom
-= 7;
}
}
my
(
$h
,
$mn
,
$s
) =
split
(/:/,
$time
);
return
$dmb
->
join
(
'date'
,[
$year
,
$mon
,
$dom
,
$h
,
$mn
,
$s
]);
}
1;