Introducing Calendar types

526 views
Skip to first unread message

José Valim

unread,
Mar 15, 2016, 8:28:31 AM3/15/16
to elixir-l...@googlegroups.com
Hello everyone,

I am glad to say we are introducing Calendar types into Elixir to make integration between all the different libraries simpler. You can see the PR here: https://github.com/elixir-lang/elixir/pull/4383. Feedback is welcome regarding the struct definitions.

In the following months, we will be augmenting those modules and adding some basic functions. Although expect the heavy work, specially if it comes to timezone support and more, to be done in the separated libraries.

Thank you!


José Valim
Skype: jv.ptec
Founder and Director of R&D

Onorio Catenacci

unread,
Mar 15, 2016, 8:42:27 AM3/15/16
to elixir-lang-core
Very nice improvement!  Thanks!

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4J56c%3DGxVgiSMH7Kd3K1ZYSvxMAj8H_qnWSwEbbf%3Dc_9w%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.



--

Paul Schoenfelder

unread,
Mar 15, 2016, 10:37:16 AM3/15/16
to elixir-l...@googlegroups.com
For those curious on what it means for libraries, as José mentioned, standard library functionality around dates and times will be pretty minimal for the near term, and probably for quite some time, so libraries like Timex and Calendar will be cutting over to use the new types, so that we're all sharing a single set of data types, and acting as extensions for the standard library, rather than as completely autonomous implementations, which should make interacting with date/time values from various libraries much more seamless. It's been a long time coming :)

Paul

Steve Domin

unread,
Mar 15, 2016, 1:33:01 PM3/15/16
to elixir-lang-core
This is really awesome, having a single set of data types will make things much much easier.

Ed W

unread,
Mar 15, 2016, 1:50:46 PM3/15/16
to elixir-l...@googlegroups.com
Although it's not been stated explicitly, can we assume that this will propagate to the next Ecto releases also?

Ed W

Jason Stiebs

unread,
Mar 17, 2016, 10:47:37 AM3/17/16
to elixir-l...@googlegroups.com
Love the choice to focus on the datatypes over trying to figure out a perfect date/time lib. :+1: 

Kip

unread,
Dec 18, 2016, 4:30:11 PM12/18/16
to elixir-lang-core, jose....@plataformatec.com.br
The introduction of the Calendar types and the %Calendar struct is really helpful. Its also helpful that the @behaviour doesn't enforce using the struct..

In several industries the idea of an annual calendar has either a fixed starting date or has a "day of week" starting date.  For example, a calendar may have a year defined as "starts on the last Friday of June" or "end of the last Tuesday in December".  Or more simply "Starts on July 1st".  Whilst it would be possible to define a Calendar module for each of the possible variations it quickly becomes impractical.  They are more akin to configuration to be applied in the context of a module that handles, for example, My.Calendar.FourFourFive or My.Calendar.FiscalYear.

Even the Calendar.ISO module in Elixir has some ambiguity since ISO 8601 defines an "ISO week number" (the week with the first Thursday in it) whereas a Gregorian calendar would assume that the first week starts with January 1st.  Maybe thats more a naming issue than configuration but configuration could also help here.

Therefore I wonder if the Calendar struct itself would benefit from having a `meta` element with default `nil` that Calendar authors could use for configuration information.  For my simple example, My.Calendar.FiscalYear might  use it to store `%{starts: {7, 1}}`.  `For My.Calendar.FourFourFive` is might be `%{starts: :last:, day_of_week: 5, month: 6}`

Benefits:
A Calendar strategy can be build for a class of calendar types commonly in use and configuration is passed around at the same time as the Calendar Module name

Downside:
Duplication of configuration in potentially lots if places. 

Perhaps there is another approach for managing configuration?  Passing around the Module name is of course cheap since its just an atom.  Arbitrary configuration potentially is much more overhead depending on the content.

José Valim

unread,
Dec 19, 2016, 3:32:16 AM12/19/16
to elixir-l...@googlegroups.com
Kip, I am not sure what would be the best way to design this system. Although the year starts on July 1st, we still only increase the year from Dec 31st -> Jan 1st, correct?

So if you concern is more about week numbers and similar, couldn't it solved by a set of auxiliary functions that receives datetimes instead of trying to build new datetimes?

