Просмотр исходного кода

Support datetime value and add datetime_format encode option

Takeru Ohta 9 лет назад
Родитель
Сommit
1d6944dcf3
6 измененных файлов с 185 добавлено и 21 удалено
  1. 15 14
      README.md
  2. 67 2
      doc/jsone.md
  3. 1 1
      src/jsone.app.src
  4. 40 3
      src/jsone.erl
  5. 39 1
      src/jsone_encode.erl
  6. 23 0
      test/jsone_encode_tests.erl

+ 15 - 14
README.md

@@ -157,20 +157,21 @@ Data Mapping (Erlang <=> JSON)
 Erlang                  JSON             Erlang
 Erlang                  JSON             Erlang
 =================================================================================================
 =================================================================================================
 
 
-null                 -> null          -> null
-true                 -> true          -> true
-false                -> false         -> false
-<<"abc">>            -> "abc"         -> <<"abc">>
-abc                  -> "abc"         -> <<"abc">> % non-special atom is regarded as a binary
-123                  -> 123           -> 123
-123.4                -> 123.4         -> 123.4
-[1,2,3]              -> [1,2,3]       -> [1,2,3]
-{[]}                 -> {}            -> {[]}                       % object_format=tuple
-{[{key, <<"val">>}]} -> {"key":"val"} -> {[{<<"key">>, <<"val">>}]} % object_format=tuple
-[{}]                 -> {}            -> [{}]                       % object_format=proplist
-[{<<"key">>, val}]   -> {"key":"val"} -> [{<<"key">>, <<"val">>}]   % object_format=proplist
-#{}                  -> {}            -> #{}                        % object_format=map
-#{key => val}        -> {"key":"val"} -> #{<<"key">> => <<"val">>}  % object_format=map
+null                 -> null                   -> null
+true                 -> true                   -> true
+false                -> false                  -> false
+<<"abc">>            -> "abc"                  -> <<"abc">>
+abc                  -> "abc"                  -> <<"abc">> % non-special atom is regarded as a binary
+{{2010,1,1},{0,0,0}} -> "2010-01-01T00:00:00Z" -> <<"2010-01-01T00:00:00Z">> % datetime (see: `jsone:datetime_encode_format/0`)
+123                  -> 123                    -> 123
+123.4                -> 123.4                  -> 123.4
+[1,2,3]              -> [1,2,3]                -> [1,2,3]
+{[]}                 -> {}                     -> {[]}                       % object_format=tuple
+{[{key, <<"val">>}]} -> {"key":"val"}          -> {[{<<"key">>, <<"val">>}]} % object_format=tuple
+[{}]                 -> {}                     -> [{}]                       % object_format=proplist
+[{<<"key">>, val}]   -> {"key":"val"}          -> [{<<"key">>, <<"val">>}]   % object_format=proplist
+#{}                  -> {}                     -> #{}                        % object_format=map
+#{key => val}        -> {"key":"val"}          -> #{<<"key">> => <<"val">>}  % object_format=map
 ```
 ```
 
 
 API
 API

+ 67 - 2
doc/jsone.md

@@ -15,6 +15,47 @@ JSON decoding/encoding module.
 
 
 
 
 
 
+### <a name="type-datetime_encode_format">datetime_encode_format()</a> ###
+
+
+<pre><code>
+datetime_encode_format() = <a href="#type-datetime_format">datetime_format()</a> | {Format::<a href="#type-datetime_format">datetime_format()</a>, TimeZone::<a href="#type-timezone">timezone()</a>}
+</code></pre>
+
+ Datetime encoding format.
+
+The default value of `TimeZone` is `utc`.
+
+```
+  %
+  % Universal Time
+  %
+  > jsone:encode({{2000, 3, 10}, {10, 3, 58}}, [{datetime_format, iso8601}]).
+  <<"\"2000-03-10T10:03:58Z\"">>
+  %
+  % Local Time (JST)
+  %
+  > jsone:encode({{2000, 3, 10}, {10, 3, 58}}, [{datetime_format, {iso8601, local}}]).
+  <<"\"2000-03-10T10:03:58+09:00\"">>
+  %
+  % Explicit TimeZone Offset
+  %
+  > jsone:encode({{2000, 3, 10}, {10, 3, 58}}, [{datetime_format, {iso8601, -2*60*60}}]).
+  <<"\"2000-03-10T10:03:58-02:00\"">>
+```
+
+
+
+### <a name="type-datetime_format">datetime_format()</a> ###
+
+
+<pre><code>
+datetime_format() = iso8601
+</code></pre>
+
+
+
+
 ### <a name="type-decode_option">decode_option()</a> ###
 ### <a name="type-decode_option">decode_option()</a> ###
 
 
 
 
