elixir-cldr / cldr_calendars

Calendar functions for CLDR
Other
15 stars 6 forks source link

[Question] Calendar with Sunday as first day of week #4

Closed dbernheisel closed 4 years ago

dbernheisel commented 4 years ago

This may be more of a "how to use" question than an issue.

In the US, the default calendar starts with the first day of the week as Sunday. How do I use ex_cldr and ex_cldr_calendars to localize the calendar for US users to have the day of the week reflected with Sunday = 1?

The supplemental CLDR data for the territory US has a preference for this, but I'm not sure that's being used or parsed. Since it's territory-specific, I'm not sure if this belongs here or in ex_cldr_territories

https://unicode.org/cldr/charts/latest/supplemental/territory_information.html#US http://demo.icu-project.org/icu-bin/locexp?d_=en&_=en_US

image

Ultimately, I'd like to be able to put a locale in as "en-Latn-US", and have the preferred calendar become something like Cldr.Calendar.EnLatnUS, or a way to provide a mapping of locales to pre-defined calenadrs (defined either by Cldr or by the application).

The closest I can get so far is ordering Sunday first in the list, but the day_of_week still reflects 7 as Sunday instead of 1.

defmodule MyApp.Calendar.US do
  @moduledoc """
  This is the same as a gregorian Calendar, but with Sunday starting the week.
  """

  use Cldr.Calendar.Base.Month, day_of_week: Cldr.Calendar.sunday(), cldr_backend: MyApp.Cldr
end

# ...

iex(1)> Cldr.Calendar.localize(~D[2020-04-05 MyApp.Calendar.US], :days_of_week)
[
  {7, "Sun"},
  {1, "Mon"},
  {2, "Tue"},
  {3, "Wed"},
  {4, "Thu"},
  {5, "Fri"},
  {6, "Sat"}
]