José Valim
Skype: jv.ptec
Founder and Director of R&D

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-core+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/e51829a8-918c-489b-8a29-e85c7f1d79e5%40googlegroups.com.

Kip Cole

unread,
Dec 19, 2016, 4:21:38 AM12/19/16
to elixir-l...@googlegroups.com
José,

Although the year starts on July 1st, we still only increase the year from Dec 31st -> Jan 1st, correct?

is a good example, because it refers to `year` in two ways:  the `year` element of the struct, and the name given to a year.  If the year in a fiscal calendar starts on July1st 2016 then that year would, for Australia, be called the 2017 financial year because it ends on June 30 2017.  Similar idea in the US for a fiscal year starting on April 1st.  After all a {y, m, d} tuple is just one way of encoding the progress of days and years - not all calendars use it (such as the Balinese calendar, or the Mayan calendar).  However since many (most?) calendars in common use can be encoded in {y, m, d}  the encoding mechanism is fine.

This is even a small ambiguity in an ISO calendar since the question of “first day of the year” depends on whether you mean the first day of the first week, or the first day of January.  The standard is a little fuzzy on this topic and which is why I think it may have been preferable to call the default implementation Calendar.Gregorian since day 1 of week 1 is the same of day 1 of the year.

The second part depends I suppose on the planned evolution of the Calendar behaviour.  For example, the behaviour includes `last_day_of_month`.  For the current implementation the assumption is that the calendar is gregorian because the return is an integer day number.  But if your calendar starts on “first thursday of april” then the last day can only really be encoded as {m, y} in the gregorian calendar because the notion of a month in this calendar does not align with a gregorian month boundary.  I suppose that if the definition of `last_day_of_month` was changed to mean “number of days in a month” then this type of calendar could be implemented without change and still be compliant with the calendar behaviour.  But I would still need some way to define configuration.  How do I capture that my version of Calendar.FiscalYear starts on July 1st, and another one starts on April 1st.  All other rules are the same - just a different start date.

I think the idea of representing a date as a {y, m, d} is effectively an encoding mechanism that happens to be the same as the proleptic Gregorian calendar - a case which matches the calendar in use by a lot of people thereby making it convenient and easy and I’m not proposing any alternative to that.

Arguably the better encoding of an arbitrary day in time is simply an integer offset since some epoch - this is the approach taken by Reingold and Dershowitz and it makes date comparison and calculation much easier.  But of course its not a familiar encoding and it needs math to convert too/from any input/output.  And given the Calendar API is published probably not of interest to you and the team to change.  It would be an analogy to the Unix timestamp so not completely unreasonable I suppose (but it would need a different epoch than Jan 1 1970).

—Kip


You received this message because you are subscribed to a topic in the Google Groups "elixir-lang-core" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elixir-lang-core/KvJQaUcUlOk/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4JFDdtYXWPiHhTqRhpybS3T_D2_HjjEgjUKtzjnHcS2AA%40mail.gmail.com.

José Valim

unread,
Dec 19, 2016, 5:21:57 AM12/19/16
to elixir-l...@googlegroups.com

This is even a small ambiguity in an ISO calendar since the question of “first day of the year” depends on whether you mean the first day of the first week, or the first day of January.

ISO8601 defines three representations: Calendar date, ordinal date and week dates. We could explicitly document we are implementing the first format.

But if your calendar starts on “first thursday of april” then the last day can only really be encoded as {m, y} in the gregorian calendar because the notion of a month in this calendar does not align with a gregorian month boundary.  I suppose that if the definition of `last_day_of_month` was changed to mean “number of days in a month” then this type of calendar could be implemented without change and still be compliant with the calendar behaviour.

If you have a calendar where the first month starts on "first thursday of April" and finishes the first month on the "first wednesday of May" such calendar still has a notion of months. In a leap week calendar they would all have 28 days with the exception of a month with a leap week (which would have 35).

Writing the paragraph above also made it absolutely clear that you are indeed correct: days_in_month/2 is a better name than last_day_of_month/2. Since last_day_of_month/2 was not yet released (only in RCs), I will go ahead and rename it.

On the other hand, You could also have week-based calendars that have no notion of months and therefore last_day_of_month or days_in_month have no purpose. Those probably wouldn't be representable with Elixir's calendar types.
 
But I would still need some way to define configuration.  How do I capture that my version of Calendar.FiscalYear starts on July 1st, and another one starts on April 1st.  All other rules are the same - just a different start date.