@@ -35,7 +76,7 @@ decode_option() = {object_format, tuple | proplist | map}
 
 
 
 
 <pre><code>
 <pre><code>
-encode_option() = native_utf8 | {float_format, [<a href="#type-float_format_option">float_format_option()</a>]} | {object_key_type, string | scalar | value} | {space, non_neg_integer()} | {indent, non_neg_integer()}
+encode_option() = native_utf8 | {float_format, [<a href="#type-float_format_option">float_format_option()</a>]} | {datetime_format, <a href="#type-datetime_encode_format">datetime_encode_format()</a>} | {object_key_type, string | scalar | value} | {space, non_neg_integer()} | {indent, non_neg_integer()}
 </code></pre>
 </code></pre>
 
 
 `native_utf8`: <br />
 `native_utf8`: <br />
@@ -45,6 +86,10 @@ encode_option() = native_utf8 | {float_format, [<a href="#type-float_format_opti
 - Encodes a `float()` value in the format which specified by `Options` <br />
 - Encodes a `float()` value in the format which specified by `Options` <br />
 - default: `[{scientific, 20}]` <br />
 - default: `[{scientific, 20}]` <br />
 
 
+`{datetime_format, Format}`:
+- Encodes a `calendar:datetime()` value in the format which specified by `Format` <br />
+- default: `{iso8601, utc}` <br />
+
 `object_key_type`:
 `object_key_type`:
 - Allowable object key type <br />
 - Allowable object key type <br />
 - `string`: Only string values are allowed (i.e. `json_string()` type) <br />
 - `string`: Only string values are allowed (i.e. `json_string()` type) <br />
@@ -188,7 +233,7 @@ json_scalar() = <a href="#type-json_boolean">json_boolean()</a> | <a href="#type
 
 
 
 
 <pre><code>
 <pre><code>
-json_string() = binary() | atom()
+json_string() = binary() | atom() | <a href="calendar.md#type-datetime">calendar:datetime()</a>
 </code></pre>
 </code></pre>
 
 
 NOTE: `decode/1` always returns `binary()` value
 NOTE: `decode/1` always returns `binary()` value
@@ -202,6 +247,26 @@ NOTE: `decode/1` always returns `binary()` value
 json_value() = <a href="#type-json_number">json_number()</a> | <a href="#type-json_string">json_string()</a> | <a href="#type-json_array">json_array()</a> | <a href="#type-json_object">json_object()</a> | <a href="#type-json_boolean">json_boolean()</a> | null
 json_value() = <a href="#type-json_number">json_number()</a> | <a href="#type-json_string">json_string()</a> | <a href="#type-json_array">json_array()</a> | <a href="#type-json_object">json_object()</a> | <a href="#type-json_boolean">json_boolean()</a> | null
 </code></pre>
 </code></pre>
 
 
+
+
+
+### <a name="type-timezone">timezone()</a> ###
+
+
+<pre><code>
+timezone() = utc | local | <a href="#type-utc_offset_seconds">utc_offset_seconds()</a>
+</code></pre>
+
+
+
+
+### <a name="type-utc_offset_seconds">utc_offset_seconds()</a> ###
+
+
+<pre><code>
+utc_offset_seconds() = -86399..86399
+</code></pre>
+
 <a name="index"></a>
 <a name="index"></a>
 
 
 ## Function Index ##
 ## Function Index ##

+ 1 - 1
src/jsone.app.src

@@ -2,7 +2,7 @@
 {application, jsone,
 {application, jsone,
  [
  [
   {description, "Erlang JSON Library"},
   {description, "Erlang JSON Library"},
-  {vsn, "1.0.0"},
+  {vsn, "1.0.2"},
   {registered, []},
   {registered, []},
   {applications, [kernel, stdlib]},
   {applications, [kernel, stdlib]},
   {contributors, ["Takeru Ohta"]},
   {contributors, ["Takeru Ohta"]},

+ 40 - 3
src/jsone.erl

@@ -51,7 +51,9 @@
 
 
               encode_option/0,
               encode_option/0,
               decode_option/0,
               decode_option/0,
-              float_format_option/0
+              float_format_option/0,
+              datetime_encode_format/0, datetime_format/0,
+              timezone/0, utc_offset_seconds/0
              ]).
              ]).
 
 
 %%--------------------------------------------------------------------------------
 %%--------------------------------------------------------------------------------
@@ -60,7 +62,7 @@
 -type json_value()          :: json_number() | json_string() | json_array() | json_object() | json_boolean() | null.
 -type json_value()          :: json_number() | json_string() | json_array() | json_object() | json_boolean() | null.
 -type json_boolean()        :: boolean().
 -type json_boolean()        :: boolean().
 -type json_number()         :: number().
 -type json_number()         :: number().
--type json_string()         :: binary() | atom(). % NOTE: `decode/1' always returns `binary()' value
+-type json_string()         :: binary() | atom() | calendar:datetime(). % NOTE: `decode/1' always returns `binary()' value
 -type json_array()          :: [json_value()].
 -type json_array()          :: [json_value()].
 -type json_object()         :: json_object_format_tuple()
 -type json_object()         :: json_object_format_tuple()
                              | json_object_format_proplist()
                              | json_object_format_proplist()
@@ -100,10 +102,41 @@
 %%
 %%
 %% > jsone:encode(1.23, [{float_format, [{decimals, 4}, compact]}]).
 %% > jsone:encode(1.23, [{float_format, [{decimals, 4}, compact]}]).
 %% <<"1.23">>
 %% <<"1.23">>
-%%'''
+%% '''
+
+-type datetime_encode_format() :: Format::datetime_format()
+                                | {Format::datetime_format(), TimeZone::timezone()}.
+%% Datetime encoding format.
+%%
+%% The default value of `TimeZone' is `utc'.
+%%
+%% ```
+%% %
+%% % Universal Time
+%% %
+%% > jsone:encode({{2000, 3, 10}, {10, 3, 58}}, [{datetime_format, iso8601}]).
+%% <<"\"2000-03-10T10:03:58Z\"">>
+%%
+%% %
+%% % Local Time (JST)
+%% %
+%% > jsone:encode({{2000, 3, 10}, {10, 3, 58}}, [{datetime_format, {iso8601, local}}]).
+%% <<"\"2000-03-10T10:03:58+09:00\"">>
+%%
+%% %
+%% % Explicit TimeZone Offset
+%% %
+%% > jsone:encode({{2000, 3, 10}, {10, 3, 58}}, [{datetime_format, {iso8601, -2*60*60}}]).
+%% <<"\"2000-03-10T10:03:58-02:00\"">>
+%% '''
+
+-type datetime_format() :: iso8601.
+-type timezone() :: utc | local | utc_offset_seconds().
+-type utc_offset_seconds() :: -86399..86399.
 
 
 -type encode_option() :: native_utf8
 -type encode_option() :: native_utf8
                        | {float_format, [float_format_option()]}
                        | {float_format, [float_format_option()]}
+                       | {datetime_format, datetime_encode_format()}
                        | {object_key_type, string | scalar | value}
                        | {object_key_type, string | scalar | value}
                        | {space, non_neg_integer()}
                        | {space, non_neg_integer()}
                        | {indent, non_neg_integer()}.
                        | {indent, non_neg_integer()}.
@@ -114,6 +147,10 @@
 %% - Encodes a `float()` value in the format which specified by `Options' <br />
 %% - Encodes a `float()` value in the format which specified by `Options' <br />
 %% - default: `[{scientific, 20}]' <br />
 %% - default: `[{scientific, 20}]' <br />
 %%
 %%
+%% `{datetime_format, Format}`:
+%% - Encodes a `calendar:datetime()` value in the format which specified by `Format' <br />
+%% - default: `{iso8601, utc}' <br />
+%%
 %% `object_key_type':
 %% `object_key_type':
 %% - Allowable object key type <br />
 %% - Allowable object key type <br />
 %% - `string': Only string values are allowed (i.e. `json_string()' type) <br />
 %% - `string': Only string values are allowed (i.e. `json_string()' type) <br />

+ 39 - 1
src/jsone_encode.erl

@@ -39,7 +39,10 @@
 -define(IS_REDUNDANT_UTF8(B1, B2, FirstBitN), (B1 =:= 0 andalso B2 < (1 bsl (FirstBitN + 1)))).
 -define(IS_REDUNDANT_UTF8(B1, B2, FirstBitN), (B1 =:= 0 andalso B2 < (1 bsl (FirstBitN + 1)))).
 -define(HEX(N, I), (binary:at(<<"0123456789abcdef">>, (N bsr (I * 4)) band 2#1111))).
 -define(HEX(N, I), (binary:at(<<"0123456789abcdef">>, (N bsr (I * 4)) band 2#1111))).
 -define(UNICODE_TO_HEX(Code), ?HEX(Code, 3), ?HEX(Code, 2), ?HEX(Code, 1), ?HEX(Code, 0)).
 -define(UNICODE_TO_HEX(Code), ?HEX(Code, 3), ?HEX(Code, 2), ?HEX(Code, 1), ?HEX(Code, 0)).
--define(IS_STR(X), is_binary(X); is_atom(X)).
+-define(IS_STR(X), (is_binary(X) orelse is_atom(X))).
+-define(IS_UINT(X), (is_integer(X) andalso X >= 0)).
+-define(IS_DATETIME(Y,M,D,H,Mi,S), (?IS_UINT(Y) andalso ?IS_UINT(M) andalso ?IS_UINT(D) andalso
+                                    ?IS_UINT(H) andalso ?IS_UINT(Mi) andalso ?IS_UINT(S))).
 
 
 -type encode_result() :: {ok, binary()} | {error, {Reason::term(), [erlang:stack_item()]}}.
 -type encode_result() :: {ok, binary()} | {error, {Reason::term(), [erlang:stack_item()]}}.
 -type next() :: {array_values, [jsone:json_value()]}
 -type next() :: {array_values, [jsone:json_value()]}
@@ -50,6 +53,7 @@
 -record(encode_opt_v1, {
 -record(encode_opt_v1, {
           native_utf8 = false :: boolean(),
           native_utf8 = false :: boolean(),
           float_format = [{scientific, 20}] :: [jsone:float_format_option()],
           float_format = [{scientific, 20}] :: [jsone:float_format_option()],
+          datetime_format = {iso8601, 0} :: {jsone:datetime_format(), jsone:utc_offset_seconds()},
           object_key_type = string :: string | scalar | value,
           object_key_type = string :: string | scalar | value,
           space = 0 :: non_neg_integer(),
           space = 0 :: non_neg_integer(),
           indent = 0 :: non_neg_integer()
           indent = 0 :: non_neg_integer()
@@ -99,6 +103,7 @@ value(true, Nexts, Buf, Opt)                         -> next(Nexts, <<Buf/binary
 value(Value, Nexts, Buf, Opt) when is_integer(Value) -> next(Nexts, <<Buf/binary, (integer_to_binary(Value))/binary>>, Opt);
 value(Value, Nexts, Buf, Opt) when is_integer(Value) -> next(Nexts, <<Buf/binary, (integer_to_binary(Value))/binary>>, Opt);
 value(Value, Nexts, Buf, Opt) when is_float(Value)   -> next(Nexts, <<Buf/binary, (float_to_binary(Value, Opt?OPT.float_format))/binary>>, Opt);
 value(Value, Nexts, Buf, Opt) when is_float(Value)   -> next(Nexts, <<Buf/binary, (float_to_binary(Value, Opt?OPT.float_format))/binary>>, Opt);
 value(Value, Nexts, Buf, Opt) when ?IS_STR(Value)    -> string(Value, Nexts, Buf, Opt);
 value(Value, Nexts, Buf, Opt) when ?IS_STR(Value)    -> string(Value, Nexts, Buf, Opt);
+value({{_,_,_},{_,_,_}} = Value, Nexts, Buf, Opt)    -> datetime(Value, Nexts, Buf, Opt);
 value({Value}, Nexts, Buf, Opt)                      -> object(Value, Nexts, Buf, Opt);
 value({Value}, Nexts, Buf, Opt)                      -> object(Value, Nexts, Buf, Opt);
 value([{}], Nexts, Buf, Opt)                         -> object([], Nexts, Buf, Opt);
 value([{}], Nexts, Buf, Opt)                         -> object([], Nexts, Buf, Opt);
 value([{_, _}|_] = Value, Nexts, Buf, Opt)           -> object(Value, Nexts, Buf, Opt);
 value([{_, _}|_] = Value, Nexts, Buf, Opt)           -> object(Value, Nexts, Buf, Opt);
@@ -112,11 +117,30 @@ string(<<Str/binary>>, Nexts, Buf, Opt) ->
 string(Str, Nexts, Buf, Opt) ->
 string(Str, Nexts, Buf, Opt) ->
     string(atom_to_binary(Str, utf8), Nexts, Buf, Opt).
     string(atom_to_binary(Str, utf8), Nexts, Buf, Opt).
 
 
+-spec datetime(calendar:datetime(), [next()], binary(), opt()) -> encode_result().
+datetime({{Y,M,D}, {H,Mi,S}}, Nexts, Buf, Opt) when ?IS_DATETIME(Y,M,D,H,Mi,S) ->
+    Str =
+        case Opt?OPT.datetime_format of
+            {iso8601, 0}  -> io_lib:format("~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0BZ", [Y, M, D, H, Mi, S]);
+            {iso8601, Tz} ->
+                {Sign, {DiffHour, DiffMinute, _}} =
+                    case Tz > 0 of
+                        true  -> {$+, calendar:seconds_to_time(Tz)};
+                        false -> {$-, calendar:seconds_to_time(-Tz)}
+                    end,
+                io_lib:format("~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0B~c~2..0B:~2..0B", [Y, M, D, H, Mi, S, Sign, DiffHour, DiffMinute])
+        end,
+    next(Nexts, <<Buf/binary, $", (list_to_binary(Str))/binary, $">>, Opt);
+datetime(Datetime, Nexts, Buf, Opt) ->
+    ?ERROR(datetime, [Datetime, Nexts, Buf, Opt]).
+
 -spec object_key(jsone:json_value(), [next()], binary(), opt()) -> encode_result().
 -spec object_key(jsone:json_value(), [next()], binary(), opt()) -> encode_result().
 object_key(Key, Nexts, Buf, Opt) when ?IS_STR(Key) ->
 object_key(Key, Nexts, Buf, Opt) when ?IS_STR(Key) ->
     string(Key, Nexts, Buf, Opt);
     string(Key, Nexts, Buf, Opt);
 object_key(Key, Nexts, Buf, Opt = ?OPT{object_key_type = scalar}) when is_number(Key) ->
 object_key(Key, Nexts, Buf, Opt = ?OPT{object_key_type = scalar}) when is_number(Key) ->
     value(Key, [{char, $"} | Nexts], <<Buf/binary, $">>, Opt);
     value(Key, [{char, $"} | Nexts], <<Buf/binary, $">>, Opt);
+object_key(Key = {{Y,M,D},{H,Mi,S}}, Nexts, Buf, Opt = ?OPT{object_key_type = Type}) when ?IS_DATETIME(Y,M,D,H,Mi,S), Type =/= string ->
+    value(Key, Nexts, Buf, Opt);
 object_key(Key, Nexts, Buf, Opt = ?OPT{object_key_type = value}) ->
 object_key(Key, Nexts, Buf, Opt = ?OPT{object_key_type = value}) ->
     case value(Key, [], <<>>, Opt) of
     case value(Key, [], <<>>, Opt) of
         {error, Reason} -> {error, Reason};
         {error, Reason} -> {error, Reason};
@@ -229,5 +253,19 @@ parse_option([{indent, N}|T], Opt) when is_integer(N), N >= 0 ->
     parse_option(T, Opt?OPT{indent = N});
     parse_option(T, Opt?OPT{indent = N});
 parse_option([{object_key_type, Type}|T], Opt) when Type =:= string; Type =:= scalar; Type =:= value ->
 parse_option([{object_key_type, Type}|T], Opt) when Type =:= string; Type =:= scalar; Type =:= value ->
     parse_option(T, Opt?OPT{object_key_type = Type});
     parse_option(T, Opt?OPT{object_key_type = Type});
+parse_option([{datetime_format, Fmt}|T], Opt) ->
+    case Fmt of
+        iso8601                                 -> parse_option(T, Opt?OPT{datetime_format = {iso8601, 0}});
+        {iso8601, utc}                          -> parse_option(T, Opt?OPT{datetime_format = {iso8601, 0}});
+        {iso8601, local}                        -> parse_option(T, Opt?OPT{datetime_format = {iso8601, local_offset()}});
+        {iso8601, N} when -86400 < N, N < 86400 -> parse_option(T, Opt?OPT{datetime_format = {iso8601, N}});
+        _                                       -> error(badarg, [[{datetime_format, Fmt}|T], Opt])
+    end;
 parse_option(List, Opt) ->
 parse_option(List, Opt) ->
     error(badarg, [List, Opt]).
     error(badarg, [List, Opt]).
+
+-spec local_offset() -> jsone:utc_offset_seconds().
+local_offset() ->
+    UTC = {{1970, 1, 2}, {0,0,0}},
+    Local = calendar:universal_time_to_local_time({{1970, 1, 2}, {0,0,0}}),
+    calendar:datetime_to_gregorian_seconds(Local) - calendar:datetime_to_gregorian_seconds(UTC).

+ 23 - 0
test/jsone_encode_tests.erl

@@ -94,6 +94,24 @@ encode_test_() ->
               ?assertEqual({ok, Expected}, jsone_encode:encode(Input))
               ?assertEqual({ok, Expected}, jsone_encode:encode(Input))
       end},
       end},
 
 
+     %% Strings variant: Datetimes
+     {"datetime: iso8601: utc",
+      fun () ->
+              ?assertEqual({ok, <<"\"2015-06-25T14:57:25Z\"">>}, jsone_encode:encode({{2015,6,25},{14,57,25}})),
+              ?assertEqual({ok, <<"\"2015-06-25T14:57:25Z\"">>}, jsone_encode:encode({{2015,6,25},{14,57,25}}, [{datetime_format, iso8601}])),
+              ?assertEqual({ok, <<"\"2015-06-25T14:57:25Z\"">>}, jsone_encode:encode({{2015,6,25},{14,57,25}}, [{datetime_format, {iso8601, utc}}]))
+      end},
+     {"datetime: iso8601: local",
+      fun () ->
+              ?assertMatch({ok, <<"\"2015-06-25T14:57:25",_:6/binary,"\"">>}, jsone_encode:encode({{2015,6,25},{14,57,25}}, [{datetime_format, {iso8601, local}}]))
+      end},
+     {"datetime: iso8601: timezone",
+      fun () ->
+              ?assertEqual({ok, <<"\"2015-06-25T14:57:25Z\"">>},      jsone_encode:encode({{2015,6,25},{14,57,25}}, [{datetime_format, {iso8601, 0}}])),
+              ?assertEqual({ok, <<"\"2015-06-25T14:57:25+00:01\"">>}, jsone_encode:encode({{2015,6,25},{14,57,25}}, [{datetime_format, {iso8601, 60}}])),
+              ?assertEqual({ok, <<"\"2015-06-25T14:57:25-00:01\"">>}, jsone_encode:encode({{2015,6,25},{14,57,25}}, [{datetime_format, {iso8601, -60}}]))
+      end},
+
      %% Arrays
      %% Arrays
      {"simple array",
      {"simple array",
       fun () ->
       fun () ->
@@ -154,6 +172,11 @@ encode_test_() ->
               ?assertEqual({ok, <<"{\"1\":2}">>}, jsone_encode:encode(#{1 => 2}, [{object_key_type, scalar}])), % OK
               ?assertEqual({ok, <<"{\"1\":2}">>}, jsone_encode:encode(#{1 => 2}, [{object_key_type, scalar}])), % OK
               ?assertEqual({ok, <<"{\"1\":2}">>}, jsone_encode:encode(#{1 => 2}, [{object_key_type, value}])),  % OK
               ?assertEqual({ok, <<"{\"1\":2}">>}, jsone_encode:encode(#{1 => 2}, [{object_key_type, value}])),  % OK
 
 
+              %% key: datetime
+              ?assertMatch({error, {badarg, _}},  jsone_encode:encode(#{{{2000,1,1}, {0,0,0}} => 2}, [{object_key_type, string}])), % NG
+              ?assertEqual({ok, <<"{\"2000-01-01T00:00:00Z\":2}">>}, jsone_encode:encode(#{{{2000,1,1}, {0,0,0}} => 2}, [{object_key_type, scalar}])), % OK
+              ?assertEqual({ok, <<"{\"2000-01-01T00:00:00Z\":2}">>}, jsone_encode:encode(#{{{2000,1,1}, {0,0,0}} => 2}, [{object_key_type, value}])),  % OK
+
               %% key: array
               %% key: array
               ?assertMatch({error, {badarg, _}},    jsone_encode:encode(#{[1] => 2}, [{object_key_type, string}])), % NG
               ?assertMatch({error, {badarg, _}},    jsone_encode:encode(#{[1] => 2}, [{object_key_type, string}])), % NG
               ?assertMatch({error, {badarg, _}},    jsone_encode:encode(#{[1] => 2}, [{object_key_type, scalar}])), % NG
               ?assertMatch({error, {badarg, _}},    jsone_encode:encode(#{[1] => 2}, [{object_key_type, scalar}])), % NG