I see that this is straight from the Cldr data; so the missing link (I'm assuming) is the supplemental data.

kipcole9 commented 4 years ago

@dbernheisel thanks for the issue. I need to think on this a little but I can clarify one thing: day_of_week is always 1..7 where 1 is Monday. Its an internal representation of the calendar day of week (ie not localised).

There's one bug I will fix, one question I have and one suggestion: :-)

  1. Cldr.Calendar.new/3 currently takes a :locale option and is intended to return a calendar configured for that locale. It is not honouring the first_day_in_week preference. Thats a bug and I'll fix it. This will at least give you the right calendar with minimal effort.

  2. The second part depends on your use case I think. Can you describe more what you are trying to do that isn't working as expected? Using the ordinal number of the day of the week isn't that common since its an internal representation. You may find Cldr.Calendar.Interval.week/{1, 2} to be of some help since it returns a date range of days for a week. For example:

    
    # With year and week
    iex> Cldr.Calendar.Interval.week 2020, 1, MyApp.Calendar.US                                       
    #DateRange<~D[2020-01-05 MyApp.Calendar.US], ~D[2020-01-11 MyApp.Calendar.US]>
    iex> Cldr.Calendar.day_of_week ~D[2020-01-05 MyApp.Calendar.US]
    7

With a date

iex> Cldr.Calendar.Interval.week ~D[2020-04-05 MyApp.Calendar.US]

DateRange<~D[2020-04-05 MyApp.Calendar.US], ~D[2020-04-11 MyApp.Calendar.US]>

iex> Cldr.Calendar.day_of_week ~D[2020-04-05 MyApp.Calendar.US]
7



3. If you're doing calendar formatting then you might get some ideas from [ex_cldr_calendars_format](https://github.com/elixir-cldr/cldr_calendars_format) which does formatting in a calendar-specific way including putting the right first day of week in the right place. I just pushed a small update to fix a bug, but also added a test with your calendar to illustrate.

An example of output for April 2020 with your calendar:

### April 2020

Sun | Mon | Tue | Wed | Thu | Fri | Sat
 :---:  |  :---:  |  :---:  |  :---:  |  :---:  |  :---:  |  :---: 
29 | 30 | 31 | **1** | **2** | **3** | **4**
**5** | **6** | **7** | **8** | **9** | **10** | **11**
**12** | **13** | **14** | **15** | **16** | **17** | **18**
**19** | **20** | **21** | **22** | **23** | **24** | **25**
**26** | **27** | **28** | **29** | **30** | 1 | 2
3 | 4 | 5 | 6 | 7 | 8 | 9
dbernheisel commented 4 years ago

Ah perfect that's what I was essentially looking for, but building my own HTML formatter without ex_cldr_calendars_format.

Here's what I have right now:

# ./config/config.exs
config :ex_cldr,
  default_backend: MyAppWeb.Cldr,
  default_locale: "en",
  json_library: Jason

# ./lib/my_app_web/cldr.ex
defmodule MyAppWeb.Cldr do
  use Cldr,
    otp_app: :my_app,
    gettext: MyAppWeb.Gettext,
    providers: [Cldr.Calendar, Cldr.DateTime, Cldr.Number, Cldr.Unit, Cldr.List, Cldr.Territory]
end

# ./lib/my_app_web/cldr_calendars.ex
defmodule MyAppWeb.Calendar.US do
  @moduledoc """
  This is the same as a regular Calendar, but with Sunday starting the week.
  """

  use Cldr.Calendar.Base.Month,
    month_of_year: 1,
    min_days_in_first_week: 1,
    day_of_week: Cldr.Calendar.sunday(),
    cldr_backend: MyAppWeb.Cldr

end

# Usage in iex to test
iex> date = ~D[2020-04-05 MyAppWeb.Calendar.US]
~D[2020-04-05 MyAppWeb.Calendar.US]

iex> Cldr.Calendar.localize(date, :days_of_week)
[
  {7, "Sun"},
  {1, "Mon"},
  {2, "Tue"},
  {3, "Wed"},
  {4, "Thu"},
  {5, "Fri"},
  {6, "Sat"}
]

iex> Date.day_of_week(date)
7

But I think Date.day_of_week(date) is supposed to be 1 for this US-based calendar. April 5th 2020 is on a Sunday, which is the first day of the week for the US calendar. first_day_of_the_week sounds like the right config to control this behaviour-- is there an option to set this preference?

Looking at Month.day_of_week I see that it ends up calling Elixir.Calendar.ISO.day_of_week where the number is generated. I'm probably misunderstanding something, but would this be where my US-based calendar would need to alter the logic?

I'm essentially trying to build what you have in your example from the ex_cldr_calendars_format above, but I'm currently using Date.day_of_week(date) to determine the position in the calendar rows/columns. I know I could hard-code it, but my hope is to offload this to the Calendar.

But-- now knowing about ex_cldr_calendars_format-- I may just refactor and use your library. Another example of awesome.

kipcole9 commented 4 years ago

I'd be very happy to collaborate on improving ex_cldr_calendars_format - having not looked at the code for a while and revisiting it this morning (my time) I think its pretty solid. You just need to implement a formatter module if you want a formatter different from the ones included (html and markdown.

Date.day_of_week/1 will always return 1..7 where 1 is Monday. Its the day - whereas I think you're referring to the ordinal day of the week which might be a useful function. But really not necessary to generate calendars I think.

Any and all calendars that are based on the Gregorian calendar (which is most but not all of them) delegate to Calendar.ISO where possible. Since all dates have the same meaning in Gregorian calendars (unlike months, weeks and years) we can use Calendar.ISO for individual date functions.

I think its cleaner to use Cldr.Calendar.Interval to generate the week ranges in any case. Thats its job :-)

kipcole9 commented 4 years ago

Let me know too if you are looking to put events on a calendar. I don't have that functionality but I'm definitely interested to implement it. You would pass a map of event => date or date range or list of dates and the formatter would include them appropriately.

dbernheisel commented 4 years ago

Aha thanks it's my misunderstanding between "ordinal day of week" vs "day of week". Yes I'm developing a mechanism for displaying events on a calendar; I'd love to contribute upstream to ex_cldr_calendar_format after I find some good patterns.

I'll close since you've fully answered the question, and then some!

kipcole9 commented 4 years ago

I have opened an issue on ex_cldr_calendars_format to continue the discussion.

kipcole9 commented 4 years ago

And on a final note ..... as of this commit ex_cldr_calendars now includes Cldr.Calendar.from_locale/2 which will create (or return) a calendar correctly configured for a locale. It's only on GitHub for now - it will be released on hex with a new version of ex_cldr coming with CLDR version 37 later this month.

In addition, the correct defaults for min_days and first_day_of_week are now applied in all cases when creating a calendar.