Calendar modules are backends that power DateTime, Date, and NaiveDateTime. This means you could have a function such as:

FiscalYear.define(MyApp.AprilCalendar, starts_at: "foo", ends_at: "bar")

The above would define a module named MyApp.AprilCalendar that implements all of the Calendar behaviour functions. It works similarly to the metadata you have mentioned, except such parameters are defined at compilation time, rather than runtime.

However, it is still not clear if the above would be the preferred solution to the problem. I am still partially inclined to think having a FiscalYear module that works orthogonally to the ISO calendar would work best. In such approach, you would have:

FiscalYear.define(MyApp.AprilBased, starts_at: "foo", ends_at: "bar")

That would define a module mirroring many of the functions in Date/DateTime except they do fiscal year related manipulations. For example, you could have Date.beginning_of_next_month(date) and MyApp.AprilBased.beginning_of_next_month(date) and they would return different results. The benefit is that you keep the data representation the same and choose how you want to manipulate it.

Do you have any preferences?
 
I think the idea of representing a date as a {y, m, d} is effectively an encoding mechanism that happens to be the same as the proleptic Gregorian calendar - a case which matches the calendar in use by a lot of people thereby making it convenient and easy and I’m not proposing any alternative to that.

Yes, we have discussed both y/m/d and second based and the consensus was around {y, m, d} due to conversion cost and pattern matching benefits.

Thanks for the e-mails!

Wojtek Mach

unread,
Dec 19, 2016, 5:32:26 AM12/19/16
to elixir-l...@googlegroups.com
Pardon for a somewhat offtopic post and a shameless plug: as a joke for April 1st I've created an implementation of International Fixed Calendar: https://github.com/wojtekmach/calendar_ifc and I've recently updated it to use new calendar types - it might be of some interest to the people in this thread.

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-core+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
Wojtek Mach

Kip Cole

unread,
Dec 19, 2016, 7:48:40 AM12/19/16
to elixir-l...@googlegroups.com
Thanks for the food for thought José.  The Calendar.define() approach sounds much better than passing a meta payload around.  In some cases I can see compile time benefit, others cases would need runtime definition but that's workable since it still would be defined only once.

I'm going to finish up some code I've been working on and incorporate the Calendar.define() strategy (I'll experiment with both approaches) and see if that would be worth a PR down the road.
--
You received this message because you are subscribed to a topic in the Google Groups "elixir-lang-core" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elixir-lang-core/KvJQaUcUlOk/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elixir-lang-co...@googlegroups.com.

Kip

unread,
Dec 19, 2016, 4:53:25 PM12/19/16
to elixir-lang-core, jose....@plataformatec.com.br
José, let me just qualify the intent of the Calendar struct and its supporting cast of friends in Date, Time, ....  there seems to be assumptions in some areas that the date tuple {y, m, d} is always the same encoding of a date.  For example in Date.compare:

@spec compare(Calendar.date, Calendar.date) :: :lt | :eq | :gt def compare(date1, date2) do case {to_erl(date1), to_erl(date2)} do {first, second} when first > second -> :gt {first, second} when first < second -> :lt _ -> :eq end end This assumes that the date encoding has the same meaning across calendars and that the {y, m, d} are always ISO representations (or at least the same representation) (a) Is the design intent that all Calendars use the {y, m, d} to represent an ISO date and therefore internally convert when required? (b) Calendars are free to use {y, m, d} for different non-ISO encodings and therefore
Date.compare and others should be adjusted to be calendar agnostic?

To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.

José Valim

unread,
Dec 19, 2016, 5:33:04 PM12/19/16
to elixir-l...@googlegroups.com
It is option B.

If we don't delegate to the calendar implementation, we always hardcode to Calendar.ISO. This way users of other calendars get a hard failure and we can discuss how to best move forward. So if you look at the "to_erl" implementation, you will see that it only supports Calendar.ISO.

This means it is time to have the conversation. :) We have two options:

1. Make "to_erl" part of the calendar behaviour. However, since the Erlang representation is a Proleptic Gregorian calendar, to_erl would effectively be a conversion to gregorian.

2. Make date_compare and naive_date_time_compare part of the calendar behaviour.

