josevalim
Creator of Elixir
How to support multiple week calendars in Elixir?
@kip said:
And then @josevalim said:
Which @kip finally replied:
I will continue the discussion below.
Trending in Discussions
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
New
@chrismccord : I just saw the Extract AGENTS.md from Phoenix.new into phx.new generator commit to the phoenix project.
My initial shotgu...
New
I was working on an Ecto migration and I needed a timestamp. So, for the nth time, I looked up the different data types for timestamps, a...
New
Just a general thread to post chat/news/info relating to AI/ML stuff that may be relevant for Nx now or in the future. Got anything to sh...
New
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
Fly’s CEO posted this recently - Turn And Face The Strange · The Fly Blog
It says that Fly is going all-in on sprites, which is a worry ...
New
Other Trending Topics
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve.
They are GUI (Emerge) and State management (S...
New
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
@hugobarauna and I (Alex Koutmos) have been hard at work on writing a book on Nerves that takes you from simply blinking LEDs to building...
New
We want to introduce a new native datatype to Erlang: native records. Although replacing all tuple records with native records is not our...
New
Chat & Discussions>Discussions
Latest on Elixir Forum
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #hex
- #performance










First 10 of 15 Posts
josevalim
That’s a good point. I can think of cases this would be an issue but I can’t come up with anything concrete. Do you have any? Shall we explore this a bit further?
Here is a slightly more complete proposal. Imagine you have a data-structure called
%WeekDay{}. It has fields such as year, week, and day of week. It also has a “week_calendar” that knows how to convert it to iso days so you can convert it back to a Date or even a DateTime with timezone (you can embed timezones on the Weekday data structure if you want to or even have NaiveWeekDay without tz and WeekDay with tz).Does this make your life better or worse? I guess though what you are implementing here is exactly a
Date*, except you are using the month as week and the day as day of week. Although it may still be worth exploring if it makes computations easier or harder.They all represent the same point in time but they are different representations. In your case, they are the same representation. So I guess my point here is that it is weird to change calendars when they have the exact same underlying representation.
kip
José, I appreciate the opportunity to work this through, it’s been vexing me for a while.
Each calendar as a concrete implementation (reasoning)
Convertability
One of the reasons I came to the idea that each variation is its own calendar is because conversion between calendars becomes straight forward using the existing
Calendarmodule functions.As an example, lets say I’m using a
445calendar. It has exactly the same notion ofyear,monthandday. Except that the start/end of the year is a different Gregorian date and the month numbers don’t align either. Neither do the week days. For financial reporting purposes I would use my445calendar. But of course I also want to know what those periods are in the Gregorian/ISO calendar. Usingdate_to_iso_days/1andiso_days_to_dateI can convert between the two in a very straight forward manner. I don’t need to use another api to manage representations.Encapsulation
As we discussed earlier, but keeping all the calendar configuration in the relevant
Calendarmodule, a calendar now easily conforms to the standardDate,TimeandDateTimestructs which is in line with a developers expectations. Although the date/time/datetime functions only require structural compatibility, i think the developer experience is simplified if the principle ofone module == one calendaris kept.Implementation details
No doubt that there is plenty of room to optimise implementation for several classes of calendars, including
445style, and calendars that have differentmin_daysandfirst_day_of_week.My thinking was something like this:
Configuration
By no means the exact API but it will be reasonably straight forward to encapsulate the generalised version of these typical calendar types in a concrete fashion.
Module generation
One additional challenge with this approach is runtime configuration. As one example, a
locale_stringcan actually specify the desired calendar as defined in BCP47. So assume we have anaccept-languageheader with alanguage-tagit might look like this:Represents Hebrew as spoken in Israel, using the traditional Hebrew calendar, and in the “Asia/Jerusalem” time zone as identified in the tz database.
The
ca=hebrewis the calendar configuration and it could beretailorciscoor whatever is interpreted by the serving system. In this case, a module needs to exist and it might need to be generated at runtime. I am not sure the optimal way to do this.josevalim
Interesting! So the representation that you are keeping in Date/Time is actually the year of week and month of week? The other question is: what would those calendars return for
week_of_year? Just the information in the struct?EDIT: Meanwhile I have removed
week_of_yearfrom the Calendar callbacks because I am really uncertain it is the best way to go. If using calendars is the best way to model week-based calendars, then havingweek_of_yearis like havingjapanese_eraas a Calendar callback.I do like this.
Qqwy
I haven’t had the time yet to read through this discussion in much detail, but I do want to say that I really like that we are having this discussion
!
I will post an in-depth post later today or tomorrow.
kip
I’ve gone backwards and forwards on this and concluded that for any given
Calendar, theyear,monthanddayare that calendars representation. This fits for Gregorian derivatives (like445, or US fiscal) since the notion ofyear,monthanddayremain but are offset from the Gregorian.Where it is uncomfortable is for week-based calendars, like
ISOWeek. In this case I usemonthto meanweek.yearanddayretail their normal meaning but within theISOWeekcontext. Its not perfect but its workable.Every calendar we have discussed has the concept of a
week(French Revolutionary has 10 days, but there rest, afair are 7 days). Maybe I’m an outlier on this. I don’t seeweek_of_yearas valuable to only week-based calendars like ISOWeek.For Gregorian-based calendars, the
first_dayandmin_daysdetermine the start of the weekly cycle. Weeks of year (and also quarter and month) are used quite a lot for scheduling and reporting (“.. my forecast for week 11 of quarter 3 is …”). I hope there may be a case to return the callback, but clearly it can be implemented outside of the behaviour.josevalim
Why not use the week-based month here too? Since that is also specified by ISO? I am mostly curious.
kip
Mostly because the standard for ISOWeek says:
Qqwy
I finally had time to catch up with the discussion.
In the earlier discussion we had when talking about implementing the calendar behaviour, I noted that the concept of a week is orthogonal to the concept of days/months ❦, and arrived at the same reasoning as we did here, that when calculating with weeks, this could be modeled by a separate calendar that uses the
month-field to store the week number.If I remember correctly, this was not done at that time and we ended up including
day_of_weekin the Calendar behaviour; the reasoning being that it was common enough for people to want to look up the week to include it in the calendar directly.I think this reasoning is still valid, although I do not think that we should make the
Calendarbehaviour itself more complex because of it.Currently, I like the
%WeekDay{}-approach the best; I think it might look as follows:WeekCalendar, which mainly works with%WeekDay{}(or maybe%WeekDate{}/%WeekDateTime{}-structs?)WeekCalendarandCalendarbehaviours (maybe we can extract this to a separate behaviour?)Calendarand aWeekCalendaris possible using thedate_to_iso_days-helper in the middle.Calendarto a compatibleWeekCalendar.day_of_week, and of course play a role in the new formatter-proposal we’re working on in a similar fashion.I see the main advantage to introducing a new behaviour as twofold:
:monthand:week, week-based calendars do not have to think about how the months work (and only implement behaviour callbacks that make sense for week-related datastructures).:week-field rather than a:month-field as well.❦ Side note: Calendar-weeks are often based on a religious background, although there is research that suggests that there definitely is psychological merit for humans to having a work-leisure cycle of ~7-8 days. In any case, the relation between weeks and ‘months’ for any given calendar is either ‘very loose’ (when trying to evenly subdivide the synodic lunar month of ~29.5 days), or completely non-existent. @kip 7 day-weeks are the most common, but there are:
kip
Its been a while but I’m getting reasonable close to releasing ex_cldr_calendars. The focus has been on gregorian-based calendars that follow the gregorian month cycle with a variable starting month as well as week-based calendars that have a variable start or end day and a configurable week.
After a number of design experiments I am settled on an approach where a calendar module can be defined statically such as the calendar that is defined by the US National Retail Federation. Its quite simple:
Which basically means the calendar year ends on the last saturday nearest the end of January. An example of a financial year calendar for a US corporation that elects to have its financial year finish on the last Saturday in July would be:
An example of a month-based calendar would be a financial year calendar for a country. In the US the government financial year starts in October so it is defined by:
(Australia would be month 7, the UK would be month 4).
Calendars can be generated at runtime by passing the configuration to
Cldr.Calendar.get_or_create(calendar, config)or toCldr.Calendar.get_or_create_for(territory)which will create a financial year calendar module configured for a given territory (country).Thoughts on the Calendar behaviour
As I’ve gone through this process I have asked myself what I would want to add to the current
Calendarbehaviour and the answer is: very little.I would like to propose that the
Inspectprotocol implementation delegate to aninspect/2function on a calendar. This would be very useful when inspecting week-based calendars, or when using (as I do) asigil_d/2to define dates in different calendars. For an ISOWeek calendar for example, it would - in my implementation - return~d[2019-W12-02]ISOWeekwhich is a lot more meaningful that the struct dump - especially since for week based calendars one has to hijack themonthfield and use it instead asweek.I would like to propose revising the
Calendar.week_of_year/1addition to theCalendarbehaviour. Weeks are such a standard part of most modern calendars, including the ISO calendar. The behaviour today nearly every other date facet: era, year, quarter, month, day. Week is the notable exception. Reporting data based upon weeks is an intrinsic part of business activities; they are how we structure formatted calendars.These are really the only two behaviour additions that after a few months of calendar hacking strike me as additions I’d like to see - which is a testament to the original design and the recent 1.8 additions.
Cldr.Calendar behaviour builds on the core Calendar
In my own
Cldr.Calendarbehaviour I definitely have other requirements that I don’t believe are relevant to the coreCalendarmodule but may be of interest to other calendarises:year/1,quarter/1,month/1,week/1that return aDate.Rangeweek_in_year/3that takes a date,:first_dayand:min_daysper the earlier discussioniso_week_in_year/1that delegates toweek_in_year/3withfirst_day: 1,min_days: 4first_gregorian_day_in_year/2andlast_gregorian_day_in_year/2to return what it saysI don’t see any of these belonging in the core
Calendarmodule but they are very useful in the practical use of calendar building and calendar usage.I still have to add spec, docs and more tests so
cldr_calendarsis definitely not ready for anyone but it will be in a couple of weeks or so. Then I can finally finish up version 2.0 ofex_cldr_dates_timeswhich only has to focus on formatting (think of it as an CLDR-based formatting using the LDML formats in a localised context).josevalim
Fantastic work @kip!
Yes, please do send a PR!
We added it to Elixir but removed it before v1.8 was released exactly because the understanding of the “week of year” is locale specific. The only other functionality that I believe to be locale specific in the current calendar is
day_of_week. So it felt awkward to addweek_of_yeargiven that it would be limited in many cases.However, my understanding is that week_of_year would help your CLDR because calendars will now be able to rely on this uniform callback. Is this correct?
Can those be implemented using the existing Calendar callbacks? Those look nice additions to Elixir but I think we would need to add at least
days_in_yearanddays_in_week.