11defmodule ValueFormatters do
22 use OK.Pipe
3- alias ValueFormatters.Cldr
3+
4+ # Allows instantiation of the module with preset options.
5+ defmacro __using__ ( module_opts ) do
6+ quote do
7+ def to_string ( value , format_definition , opts \\ [ ] ) do
8+ defaults =
9+ Keyword . get ( unquote ( module_opts ) , :defaults , % { } )
10+ |> Map . merge ( Keyword . get ( opts , :defaults , % { } ) )
11+
12+ opts =
13+ Keyword . put_new ( opts , :cldr , unquote ( module_opts [ :cldr ] ) )
14+ |> Keyword . put ( :defaults , defaults )
15+
16+ ValueFormatters . to_string ( value , format_definition , opts )
17+ end
18+ end
19+ end
420
521 @ date_display_options [ :none , :short , :medium , :long , :full ]
622 @ time_display_options [ :none , :short , :medium , :long , :full ]
723
8- def to_string ( value , format_definition , options \\ [ ] )
9-
10- def to_string ( nil , _format_definition , _options ) , do: { :ok , "" }
11- def to_string ( "" , _format_definition , _options ) , do: { :ok , "" }
24+ def to_string ( value , format_definition , opts \\ [ ] )
1225
13- # def to_string(true , _format_definition, _options ), do: {:ok, Cldr.Message.format("Yes") }
14- # def to_string(false , _format_definition, _options ), do: {:ok, Cldr.Message.format("No") }
26+ def to_string ( nil , _format_definition , _opts ) , do: { :ok , "" }
27+ def to_string ( "" , _format_definition , _opts ) , do: { :ok , "" }
1528
16- def to_string ( value , format_definition , options ) do
29+ def to_string ( value , format_definition , opts ) do
1730 format_definition =
1831 format_definition
1932 |> expand_format_definition ( value )
20- |> merge_with_defaults ( options )
33+ |> merge_with_defaults ( opts )
2134
2235 # do the formatting
2336 case format_definition [ "format" ] do
24- "number" -> format_number ( value , format_definition , options )
25- "string" -> format_string ( value , format_definition )
26- "date" -> format_date ( value , format_definition , options )
27- "date_relative" -> format_date_relative ( value , format_definition , options )
28- "coordinates" -> format_coordinates ( value , format_definition )
29- _ -> { :error , "Unsupported format #{ format_definition [ "format" ] } " }
37+ "number" ->
38+ format_number ( value , format_definition , opts )
39+
40+ "string" ->
41+ format_string ( value , format_definition )
42+
43+ "date" ->
44+ format_date ( value , format_definition , opts )
45+
46+ "date_relative" ->
47+ format_date_relative ( value , format_definition , opts )
48+
49+ "date_iso" ->
50+ format_date_iso ( value , format_definition , opts )
51+
52+ "date_unix" ->
53+ format_date_unix ( value , format_definition , opts )
54+
55+ "coordinates" ->
56+ format_coordinates ( value , format_definition , opts )
57+
58+ _ ->
59+ { :error , "Unsupported format #{ format_definition [ "format" ] } " }
3060 end
3161 |> handle_cldr_error ( )
3262 end
@@ -37,8 +67,7 @@ defmodule ValueFormatters do
3767 end
3868
3969 # In case of a shorthand formatDefinition, expand it
40- defp expand_format_definition ( format_definition , _value )
41- when format_definition in [ "number" , "string" , "date" , "date_relative" , "coordinates" ] do
70+ defp expand_format_definition ( format_definition , _value ) when is_binary ( format_definition ) do
4271 % { "format" => format_definition }
4372 end
4473
@@ -82,6 +111,17 @@ defmodule ValueFormatters do
82111 end
83112 end
84113
114+ defp cldr ( opts , mod_name ) do
115+ with { :ok , cldr } <- Keyword . fetch ( opts , :cldr ) do
116+ Module . concat ( cldr , mod_name )
117+ else
118+ :error ->
119+ raise ArgumentError ,
120+ message:
121+ "Attempted to access a Cldr module, but non was specified. Pass the :cldr option to to_string/3 or when instantiating the module."
122+ end
123+ end
124+
85125 defp format_number ( value , number_definition , opts ) when is_number ( value ) do
86126 precision = Map . get ( number_definition , "precision" )
87127 unit = Map . get ( number_definition , "unit" )
@@ -97,7 +137,10 @@ defmodule ValueFormatters do
97137 { value , precision }
98138 end
99139
100- Cldr.Number . to_string ( rounded_value , locale: get_locale ( opts ) , fractional_digits: precision )
140+ cldr ( opts , Number ) . to_string ( rounded_value ,
141+ locale: get_locale ( opts ) ,
142+ fractional_digits: precision
143+ )
101144 ~> append_unit ( unit , opts )
102145 end
103146
@@ -155,22 +198,60 @@ defmodule ValueFormatters do
155198
156199 # Value of type Time has to be formatted with Cldr.Time
157200 date_display == :none or is_time ( value ) ->
158- Cldr. Time. to_string ( value , format: time_display , locale: get_locale ( opts ) )
201+ cldr ( opts , Time ) . to_string ( value , format: time_display , locale: get_locale ( opts ) )
159202
160203 # Value of type Date has to be formatted with Cldr.Date
161204 time_display == :none or is_date ( value ) ->
162- Cldr. Date. to_string ( value , format: date_display , locale: get_locale ( opts ) )
205+ cldr ( opts , Date ) . to_string ( value , format: date_display , locale: get_locale ( opts ) )
163206
164207 # Covers DateTime and NaiveDateTime
165208 true ->
166- Cldr. DateTime. to_string ( value ,
209+ cldr ( opts , DateTime ) . to_string ( value ,
167210 date_format: date_display ,
168211 time_format: time_display ,
169212 locale: get_locale ( opts )
170213 )
171214 end
172- else
173- { :error , reason } -> { :error , reason }
215+ end
216+ end
217+
218+ defp format_date_iso ( value , date_definition , opts ) do
219+ with { :ok , value } <- pre_process_date_value ( value , date_definition , opts ) do
220+ case value do
221+ % Date { } ->
222+ { :ok , Date . to_iso8601 ( value ) }
223+
224+ % Time { } ->
225+ { :ok , Time . to_iso8601 ( value ) }
226+
227+ % DateTime { } ->
228+ { :ok , DateTime . to_iso8601 ( value ) }
229+
230+ _ ->
231+ { :error , "Invalid Date/Time value #{ value } " }
232+ end
233+ end
234+ end
235+
236+ defp format_date_unix ( value , date_definition , opts ) do
237+ with { :ok , value } <- pre_process_date_value ( value , date_definition , opts ) ,
238+ { :ok , value } <- ensure_unix_convertible ( value ) do
239+ milliseconds =
240+ Map . get ( date_definition , "milliseconds" , false )
241+
242+ precision = if milliseconds , do: :millisecond , else: :second
243+
244+ DateTime . to_unix ( value , precision )
245+ |> Integer . to_string ( )
246+ |> OK . wrap ( )
247+ end
248+ end
249+
250+ defp ensure_unix_convertible ( value ) do
251+ case value do
252+ % Date { } -> DateTime . new ( value , ~T[ 00:00:00] )
253+ % DateTime { } -> { :ok , value }
254+ _ -> { :error , "Value #{ inspect ( value ) } is not a Date or DateTime." }
174255 end
175256 end
176257
@@ -242,7 +323,7 @@ defmodule ValueFormatters do
242323 defp format_date_relative ( value , date_definition , opts ) do
243324 with { :ok , value } <- pre_process_date_value ( value , date_definition , opts ) do
244325 if not is_time ( value ) do
245- Cldr. DateTime.Relative. to_string ( value , locale: get_locale ( opts ) )
326+ cldr ( opts , DateTime.Relative ) . to_string ( value , locale: get_locale ( opts ) )
246327 else
247328 { :error , "Date part is required for relative date formatting." }
248329 end
@@ -251,7 +332,7 @@ defmodule ValueFormatters do
251332 end
252333 end
253334
254- defp format_coordinates ( value , coordinate_definition ) do
335+ defp format_coordinates ( value , coordinate_definition , opts ) do
255336 [ lat , lng , radius ] =
256337 case value do
257338 % { "lat" => lat , "lng" => lng , "radius" => radius } -> [ lat , lng , radius ]
@@ -261,11 +342,16 @@ defmodule ValueFormatters do
261342 end
262343
263344 with { :ok , lat_formatted } <-
264- format_number ( lat , % { "format" => "number" , "precision" => 5 } , [ ] ) ,
265- { :ok , lng_formatted } <- format_number ( lng , % { "format" => "number" , "precision" => 5 } , [ ] ) do
345+ format_number ( lat , % { "format" => "number" , "precision" => 5 } , opts ) ,
346+ { :ok , lng_formatted } <-
347+ format_number ( lng , % { "format" => "number" , "precision" => 5 } , opts ) do
266348 if get_in ( coordinate_definition , [ "radius_display" ] ) != false and radius != nil do
267349 with { :ok , radius_formatted } <-
268- format_number ( radius , % { "format" => "number" , "precision" => 0 , "unit" => "m" } , [ ] ) do
350+ format_number (
351+ radius ,
352+ % { "format" => "number" , "precision" => 0 , "unit" => "m" } ,
353+ opts
354+ ) do
269355 { :ok , "#{ lat_formatted } \u{00B0} , #{ lng_formatted } \u{00B0} , #{ radius_formatted } " }
270356 else
271357 { :error , reason } -> { :error , reason }
@@ -278,10 +364,9 @@ defmodule ValueFormatters do
278364 end
279365 end
280366
281- defp get_locale ( opts , default \\ nil ) do
367+ defp get_locale ( opts ) do
282368 Keyword . get ( opts , :locale ) ||
283- Process . get ( :locale ) ||
284- default
369+ Process . get ( :locale )
285370 end
286371
287372 defp get_timezone ( opts , default \\ nil ) do
0 commit comments