One question you may know the answer: are calendar conversions bijective? For example, is a date in one calendar guaranteed to represent a single date in another calendar and vice-versa? I would think that yes, because of time, but never underestimate calendars. But if yes, we could convert dates to a known calendar (Proleptic Gregorian) and then compare them (solution 1). This would also allow comparisons between calendars (which again, requires them to be bijective).

Paul Schoenfelder

unread,
Dec 19, 2016, 5:51:47 PM12/19/16
to elixir-l...@googlegroups.com
I think a good approach would be to define a reference date, from which all calendars can use as a point to convert to and from. This method is used in the book Calendrical Calculations, and their associated Java/Scheme implementation called Calendrica (which includes Egyptian/Armenian, Gregorian, Julian, Coptic/Ethiopic, ISO, Islamic, Hebrew, Ecclesiastical, Hindu, Mayan, Balinese Pawukon, Persian, French Revolutionary, and Chinese calendars). It works by defining a "fixed" calendar, which starts with day 1, and then defining conversions to and from the "fixed" calendar by using the number of days relative to that calendar. In Calendrica, the start of "fixed" date 1 is the same as the start of the Gregorian (proleptic) calendar, e.g. midnight of January 1st, year 1, thus to_fixed({1,1,1}) == 1. Since the passage of time can be represented as days -1, 0, 1, 2, ..N relative to the fixed calendar, this provides a way to convert between any set of calendars, by simply converting to the fixed calendar, and then converting to the destination calendar. Time is similarly treated, as each calendar also defines the point in time at which a day "starts", so conversions are all unified to occur at noon. Since in many calendars this is based on location, it is up to the calendar implementation to convert to noon prior to doing conversions to another calendar. There is a great deal more that goes into it, but hopefully it at least sparks some discussion about how to approach this. In my opinion, it's the best approach I've come across so far (and I wouldn't be surprised if it's effectively the only workable approach).

Paul

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-core+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4J_wV%3DtzXdjUubrYpi_fgPOSa39eG0CAwa0y3F3oMvGGw%40mail.gmail.com.

Kip Cole

unread,
Dec 19, 2016, 8:14:18 PM12/19/16
to elixir-l...@googlegroups.com
José, Paul, thanks both.

José: option (b) would seem better to me as well - and it means my work is not (yet?) in vain :-)

Paul: I’m working on calendrical calculations for Elixir and want to be Calendar behaviour compatible.    I think some small and compatible adjustments to Date and DateTime/NaiveDateTime can allow all of the calendars in Calendrical Calculations to be developed in accordance with the behaviour except those that don’t reduce to a {y, m, d} tuple - like the  Balinese calendar.  Including Date.to_fixed() would be a necessary part of that as a way of allowing date comparisons and calendar conversions.  Which can be done with no performance impact to Calendar. ISO I believe.

With the guidance from José in this thread I can finish up development now and see if the community thinks its worth a PR.  Of course calendars beyond the standard ISO would be a separate package(s) and any changes will have to have the same or better performance profile and pass all current tests too.

Perhaps its a quirk, but I happen to really like calendar stuff - its the relationship to human history I think.


Oh, and I’m not proposing the 
You received this message because you are subscribed to a topic in the Google Groups "elixir-lang-core" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elixir-lang-core/KvJQaUcUlOk/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CAK%3D%2B-Tu9HxoYxnvc7Q-2cPgXLW3U1Y6edBv1yo%3DD0ar16Aqs6A%40mail.gmail.com.

José Valim

unread,
Dec 29, 2016, 12:03:45 PM12/29/16
to elixir-l...@googlegroups.com
> With the guidance from José in this thread I can finish up development now and see if the community thinks its worth a PR.  Of course calendars beyond the standard ISO would be a separate package(s) and any changes will have to have the same or better performance profile and pass all current tests too.

A bit delayed on my reply but I would love to see what you come up with. Let me know if there is anything else I can help with.



José Valim
Skype: jv.ptec
Founder and Director of R&D

On Tue, Dec 20, 2016 at 2:14 AM, Kip Cole <kipc...@gmail.com> wrote:
José, Paul, thanks both.

José: option (b) would seem better to me as well - and it means my work is not (yet?) in vain :-)

