diff --git a/db/Makefile b/db/Makefile index 0da5f4c..44071f0 100644 --- a/db/Makefile +++ b/db/Makefile @@ -1,9 +1,10 @@ TZDIR=tzdata TZ_FILES=$(addprefix $(TZDIR)/, africa antarctica asia australasia backward etcetera europe northamerica southamerica) +tzout: DATE := $(shell date +%F) tzout: $(TZ_FILES) TZ_VERSION=`perl -n -e 'm/^VERSION\s*=\s*(\S+)/ and print $$1;' $(TZDIR)/Makefile`; \ - ./tz-erl --version $$TZ_VERSION -o $@ $^ + ./tz-erl --version $$TZ_VERSION --date $(DATE) -o $@ $^ $(TZ_FILES): $(TZDIR) diff --git a/db/tz-erl b/db/tz-erl index 4936553..3f41e21 100755 --- a/db/tz-erl +++ b/db/tz-erl @@ -145,15 +145,17 @@ sub process_data { } } +# Converts an offset in the format "[+-]?HH:MM" or "[+-]?HH" into minutes. +# For example, "2:00" -> 120, "-0:30" -> -30, "+5" -> 300. sub offset_minutes { my ($off, $adj) = @_; my $convert_offset = sub { - my $m = $_[0]; - if ($m =~ m/^([\+\-]?)(?:(\d+):)?(\d+)$/) { - $m = (defined $2 ? $2 : 0) * MPH + $3; - if ($1 eq '-') { $m = -$m; } - } + $_[0] =~ m/^([\+\-]?)(\d+)(?::(\d+))?$/ + or die "offset \"$_[0]\" did not match"; + my $m = $2 * MPH; + if (defined $3) { $m += $3; } + if ($1 eq '-') { $m = -$m; } return $m; }; @@ -256,9 +258,13 @@ sub zone_line { # We ignore any zone line that has a definite until (end) time that # is in the past. if (defined $until) { - my $until_year = ($until =~ m/^(\d+)/)[0]; - if ($until_year >= $current_year) { - die "until $until not handled"; + my ($until_year, $until_month, $until_day) = split_ymd($until); + if (($until_year > $current_year) || + (($until_year == $current_year) && + (($until_month > $current_month) || + (($until_month == $current_month) && + ($until_day >= $current_day))))) { + "future until \"$until\" not handled"; } return; } @@ -286,7 +292,10 @@ sub zone_line { $rule1 = $rule2 = RULE_NULL; } elsif (scalar(@rules) == 1) { # One active rule. This is a year that DST started or stopped - # being observed + # being observed. erlang_localtime doesn't handle this. If DST + # stopped being observed in this year, don't output a DST rule. + # If DST started being observed, do. (Except that we don't handle + # this yet.) print STDERR Data::Dump::dump(\@rules), "\n"; die "one rule for $name"; $name1 = zonename($format, $rules[0]->[RULE_LETTERS], undef); @@ -427,6 +436,23 @@ INIT { %dow_from_name= map { $dow_to_name[$_] => $_ } (0..$#dow_to_name); } +sub split_ymd { + my ($ymd) = @_; + $ymd =~ m/^(\d+)(?:\s+(\w+)(?:\s+(\d+)))?/ + or die "parse \"$ymd\" for ymd failed"; + my $year = $1; + my $month = do { + if (defined $2) { + defined $mon_from_name{$2} or die "parse \"$ymd\" for month failed"; + $mon_from_name{$2}; + } else { + 0 + } + }; + my $day = defined $3 ? $3 : 0; + return ($year, $month, $day); +} + sub on_to_day_of_month { my ($on, $year, $month) = @_; diff --git a/include/tz_database.hrl b/include/tz_database.hrl index 2539526..7e6ac2f 100644 --- a/include/tz_database.hrl +++ b/include/tz_database.hrl @@ -427,33 +427,33 @@ {"Eire",{"GMT","GMT"},{"IST","IST"},0,60,{last,sun,mar},{1,0},{last,sun,oct},{2,0}}, {"Etc/GMT",{"GMT","GMT"},undef,0,0,undef,{0,0},undef,{0,0}}, {"Etc/GMT+0",{"GMT","GMT"},undef,0,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT+1",{"GMT+1","GMT+1"},undef,-1,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT+10",{"GMT+10","GMT+10"},undef,-10,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT+11",{"GMT+11","GMT+11"},undef,-11,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT+12",{"GMT+12","GMT+12"},undef,-12,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT+2",{"GMT+2","GMT+2"},undef,-2,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT+3",{"GMT+3","GMT+3"},undef,-3,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT+4",{"GMT+4","GMT+4"},undef,-4,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT+5",{"GMT+5","GMT+5"},undef,-5,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT+6",{"GMT+6","GMT+6"},undef,-6,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT+7",{"GMT+7","GMT+7"},undef,-7,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT+8",{"GMT+8","GMT+8"},undef,-8,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT+9",{"GMT+9","GMT+9"},undef,-9,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT+1",{"GMT+1","GMT+1"},undef,60,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT+10",{"GMT+10","GMT+10"},undef,600,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT+11",{"GMT+11","GMT+11"},undef,660,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT+12",{"GMT+12","GMT+12"},undef,720,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT+2",{"GMT+2","GMT+2"},undef,120,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT+3",{"GMT+3","GMT+3"},undef,180,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT+4",{"GMT+4","GMT+4"},undef,240,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT+5",{"GMT+5","GMT+5"},undef,300,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT+6",{"GMT+6","GMT+6"},undef,360,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT+7",{"GMT+7","GMT+7"},undef,420,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT+8",{"GMT+8","GMT+8"},undef,480,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT+9",{"GMT+9","GMT+9"},undef,540,0,undef,{0,0},undef,{0,0}}, {"Etc/GMT-0",{"GMT","GMT"},undef,0,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT-1",{"GMT-1","GMT-1"},undef,1,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT-10",{"GMT-10","GMT-10"},undef,10,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT-11",{"GMT-11","GMT-11"},undef,11,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT-12",{"GMT-12","GMT-12"},undef,12,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT-13",{"GMT-13","GMT-13"},undef,13,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT-14",{"GMT-14","GMT-14"},undef,14,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT-2",{"GMT-2","GMT-2"},undef,2,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT-3",{"GMT-3","GMT-3"},undef,3,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT-4",{"GMT-4","GMT-4"},undef,4,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT-5",{"GMT-5","GMT-5"},undef,5,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT-6",{"GMT-6","GMT-6"},undef,6,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT-7",{"GMT-7","GMT-7"},undef,7,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT-8",{"GMT-8","GMT-8"},undef,8,0,undef,{0,0},undef,{0,0}}, - {"Etc/GMT-9",{"GMT-9","GMT-9"},undef,9,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT-1",{"GMT-1","GMT-1"},undef,-60,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT-10",{"GMT-10","GMT-10"},undef,-600,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT-11",{"GMT-11","GMT-11"},undef,-660,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT-12",{"GMT-12","GMT-12"},undef,-720,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT-13",{"GMT-13","GMT-13"},undef,-780,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT-14",{"GMT-14","GMT-14"},undef,-840,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT-2",{"GMT-2","GMT-2"},undef,-120,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT-3",{"GMT-3","GMT-3"},undef,-180,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT-4",{"GMT-4","GMT-4"},undef,-240,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT-5",{"GMT-5","GMT-5"},undef,-300,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT-6",{"GMT-6","GMT-6"},undef,-360,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT-7",{"GMT-7","GMT-7"},undef,-420,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT-8",{"GMT-8","GMT-8"},undef,-480,0,undef,{0,0},undef,{0,0}}, + {"Etc/GMT-9",{"GMT-9","GMT-9"},undef,-540,0,undef,{0,0},undef,{0,0}}, {"Etc/GMT0",{"GMT","GMT"},undef,0,0,undef,{0,0},undef,{0,0}}, {"Etc/Greenwich",{"GMT","GMT"},undef,0,0,undef,{0,0},undef,{0,0}}, {"Etc/UCT",{"UCT","UCT"},undef,0,0,undef,{0,0},undef,{0,0}}, diff --git a/src/localtime.erl b/src/localtime.erl index ea07769..d6f18ed 100644 --- a/src/localtime.erl +++ b/src/localtime.erl @@ -20,6 +20,7 @@ ,get_timezone/1 ,list_timezones/0 ,adjust_datetime/2 + ,fmt_shift/1 ]). % utc_to_local(UtcDateTime, Timezone) -> LocalDateTime | [LocalDateTime, DstLocalDateTime] | {error, ErrDescr} @@ -214,6 +215,8 @@ fmt_shift({'+', H, M}) -> H * 60 + M; fmt_shift({'-', H, M}) -> -(H * 60 + M); +fmt_shift(0) -> + 0; fmt_shift(Any) -> throw(Any). @@ -231,14 +234,18 @@ tr_char([H|T], From, To, Acc) -> end. -define(SPACE_CHAR, 32). -get_timezone_inner(TimeZone) -> +get_timezone_inner(TimeZone) when is_binary(TimeZone) -> + get_timezone_inner(erlang:binary_to_list(TimeZone)); +get_timezone_inner(TimeZone) when is_list(TimeZone) -> TimeZoneNoSpaces = tr_char(TimeZone, ?SPACE_CHAR, $_), case dict:find(TimeZoneNoSpaces, ?tz_index) of error -> TimeZoneNoSpaces; {ok, [TZName | _]} -> TZName - end. + end; +get_timezone_inner(_) -> + throw({error, "Timezone should be string/binary"}). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl").