Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

01 - Up & Running

01-up-and-running.livemd

01 - Up & Running

Mix.install([
  :ex_cldr,
  :ex_cldr_dates_times,
  :ex_money
])

# define a CLDR backend module
defmodule DemoApp.Backend do
  use Cldr,
    locales: ["en", "en-GB", "de", "pt", "hi"],
    default_locale: "en",
    providers: [Cldr.Number, Cldr.Calendar, Cldr.DateTime]
end

# Set an app-wide default cldr backend
Application.put_env(:ex_cldr, :default_backend, DemoApp.Backend)

Dates & times

Let’s look at the timestamp of the first commit to the Elixir git repo at https://github.com/elixir-lang/elixir

> git log --reverse

commit 337c3f2d569a42ebd5fcab6fef18c5e012f9be5b
Author: José Valim 
Date:   Sun Jan 9 09:46:08 2011 +0100

    First commit.
first_commit = ~U[2011-01-09 08:46:08Z]
~U[2011-01-09 08:46:08Z]
# how long ago was that first commit made?

now = DateTime.utc_now()
Cldr.DateTime.Relative.to_string!(first_commit, relative_to: now)
"13 years ago"

Here we are dealing with the timestamps in a relative sense, i.e. as a duration. Without relying on the CLDR, this could have added a lot of complexity to our code. But luckily for us, ex_cldr reduces that complexity to a single call to Cldr.DateTime.Relative.to_string!/3.

Note that this same function works for any length of duration, both positive and negative:

durations_in_seconds = [
  -30,
  -300,
  -30_000,
  -300_000,
  -3_000_000,
  -30_000_000,
  -300_000_000,
  300_000_000
]

Enum.map(durations_in_seconds, fn duration ->
  Cldr.DateTime.Relative.to_string!(DateTime.add(now, duration, :second), relative_to: now)
end)
["30 seconds ago", "5 minutes ago", "8 hours ago", "3 days ago", "last month", "11 months ago",
 "10 years ago", "in 10 years"]

Later-on we’ll see how to display dates and times correctly for specific locales (eg. UK-english). But to display a timestamp in the default US-English is also a single function call.

# US-English medium length format

Cldr.DateTime.to_string!(first_commit)
"Jan 9, 2011, 8:46:08 AM"
# similarly, a default longer format is available

Cldr.DateTime.to_string!(first_commit, format: :long)
"January 9, 2011, 8:46:08 AM UTC"
# and also a default shorter format

Cldr.DateTime.to_string!(first_commit, format: :short)
"1/9/11, 8:46 AM"
# and we can use format strings when we want to have more control,
# see https://hexdocs.pm/ex_cldr_dates_times/readme.html#format-strings

Cldr.DateTime.to_string!(first_commit, format: "dd-MM-yyyy hh:mm")
"09-01-2011 08:46"
# The same approach that works for DateTime variables also extends to Date and Time variables
# respectively, e.g:

[Cldr.Date.to_string!(~D[2011-01-09]), Cldr.Time.to_string!(~T[08:46:08])]
["Jan 9, 2011", "8:46:08 AM"]

Numbers and currencies

In the interfaces that we build, it’s often useful to format numbers to make them more legible. We could do that the hard way, or we can let ex_cldr take care of it for us:

big_number = 91_825_808.102384

Cldr.Number.to_string!(big_number)
"91,825,808.102"
Cldr.Number.to_string!(big_number, round_nearest: 1_000_000)
"92,000,000"

And the same approach that works for regular numbers, also works for currencies. Here we use the ex_money package that builds on top of ex_cldr:

Money.new(:USD, "123.45")
|> Money.to_string!()
"$123.45"

Configuring locales

Now the magic happens.

We saw above how simple it is to display a dates or times in the default locale (en-US). Now, by just configuring a few more locales, and specifying which one we want, we can use exaclty the same code from above to display dates and times to individual users in the locale that they might expect.

In the Notebook dependencies and setup section at the top, you’ll notice that we added a few more interesting locales. We’ll configure this livebook’s Elixir process to each of those locales, to see how the variables in the examples above would be formatted.

We’ll start by wrapping some of the examples above in a function:

defmodule Helper do
  def variable_formatting_samples() do
    first_commit = ~U[2011-01-09 08:46:08Z]

    [
      Cldr.DateTime.Relative.to_string!(first_commit, relative_to: DateTime.utc_now()),
      Cldr.DateTime.to_string!(first_commit),
      Cldr.DateTime.to_string!(first_commit, format: :long),
      Cldr.DateTime.to_string!(first_commit, format: :short),
      Cldr.Date.to_string!(~D[2011-01-09]),
      Cldr.Time.to_string!(~T[08:46:08]),
      Cldr.Number.to_string!(91_825_808.102384, round_nearest: 1_000_000),
      Money.to_string!(Money.new(:USD, "123.45"))
    ]
  end
end
{:module, Helper, <<70, 79, 82, 49, 0, 0, 11, ...>>, {:variable_formatting_samples, 0}}
# Now, we configure the current Elixir process as it might be set up for a Hindi user:
Cldr.put_locale("hi")

# And just like that, all of the variables display exactly as the user would expect them to:
Helper.variable_formatting_samples()
["13 वर्ष पहले", "9 जन॰ 2011, 8:46:08 am",
 "9 जनवरी 2011, 8:46:08 am UTC", "9/1/11, 8:46 am", "9 जन॰ 2011", "8:46:08 am",
 "9,20,00,000", "$123.45"]
# similarly for German users:
Cldr.put_locale("de")

Helper.variable_formatting_samples()
["vor 13 Jahren", "09.01.2011, 08:46:08", "9. Januar 2011, 08:46:08 UTC", "09.01.11, 08:46",
 "09.01.2011", "08:46:08", "92.000.000", "12.345,00 $"]
# or (Brazilian) Portuguese:
Cldr.put_locale("pt")

Helper.variable_formatting_samples()
["há 13 anos", "9 de jan. de 2011 08:46:08", "9 de janeiro de 2011 08:46:08 UTC",
 "09/01/2011 08:46", "9 de jan. de 2011", "08:46:08", "92.000.000", "US$ 12.345,00"]
# Even for UK-English

Cldr.put_locale("en-GB")

Helper.variable_formatting_samples()
["13 years ago", "9 Jan 2011, 08:46:08", "9 January 2011, 08:46:08 UTC", "09/01/2011, 08:46",
 "9 Jan 2011", "08:46:08", "92,000,000", "US$123.45"]

Notice here that our code stayed exaclty the same for each of these users. The only thing that changed is that we specified a locale (for the current Elixir process) using CLDR.put_locale/1, and ex_cldr was able to display all of the variables in the expected format for each of the locales that was configured in the Notebook dependencies and setup section.