Paul: I’m working on calendrical calculations for Elixir and want to be Calendar behaviour compatible.    I think some small and compatible adjustments to Date and DateTime/NaiveDateTime can allow all of the calendars in Calendrical Calculations to be developed in accordance with the behaviour except those that don’t reduce to a {y, m, d} tuple - like the  Balinese calendar.  Including Date.to_fixed() would be a necessary part of that as a way of allowing date comparisons and calendar conversions.  Which can be done with no performance impact to Calendar. ISO I believe.

With the guidance from José in this thread I can finish up development now and see if the community thinks its worth a PR.  Of course calendars beyond the standard ISO would be a separate package(s) and any changes will have to have the same or better performance profile and pass all current tests too.

Perhaps its a quirk, but I happen to really like calendar stuff - its the relationship to human history I think.


Oh, and I’m not proposing the 
On 20 Dec 2016, at 9:51 AM, Paul Schoenfelder <paulscho...@gmail.com> wrote:

I think a good approach would be to define a reference date, from which all calendars can use as a point to convert to and from. This method is used in the book Calendrical Calculations, and their associated Java/Scheme implementation called Calendrica (which includes Egyptian/Armenian, Gregorian, Julian, Coptic/Ethiopic, ISO, Islamic, Hebrew, Ecclesiastical, Hindu, Mayan, Balinese Pawukon, Persian, French Revolutionary, and Chinese calendars). It works by defining a "fixed" calendar, which starts with day 1, and then defining conversions to and from the "fixed" calendar by using the number of days relative to that calendar. In Calendrica, the start of "fixed" date 1 is the same as the start of the Gregorian (proleptic) calendar, e.g. midnight of January 1st, year 1, thus to_fixed({1,1,1}) == 1. Since the passage of time can be represented as days -1, 0, 1, 2, ..N relative to the fixed calendar, this provides a way to convert between any set of calendars, by simply converting to the fixed calendar, and then converting to the destination calendar. Time is similarly treated, as each calendar also defines the point in time at which a day "starts", so conversions are all unified to occur at noon. Since in many calendars this is based on location, it is up to the calendar implementation to convert to noon prior to doing conversions to another calendar. There is a great deal more that goes into it, but hopefully it at least sparks some discussion about how to approach this. In my opinion, it's the best approach I've come across so far (and I wouldn't be surprised if it's effectively the only workable approach).

Paul
On Mon, Dec 19, 2016 at 4:32 PM, José Valim <jose.valim@plataformatec.com.br> wrote:
It is option B.

If we don't delegate to the calendar implementation, we always hardcode to Calendar.ISO. This way users of other calendars get a hard failure and we can discuss how to best move forward. So if you look at the "to_erl" implementation, you will see that it only supports Calendar.ISO.

This means it is time to have the conversation. :) We have two options:

1. Make "to_erl" part of the calendar behaviour. However, since the Erlang representation is a Proleptic Gregorian calendar, to_erl would effectively be a conversion to gregorian.

2. Make date_compare and naive_date_time_compare part of the calendar behaviour.

One question you may know the answer: are calendar conversions bijective? For example, is a date in one calendar guaranteed to represent a single date in another calendar and vice-versa? I would think that yes, because of time, but never underestimate calendars. But if yes, we could convert dates to a known calendar (Proleptic Gregorian) and then compare them (solution 1). This would also allow comparisons between calendars (which again, requires them to be bijective).


-- 
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-core+unsub...@googlegroups.com.


-- 
You received this message because you are subscribed to a topic in the Google Groups "elixir-lang-core" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elixir-lang-core/KvJQaUcUlOk/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elixir-lang-core+unsub...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-core+unsubscribe@googlegroups.com.

Kip Cole

unread,
Jan 1, 2017, 5:06:08 AM1/1/17
to elixir-l...@googlegroups.com
José, it seems appropriate to post a PR for Calendar on the first day of the year :-)  https://github.com/elixir-lang/elixir/pull/5603

This embodies the idea of a canonical integer date representation.  I have used the principles embodied in Calendrical Calculations by Dershowitz and Rheingold since I think that provides a very solid and well-proven basis for calendar creators.  I added a Calendar.Julian as one example.

I took the opportunity to add support for DateRange and the Enum protocol, a Date.diff/2 function and the standard set of kday functions.

Happy New Year to all.

To unsubscribe from this group and all its topics, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4JBze9basD93GwzUtfAeeiV1uaozC_wMeJm1fG4ChU7hg%40mail.gmail.com.
Reply all
Reply to author
Forward
0 new messages