[Phoenix LiveView] 현지 시간대로 날짜/시간 형식 지정

사용자의 시간대를 사용하여 날짜/시간 형식을 지정하고 싶었습니다. 여기 내가 배운 것이 있습니다.

시간대 및 로케일을 가져오는 중



브라우저 쪽



초기 렌더링 직후에 클라이언트-서버 연결이 설정될 때 LiveView 프로세스에서 수신할 수 있도록 브라우저에서 현지 시간대 및 로케일에 대한 정보를 가져와 라이브 소켓 매개변수에 포함하려고 합니다.

다음은 몇 가지 편리한 기능입니다.


/assets/js/app.js 에서 라이브 소켓 매개변수에 키-값 쌍을 간단히 추가할 수 있습니다. 그러면 클라이언트가 LiveView에 연결될 때 액세스할 수 있습니다.

-let liveSocket = new LiveSocket('/live', Socket, { params: { _csrf_token: csrfToken } });
+let liveSocket = new LiveSocket('/live', Socket, {
+  params: {
+    _csrf_token: csrfToken,
+    locale: Intl.NumberFormat().resolvedOptions().locale,
+    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
+    timezone_offset: -(new Date().getTimezoneOffset() / 60),
+  },
+});



[info] CONNECTED TO Phoenix.LiveView.Socket in 112µs
  Transport: :websocket
  Serializer: Phoenix.Socket.V2.JSONSerializer
  Parameters: %{"_csrf_token" => "Ay8cCDsHZCFYBicSKTMHfi5EIjowK3sJHWHrqqVH4hcboKI8a1v_wB4g",
                "_mounts" => "0",
                "_track_static" => %{"0" => "http://localhost:4000/css/app.css",
                                     "1" => "http://localhost:4000/js/app.js"},
                "locale" => "en-US",
                "timezone" => "America/New_York",
                "timezone_offset" => "-5",
                "vsn" => "2.0.0"}


서버측(라이브뷰)



이제 소켓 매개변수에서 시간대와 로케일을 가져오려고 합니다. 한 가지 중요한 것은 소켓 매개변수가 only remain available during mount 이라는 것입니다. 또한 초기 렌더링 시점에는 브라우저에서 파생된 정보를 사용할 수 없기 때문에 기본값을 제공해야 합니다.

defmodule MnishiguchiWeb.TimezoneLive do
  use MnishiguchiWeb, :live_view

  @default_locale "en"
  @default_timezone "UTC"
  @default_timezone_offset 0

  @impl true
  def mount(_params, _session, socket) do
    socket =
      socket
      |> assign_locale()
      |> assign_timezone()
      |> assign_timezone_offset()

    {:ok, socket}
  end

  defp assign_locale(socket) do
    locale = get_connect_params(socket)["locale"] || @default_locale
    assign(socket, locale: locale)
  end

  defp assign_timezone(socket) do
    timezone = get_connect_params(socket)["timezone"] || @default_timezone
    assign(socket, timezone: timezone)
  end

  defp assign_timezone_offset(socket) do
    timezone_offset = get_connect_params(socket)["timezone_offset"] || @default_timezone_offset
    assign(socket, timezone_offset: timezone_offset)
  end

  ...


날짜/시간 형식화



시간대와 로케일이 LiveView 프로세스에 저장되면 datetime 값을 사용자에게 친숙한 형식으로 변환하려고 합니다. 이를 위해 두 개의 편리한 라이브러리가 있습니다.

  • Timex - :tzdata 패키지
  • 를 통해 전체 시간대를 지원하는 Elixir 프로젝트를 위한 풍부하고 포괄적인 날짜/시간 라이브러리

  • Cldr - Unicode Consortium’s Common Locale Data Repository (CLDR)을 위한 Elixir 라이브러리

  • 다음 라이브러리를 mix.exs에 추가합니다.

       defp deps do
         [
           ...
    +      {:timex, "~> 3.6"},
    +      {:ex_cldr_dates_times, "~> 2.0"},
           ...
         ]
       end
    


    그런 다음 mix deps.get를 실행합니다.

    Cldr library documentation 에 따르면 다음과 같이 모듈을 생성하기만 하면 됩니다.

    defmodule Mnishiguchi.Cldr do
      @default_locale "en"
      @default_timezone "UTC"
      @default_format :long
    
      use Cldr,
        locales: ["en", "ja"],
        default_locale: @default_locale,
        providers: [Cldr.Number, Cldr.Calendar, Cldr.DateTime]
    
      @doc """
      Formats datatime based on specified options.
    
      ## Examples
    
          iex> format_time(~U[2021-03-02 22:05:28Z], locale: "ja", timezone: "Asia/Tokyo")
          "2021年3月3日 7:05:28 JST"
    
          iex> format_time(~U[2021-03-02 22:05:28Z], locale: "ja", timezone: "America/New_York")
          "2021年3月2日 17:05:28 EST"
    
          iex> format_time(~U[2021-03-02 22:05:28Z], locale: "en-US", timezone: "America/New_York")
          "March 2, 2021 at 5:05:28 PM EST"
    
          # Fallback to ISO8601 string.
          iex> format_time(~U[2021-03-02 22:05:28Z], timezone: "Hello")
          "2021-03-02T22:05:28+00:00"
    
      """
      @spec format_time(DateTime.t(), nil | list | map) :: binary
      def format_time(datetime, options \\ []) do
        locale = options[:locale] || @default_locale
        timezone = options[:timezone] || @default_timezone
        format = options[:format] || @default_format
        cldr_options = [locale: locale, format: format]
    
        with time_in_tz <- Timex.Timezone.convert(datetime, timezone),
             {:ok, formatted_time} <- __MODULE__.DateTime.to_string(time_in_tz, cldr_options) do
          formatted_time
        else
          {:error, _reason} ->
            Timex.format!(datetime, "{ISO:Extended}")
        end
      end
    


    그러면 어디에서나 format_time 기능을 수행할 수 있습니다.

    로딩 아이콘 추가



    이것은 기술적으로 선택 사항이지만 초기 렌더링 당시 브라우저의 정보를 모르기 때문에 기본값으로 대체해야 합니다. 따라서 사용자는 연결이 설정되는 즉시 기본 형식에서 로컬 형식으로 변경되는 시간 형식의 이상한 효과를 보게 됩니다.

    This guy uses a different approach 이 문제에 대해 설명하지만 LiveView가 연결될 때까지 콘텐츠를 숨기기로 선택했습니다. 멋진 로딩 아이콘을 표시하여 UI가 여전히 자연스럽게 보일 것이라고 믿습니다. Single Element CSS Spinners 라이브러리가 편리하다는 것을 알았습니다.

    <%= unless connected?(@socket) do %>
      <div style="min-height:90vh">
        <div class="loader">Loading...</div>
      </div>
    <% else %>
    
      <!-- contents -->
    
    <% end %>
    


    자원


  • Phoenix.LiveView connected?/1
  • Phoenix.LiveView get_connect_params/1

  • Managing browser timezones to display dates with Phoenix Live View by Alex-Min

  • Single Element CSS Spinners by lukehaas
  • Determine a user's timezone | Stackoverflow
  • elixir-cldr/cldr_dates_times
  • bitwalker/timex
  • 좋은 웹페이지 즐겨찾기