Add Timezone/DST Disambiguation
This commit is contained in:
parent
ef659da9a4
commit
7384819edb
4 changed files with 200 additions and 44 deletions
|
@ -1,3 +1,8 @@
|
||||||
|
## 0.3.0 (In development)
|
||||||
|
|
||||||
|
* Add Timezone/Daylight Saving Disambiguation
|
||||||
|
* Add the `auto` timezone shortcut
|
||||||
|
|
||||||
## 0.2.1
|
## 0.2.1
|
||||||
|
|
||||||
* Fix allowing timezone names to be binary
|
* Fix allowing timezone names to be binary
|
||||||
|
|
|
@ -65,6 +65,10 @@ T, Z, r, and c), `qdate` will handle them for us.
|
||||||
+ `to_now(Date)` - converts any date/time format to Erlang now format.
|
+ `to_now(Date)` - converts any date/time format to Erlang now format.
|
||||||
+ `to_unixtime(Date)` - converts any date/time format to a unixtime integer
|
+ `to_unixtime(Date)` - converts any date/time format to a unixtime integer
|
||||||
|
|
||||||
|
A **ToTimezone** value of the atom `auto` will automatically determine the
|
||||||
|
timezone. For example, `to_date(Date, auto)` is exactly the same as
|
||||||
|
`to_date(Date)`
|
||||||
|
|
||||||
**A Note About Argument Order**: In all cases, `ToTimezone` is optional and if
|
**A Note About Argument Order**: In all cases, `ToTimezone` is optional and if
|
||||||
omitted, will be determined as described below in "Understanding Timezone
|
omitted, will be determined as described below in "Understanding Timezone
|
||||||
Determining and Conversion". If `ToTimezone` is specified, it will always be
|
Determining and Conversion". If `ToTimezone` is specified, it will always be
|
||||||
|
@ -93,6 +97,80 @@ will infer the timezone in the following order.
|
||||||
application variable `default_timezone`.
|
application variable `default_timezone`.
|
||||||
+ If no timezone is specified by either of the above, `qdate` assumes "GMT"
|
+ If no timezone is specified by either of the above, `qdate` assumes "GMT"
|
||||||
for all dates.
|
for all dates.
|
||||||
|
+ A timezone value of `auto` will act as if no timezone is specified.
|
||||||
|
|
||||||
|
#### Disambiguating Ambiguous Timezone Conversions
|
||||||
|
|
||||||
|
Sometimes, when youre converting a datetime from one timezone to another, there
|
||||||
|
are potentially two different results if the conversion happens to land on in a
|
||||||
|
timezone that's in the middle of a Daylight Saving conversion. For example,
|
||||||
|
converting "11-Nov-2013 1:00:am" in "America/New York" to "GMT" could be both
|
||||||
|
"5am" and "6am" in GMT, since "1am EST". This is a side effect of the
|
||||||
|
"intelligence" of `qdate` - `qdate` would notice that 1am in New York is EST,
|
||||||
|
and should be converted to "1am EST", and then do the conversion from "1am EST"
|
||||||
|
to "GMT". This can lead to confusion.
|
||||||
|
|
||||||
|
Further, since `qdate` attempts to be "smart" about mistakenly entered
|
||||||
|
timezones (ie, if you entered "2013-01-01 EDT", `qdate` knows that "EDT"
|
||||||
|
(Eastern Daylight Time) doesn't apply to January first, so it *assumes* you
|
||||||
|
meant "EST".
|
||||||
|
|
||||||
|
**THE SOLUTION** to this tangled mess that we call Daylight Saving Time is to
|
||||||
|
provide an option to disambiguate if you so desire. By default disambiguation
|
||||||
|
is disabled, and `qdate` will just guess as to it's best choice. But if you so
|
||||||
|
desire, you can make sure qdate does *both* conversions, and returns both.
|
||||||
|
|
||||||
|
You can do this by passing a `Disambiguation` argument to `to_string` or
|
||||||
|
`to_date`. `Disambiguation` can be an atom of the values:
|
||||||
|
|
||||||
|
+ `prefer_standard` *(Default Behavior)*: If an ambiguous result occurs,
|
||||||
|
qdate will return the date in standard time rather than daylight time.
|
||||||
|
+ `prefer_daylight`: If an ambiguous result occurs, qdate will return the
|
||||||
|
preferred daylight time.
|
||||||
|
+ `both`: If an ambiguous result occurs, `qdate` will return the tuple:
|
||||||
|
`{ambiguous, DateStandard, DateDaylight}`, where `DateStandard` is the date
|
||||||
|
in Standard Time, and `DateDaylight` is the date in Daylight Saving Time.
|
||||||
|
|
||||||
|
So the two more helper functions are:
|
||||||
|
|
||||||
|
+ `to_date(ToTimezone, Disambiguate, Date)`
|
||||||
|
+ `to_string(FormatString, ToTimezone, Disambiguate, Date)`
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```erlang
|
||||||
|
1> qdate:set_timezone("GMT").
|
||||||
|
ok
|
||||||
|
|
||||||
|
%% Here, converting GMT 2013-11-03 6AM to America/New York yields an ambiguous
|
||||||
|
%% result
|
||||||
|
2> qdate:to_date("America/New York", both, {{2013,11,3},{6,0,0}}).
|
||||||
|
{ambiguous,{{2013,11,3},{1,0,0}},{{2013,11,3},{2,0,0}}}
|
||||||
|
|
||||||
|
%% Let's just use daylight time
|
||||||
|
3> qdate:to_date("America/New York", prefer_daylight, {{2013,11,3},{6,0,0}}).
|
||||||
|
{{2013,11,3},{2,0,0}}
|
||||||
|
|
||||||
|
%% Let's just use standard time (the default behavior)
|
||||||
|
4> qdate:to_date("America/New York", prefer_standard, {{2013,11,3},{6,0,0}}).
|
||||||
|
{{2013,11,3},{1,0,0}}
|
||||||
|
|
||||||
|
5> qdate:set_timezone("America/New York").
|
||||||
|
ok
|
||||||
|
|
||||||
|
%% Switching from 1AM Eastern Time to GMT yields a potentially ambiguous result
|
||||||
|
6> qdate:to_date("GMT", both, {{2013,11,3},{1,0,0}}).
|
||||||
|
{ambiguous,{{2013,11,3},{6,0,0}},{{2013,11,3},{5,0,0}}}
|
||||||
|
|
||||||
|
%% Use daylight time for conversion
|
||||||
|
7> qdate:to_date("GMT", prefer_daylight, {{2013,11,3},{1,0,0}}).
|
||||||
|
{{2013,11,3},{5,0,0}}
|
||||||
|
|
||||||
|
%% Here we demonstrated that even if we ask for "both", if there is no
|
||||||
|
%% ambiguity, the plain date is returned
|
||||||
|
8> qdate:to_date("GMT", both, {{2013,11,3},{5,0,0}}).
|
||||||
|
{{2013,11,3},{10,0,0}}
|
||||||
|
```
|
||||||
|
|
||||||
#### Conversion Functions provided for API compatibility with `ec_date`
|
#### Conversion Functions provided for API compatibility with `ec_date`
|
||||||
|
|
||||||
|
@ -543,6 +621,7 @@ not exist.
|
||||||
### Thanks to Additional Contributors
|
### Thanks to Additional Contributors
|
||||||
|
|
||||||
+ [Mark Allen](https://github.com/mrallen1)
|
+ [Mark Allen](https://github.com/mrallen1)
|
||||||
|
+ [Christopher Phillips](http://github.com/lostcolony)
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{application, qdate,
|
{application, qdate,
|
||||||
[
|
[
|
||||||
{description, "Simple Date and Timezone handling for Erlang"},
|
{description, "Simple Date and Timezone handling for Erlang"},
|
||||||
{vsn, "0.2.1"},
|
{vsn, "0.3.0-pre"},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{applications, [
|
{applications, [
|
||||||
kernel,
|
kernel,
|
||||||
|
|
156
src/qdate.erl
156
src/qdate.erl
|
@ -13,8 +13,10 @@
|
||||||
to_string/1,
|
to_string/1,
|
||||||
to_string/2,
|
to_string/2,
|
||||||
to_string/3,
|
to_string/3,
|
||||||
|
to_string/4,
|
||||||
to_date/1,
|
to_date/1,
|
||||||
to_date/2,
|
to_date/2,
|
||||||
|
to_date/3,
|
||||||
to_now/1,
|
to_now/1,
|
||||||
to_unixtime/1,
|
to_unixtime/1,
|
||||||
unixtime/0
|
unixtime/0
|
||||||
|
@ -67,6 +69,8 @@
|
||||||
end).
|
end).
|
||||||
|
|
||||||
-define(DETERMINE_TZ, determine_timezone()).
|
-define(DETERMINE_TZ, determine_timezone()).
|
||||||
|
-define(DEFAULT_DISAMBIG, prefer_standard).
|
||||||
|
-define(else, true).
|
||||||
|
|
||||||
start() ->
|
start() ->
|
||||||
application:start(qdate).
|
application:start(qdate).
|
||||||
|
@ -74,22 +78,24 @@ start() ->
|
||||||
stop() ->
|
stop() ->
|
||||||
application:stop(qdate).
|
application:stop(qdate).
|
||||||
|
|
||||||
|
|
||||||
to_string(Format) ->
|
to_string(Format) ->
|
||||||
to_string(Format, os:timestamp()).
|
to_string(Format, os:timestamp()).
|
||||||
|
|
||||||
to_string(Format, Date) ->
|
to_string(Format, Date) ->
|
||||||
to_string(Format, ?DETERMINE_TZ, Date).
|
to_string(Format, ?DETERMINE_TZ, Date).
|
||||||
|
|
||||||
to_string(FormatKey, ToTZ, Date) when is_atom(FormatKey) orelse is_tuple(FormatKey) ->
|
to_string(Format, ToTZ, Date) ->
|
||||||
|
to_string(Format, ToTZ, ?DEFAULT_DISAMBIG, Date).
|
||||||
|
|
||||||
|
to_string(FormatKey, ToTZ, Disambiguate, Date) when is_atom(FormatKey) orelse is_tuple(FormatKey) ->
|
||||||
Format = case qdate_srv:get_format(FormatKey) of
|
Format = case qdate_srv:get_format(FormatKey) of
|
||||||
undefined -> throw({undefined_format_key,FormatKey});
|
undefined -> throw({undefined_format_key,FormatKey});
|
||||||
F -> F
|
F -> F
|
||||||
end,
|
end,
|
||||||
to_string(Format, ToTZ, Date);
|
to_string(Format, ToTZ, Disambiguate, Date);
|
||||||
to_string(Format, ToTZ, Date) when is_binary(Format) ->
|
to_string(Format, ToTZ, Disambiguate, Date) when is_binary(Format) ->
|
||||||
list_to_binary(to_string(binary_to_list(Format), ToTZ, Date));
|
list_to_binary(to_string(binary_to_list(Format), ToTZ, Disambiguate, Date));
|
||||||
to_string(Format, ToTZ, Date) when is_list(Format) ->
|
to_string(Format, ToTZ, Disambiguate, Date) when is_list(Format) ->
|
||||||
%% it may seem odd that we're ensuring it here, and then again
|
%% it may seem odd that we're ensuring it here, and then again
|
||||||
%% as one of the last steps of the to_date process, but we need
|
%% as one of the last steps of the to_date process, but we need
|
||||||
%% the actual name for the strings for the PHP "T" and "e", so
|
%% the actual name for the strings for the PHP "T" and "e", so
|
||||||
|
@ -97,46 +103,73 @@ to_string(Format, ToTZ, Date) when is_list(Format) ->
|
||||||
%% Then we can pass it on to to_date as well. That way we don't have
|
%% Then we can pass it on to to_date as well. That way we don't have
|
||||||
%% to do it twice, since it's already ensured.
|
%% to do it twice, since it's already ensured.
|
||||||
ActualToTZ = ensure_timezone(ToTZ),
|
ActualToTZ = ensure_timezone(ToTZ),
|
||||||
to_string_worker(Format, ActualToTZ, to_date(ActualToTZ, Date)).
|
case to_date(ActualToTZ, Disambiguate, Date) of
|
||||||
|
{ambiguous, Standard, Daylight} ->
|
||||||
|
{ambiguous,
|
||||||
|
to_string_worker(Format, ActualToTZ, prefer_standard, Standard),
|
||||||
|
to_string_worker(Format, ActualToTZ, prefer_daylight, Daylight)};
|
||||||
|
ActualDate ->
|
||||||
|
case tz_name(ActualDate,Disambiguate, ActualToTZ) of
|
||||||
|
{ambiguous,_,_} ->
|
||||||
|
{ambiguous,
|
||||||
|
to_string_worker(Format, ActualToTZ, prefer_standard, ActualDate),
|
||||||
|
to_string_worker(Format, ActualToTZ, prefer_daylight, ActualDate)};
|
||||||
|
_ ->
|
||||||
|
to_string_worker(Format, ActualToTZ, Disambiguate, ActualDate)
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
to_string_worker([], _, _) ->
|
|
||||||
|
|
||||||
|
to_string_worker([], _, _, _) ->
|
||||||
"";
|
"";
|
||||||
to_string_worker([$e|RestFormat], ToTZ, Date) ->
|
to_string_worker([$e|RestFormat], ToTZ, Disamb, Date) ->
|
||||||
ToTZ ++ to_string_worker(RestFormat, ToTZ, Date);
|
ToTZ ++ to_string_worker(RestFormat, ToTZ, Disamb, Date);
|
||||||
to_string_worker([$I|RestFormat], ToTZ, Date) ->
|
to_string_worker([$I|RestFormat], ToTZ, Disamb, Date) ->
|
||||||
I = case localtime_dst:check(Date, ToTZ) of
|
I = case localtime_dst:check(Date, ToTZ) of
|
||||||
is_in_dst -> "1";
|
is_in_dst -> "1";
|
||||||
is_not_in_dst -> "0";
|
is_not_in_dst -> "0";
|
||||||
ambiguous_time -> "?"
|
ambiguous_time -> "?"
|
||||||
end,
|
end,
|
||||||
I ++ to_string_worker(RestFormat, ToTZ, Date);
|
I ++ to_string_worker(RestFormat, ToTZ, Disamb, Date);
|
||||||
to_string_worker([H | RestFormat], ToTZ, Date) when H==$O orelse H==$P ->
|
to_string_worker([H | RestFormat], ToTZ, Disamb, Date) when H==$O orelse H==$P ->
|
||||||
Shift = get_timezone_shift(ToTZ, Date),
|
Shift = get_timezone_shift(ToTZ, Disamb, Date),
|
||||||
Separator = case H of
|
Separator = case H of
|
||||||
$O -> "";
|
$O -> "";
|
||||||
$P -> ":"
|
$P -> ":"
|
||||||
end,
|
end,
|
||||||
format_shift(Shift,Separator) ++ to_string_worker(RestFormat, ToTZ, Date);
|
format_shift(Shift,Separator) ++ to_string_worker(RestFormat, ToTZ, Disamb, Date);
|
||||||
to_string_worker([$T | RestFormat], ToTZ, Date) ->
|
to_string_worker([$T | RestFormat], ToTZ, Disamb, Date) ->
|
||||||
{ShortName,_} = localtime:tz_name(Date, ToTZ),
|
ShortName = tz_name(Date, Disamb, ToTZ),
|
||||||
ShortName ++ to_string_worker(RestFormat, ToTZ, Date);
|
ShortName ++ to_string_worker(RestFormat, ToTZ, Disamb, Date);
|
||||||
to_string_worker([$Z | RestFormat], ToTZ, Date) ->
|
to_string_worker([$Z | RestFormat], ToTZ, Disamb, Date) ->
|
||||||
{Sign, Hours, Mins} = get_timezone_shift(ToTZ, Date),
|
{Sign, Hours, Mins} = get_timezone_shift(ToTZ, Disamb, Date),
|
||||||
Seconds = (Hours * 3600) + (Mins * 60),
|
Seconds = (Hours * 3600) + (Mins * 60),
|
||||||
atom_to_list(Sign) ++ integer_to_list(Seconds) ++ to_string_worker(RestFormat, ToTZ, Date);
|
atom_to_list(Sign) ++ integer_to_list(Seconds) ++ to_string_worker(RestFormat, ToTZ, Disamb, Date);
|
||||||
to_string_worker([$r | RestFormat], ToTZ, Date) ->
|
to_string_worker([$r | RestFormat], ToTZ, Disamb, Date) ->
|
||||||
NewFormat = "D, d M Y H:i:s O",
|
NewFormat = "D, d M Y H:i:s O",
|
||||||
to_string_worker(NewFormat, ToTZ, Date) ++ to_string_worker(RestFormat, ToTZ, Date);
|
to_string_worker(NewFormat, ToTZ, Disamb, Date) ++ to_string_worker(RestFormat, ToTZ, Disamb, Date);
|
||||||
to_string_worker([$c | RestFormat], ToTZ, Date) ->
|
to_string_worker([$c | RestFormat], ToTZ, Disamb, Date) ->
|
||||||
Format1 = "Y-m-d",
|
Format1 = "Y-m-d",
|
||||||
Format2 = "H:i:sP",
|
Format2 = "H:i:sP",
|
||||||
to_string_worker(Format1, ToTZ, Date)
|
to_string_worker(Format1, ToTZ, Disamb, Date)
|
||||||
++ "T"
|
++ "T"
|
||||||
++ to_string_worker(Format2, ToTZ, Date)
|
++ to_string_worker(Format2, ToTZ, Disamb, Date)
|
||||||
++ to_string_worker(RestFormat, ToTZ, Date);
|
++ to_string_worker(RestFormat, ToTZ, Disamb, Date);
|
||||||
to_string_worker([H | RestFormat], ToTZ, Date) ->
|
to_string_worker([H | RestFormat], ToTZ, Disamb, Date) ->
|
||||||
ec_date:format([H], Date) ++ to_string_worker(RestFormat, ToTZ, Date).
|
ec_date:format([H], Date) ++ to_string_worker(RestFormat, ToTZ, Disamb, Date).
|
||||||
|
|
||||||
|
tz_name(Date, Disambiguate, ToTZ) ->
|
||||||
|
case localtime:tz_name(Date, ToTZ) of
|
||||||
|
{ShortName, _} when is_list(ShortName) ->
|
||||||
|
ShortName;
|
||||||
|
{{ShortStandard,_},{ShortDST,_}} ->
|
||||||
|
case Disambiguate of
|
||||||
|
prefer_standard -> ShortStandard;
|
||||||
|
prefer_daylight -> ShortDST;
|
||||||
|
both -> {ambiguous, ShortStandard, ShortDST}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
format_shift({Sign,Hours,Mins},Separator) ->
|
format_shift({Sign,Hours,Mins},Separator) ->
|
||||||
SignStr = atom_to_list(Sign),
|
SignStr = atom_to_list(Sign),
|
||||||
|
@ -166,11 +199,14 @@ nparse(String) ->
|
||||||
to_date(RawDate) ->
|
to_date(RawDate) ->
|
||||||
to_date(?DETERMINE_TZ, RawDate).
|
to_date(?DETERMINE_TZ, RawDate).
|
||||||
|
|
||||||
to_date(ToTZ, RawDate) when is_binary(RawDate) ->
|
|
||||||
to_date(ToTZ, binary_to_list(RawDate));
|
|
||||||
to_date(ToTZ, RawDate) when is_binary(ToTZ) ->
|
|
||||||
to_date(binary_to_list(ToTZ), RawDate);
|
|
||||||
to_date(ToTZ, RawDate) ->
|
to_date(ToTZ, RawDate) ->
|
||||||
|
to_date(ToTZ, ?DEFAULT_DISAMBIG, RawDate).
|
||||||
|
|
||||||
|
to_date(ToTZ, Disambiguate, RawDate) when is_binary(RawDate) ->
|
||||||
|
to_date(ToTZ, Disambiguate, binary_to_list(RawDate));
|
||||||
|
to_date(ToTZ, Disambiguate, RawDate) when is_binary(ToTZ) ->
|
||||||
|
to_date(binary_to_list(ToTZ), Disambiguate, RawDate);
|
||||||
|
to_date(ToTZ, Disambiguate, RawDate) ->
|
||||||
{ExtractedDate, ExtractedTZ} = extract_timezone(RawDate),
|
{ExtractedDate, ExtractedTZ} = extract_timezone(RawDate),
|
||||||
{RawDate3, FromTZ} = case try_registered_parsers(RawDate) of
|
{RawDate3, FromTZ} = case try_registered_parsers(RawDate) of
|
||||||
undefined ->
|
undefined ->
|
||||||
|
@ -182,12 +218,12 @@ to_date(ToTZ, RawDate) ->
|
||||||
end,
|
end,
|
||||||
try raw_to_date(RawDate3) of
|
try raw_to_date(RawDate3) of
|
||||||
D={{_,_,_},{_,_,_}} ->
|
D={{_,_,_},{_,_,_}} ->
|
||||||
date_tz_to_tz(D, FromTZ, ToTZ)
|
date_tz_to_tz(D, Disambiguate, FromTZ, ToTZ)
|
||||||
catch
|
catch
|
||||||
_:_ ->
|
_:_ ->
|
||||||
case raw_to_date(RawDate) of
|
case raw_to_date(RawDate) of
|
||||||
D2={{_,_,_},{_,_,_}} ->
|
D2={{_,_,_},{_,_,_}} ->
|
||||||
date_tz_to_tz(D2, ?DETERMINE_TZ, ToTZ)
|
date_tz_to_tz(D2, Disambiguate, ?DETERMINE_TZ, ToTZ)
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -270,11 +306,12 @@ compare(A, Op, B) ->
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Timezone Stuff %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Timezone Stuff %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
get_timezone_shift(TZ, Date) ->
|
get_timezone_shift(TZ, Disambiguate, Date) ->
|
||||||
case localtime:tz_shift(Date, TZ) of
|
case localtime:tz_shift(Date, TZ) of
|
||||||
unable_to_detect -> {error,unable_to_detect};
|
unable_to_detect -> {error,unable_to_detect};
|
||||||
{error,T} -> {error,T};
|
{error,T} -> {error,T};
|
||||||
{Sh, _DstSh} -> Sh;
|
{Sh, _} when Disambiguate==prefer_standard -> Sh;
|
||||||
|
{_, Sh} when Disambiguate==prefer_daylight -> Sh;
|
||||||
Sh -> Sh
|
Sh -> Sh
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -336,12 +373,29 @@ determine_timezone() ->
|
||||||
%% If FromTZ is an integer, then it's an integer that represents the number of minutes
|
%% If FromTZ is an integer, then it's an integer that represents the number of minutes
|
||||||
%% relative to GMT. So we convert the date to GMT based on that number, then we can
|
%% relative to GMT. So we convert the date to GMT based on that number, then we can
|
||||||
%% do the other timezone conversion.
|
%% do the other timezone conversion.
|
||||||
date_tz_to_tz(Date, FromTZ, ToTZ) when is_integer(FromTZ) ->
|
date_tz_to_tz(Date, Disambiguate, FromTZ, ToTZ) when is_integer(FromTZ) ->
|
||||||
NewDate = localtime:adjust_datetime(Date, FromTZ),
|
NewDate = localtime:adjust_datetime(Date, FromTZ),
|
||||||
date_tz_to_tz(NewDate, "GMT", ToTZ);
|
date_tz_to_tz(NewDate, Disambiguate, "GMT", ToTZ);
|
||||||
date_tz_to_tz(Date, FromTZ, ToTZ) ->
|
date_tz_to_tz(Date, Disambiguate, FromTZ, ToTZ) ->
|
||||||
ActualToTZ = ensure_timezone(ToTZ),
|
ActualToTZ = ensure_timezone(ToTZ),
|
||||||
localtime:local_to_local(Date,FromTZ,ActualToTZ).
|
case Disambiguate of
|
||||||
|
prefer_standard ->
|
||||||
|
localtime:local_to_local(Date, FromTZ, ActualToTZ);
|
||||||
|
prefer_daylight ->
|
||||||
|
localtime:local_to_local_dst(Date, FromTZ, ActualToTZ);
|
||||||
|
both ->
|
||||||
|
date_tz_to_tz_both(Date, FromTZ, ToTZ)
|
||||||
|
end.
|
||||||
|
|
||||||
|
date_tz_to_tz_both(Date, FromTZ, ToTZ) ->
|
||||||
|
Standard = localtime:local_to_local(Date, FromTZ, ToTZ),
|
||||||
|
Daylight = localtime:local_to_local_dst(Date, FromTZ, ToTZ),
|
||||||
|
if
|
||||||
|
Standard=:=Daylight ->
|
||||||
|
Standard;
|
||||||
|
?else ->
|
||||||
|
{ambiguous, Standard, Daylight}
|
||||||
|
end.
|
||||||
|
|
||||||
set_timezone(TZ) when is_binary(TZ) ->
|
set_timezone(TZ) when is_binary(TZ) ->
|
||||||
set_timezone(binary_to_list(TZ));
|
set_timezone(binary_to_list(TZ));
|
||||||
|
@ -360,6 +414,8 @@ get_timezone() ->
|
||||||
get_timezone(Key) ->
|
get_timezone(Key) ->
|
||||||
qdate_srv:get_timezone(Key).
|
qdate_srv:get_timezone(Key).
|
||||||
|
|
||||||
|
ensure_timezone(auto) ->
|
||||||
|
?DETERMINE_TZ;
|
||||||
ensure_timezone(Key) when is_atom(Key) orelse is_tuple(Key) ->
|
ensure_timezone(Key) when is_atom(Key) orelse is_tuple(Key) ->
|
||||||
case get_timezone(Key) of
|
case get_timezone(Key) of
|
||||||
undefined -> throw({timezone_key_not_found,Key});
|
undefined -> throw({timezone_key_not_found,Key});
|
||||||
|
@ -469,7 +525,8 @@ tz_test_() ->
|
||||||
tz_tests(SetupData),
|
tz_tests(SetupData),
|
||||||
test_process_die(SetupData),
|
test_process_die(SetupData),
|
||||||
parser_format_test(SetupData),
|
parser_format_test(SetupData),
|
||||||
test_deterministic_parser(SetupData)
|
test_deterministic_parser(SetupData),
|
||||||
|
test_disambiguation(SetupData)
|
||||||
]}
|
]}
|
||||||
end
|
end
|
||||||
}.
|
}.
|
||||||
|
@ -489,6 +546,21 @@ test_deterministic_parser(_) ->
|
||||||
?_assertEqual({{2012,5,10}, {0,0,0}}, qdate:to_date("2012-5-10"))
|
?_assertEqual({{2012,5,10}, {0,0,0}}, qdate:to_date("2012-5-10"))
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
|
test_disambiguation(_) ->
|
||||||
|
{inorder, [
|
||||||
|
?_assertEqual(ok, set_timezone("America/New York")),
|
||||||
|
?_assertEqual({ambiguous, {{2013,11,3},{6,0,0}}, {{2013,11,3},{5,0,0}}}, qdate:to_date("GMT",both,{{2013,11,3},{1,0,0}})),
|
||||||
|
?_assertEqual({{2013,11,3},{6,0,0}}, qdate:to_date("GMT",prefer_standard,{{2013,11,3},{1,0,0}})),
|
||||||
|
?_assertEqual({{2013,11,3},{5,0,0}}, qdate:to_date("GMT",prefer_daylight,{{2013,11,3},{1,0,0}})),
|
||||||
|
?_assertEqual({ambiguous, "GMT","GMT"}, qdate:to_string("T", "GMT", both, {{2013,11,3},{1,0,0}})),
|
||||||
|
?_assertEqual({ambiguous, "EST","EDT"}, qdate:to_string("T", auto, both, {{2013,11,3},{1,0,0}})),
|
||||||
|
?_assertEqual(ok, set_timezone("GMT")),
|
||||||
|
?_assertEqual({ambiguous, {{2013,11,3},{1,0,0}}, {{2013,11,3},{2,0,0}}}, qdate:to_date("America/New York", both, {{2013,11,3},{6,0,0}})),
|
||||||
|
?_assertEqual({{2013,11,3},{2,0,0}}, qdate:to_date("America/New York", prefer_daylight, {{2013,11,3},{6,0,0}})),
|
||||||
|
?_assertEqual({{2013,11,3},{1,0,0}}, qdate:to_date("America/New York", prefer_standard, {{2013,11,3},{6,0,0}}))
|
||||||
|
]}.
|
||||||
|
|
||||||
|
|
||||||
tz_tests(_) ->
|
tz_tests(_) ->
|
||||||
{inorder,[
|
{inorder,[
|
||||||
?_assertEqual(ok,set_timezone(<<"Europe/Moscow">>)),
|
?_assertEqual(ok,set_timezone(<<"Europe/Moscow">>)),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue