Jalaali (Shamsi) calendar for elixir (Persian calendar)

Hey everyone

i’ve developed a library for Jalaali calendar for elixir which supports converting Gregorian dates to Jalaali and vice versa.

Jalaali is also known as Persian calendar or Shamsi calendar and is widely used in Persia and Afghanistan.

its available in hex with name of :jalaali

its pretty easy to use

iex> jalaali_dat = Jalaali.to_jalaali(DateTime.utc_now)

and…

iex> Jalaali.to_gregorian(jalaali_date)

hope you guys enjoy using it (if you ever need :smile:) ,
Thanks

13 Likes

Thank you for the package!

Btw, Elixir calendar types supports custom calendars. By default we use Calendar.ISO but there is a field on every struct called :calendar which you could store your custom calendar and it would be invoked when appropriate by Elixir.

I would love to know if you tried implementing the Jalaali on top of Elixir date types. I will be glad to answer any question you may have about the process or fix any bug you spot.

8 Likes

Hey Jose

First of all i wanted to thank you for creating elixir and maintaining it. You are really active and passionate about it.
I just began learning elixir in about 20 days and i am absolutely in love with it and its community, where every one is trying to do their best to keep it perfect.

Unfortunately i did not know that i can change :calendar in Date for localization. shame on me i’m a bit lazy with reading docs! :disappointed_relieved:

But i will fix that and make it to work as a Calendar.
I will let you know about the process and if i got to anything i couldn’t solve i will ask for your help

Thank you for your support you just made me get more and more excited about Elixir. :blue_heart:

P.S: Did i forgot to say that “YOU ARE AWESOME”?

8 Likes

Hello again,

I’ve been reading docs of Calendar on Elixir. and i have to say, i don’t get how to use it for calendar conversion! I also didn’t find any third party implementation of calendars that uses :calendar.

The Calendar behaviour does not provide any callbacks that forces every calendar implementation to provide a conversion to an ISO date or Unix Epoch. (does it?)

Maybe i didn’t get it!

i also have implemented Jalaali.Calendar (Based on Calendar.ISO) in here: https://github.com/alisinabh/elixir-jalaali/blob/calimpl/lib/jalaali/calendar.ex

What am i doing wrong? @josevalim

Thank you

1 Like

@alisinabh since you are the first person doing the integration, if there is a bug, it is very likely on our side. :slight_smile:

The Calendar callbacks are only for existing Date, NaiveDateTime and DateTime. There are no functions in Date, NaiveDateTime and DateTime for creating custom dates in other calendars. So my advice would be for you to create a module Jallali with functions such as:

Jallali.date(year, month, date)
Jallali.utc_naive_datetime()
...

And so on and make those return a Date, NaiveDateTime, etc with the calendar field set to Jallali.Calendar.

Does this make things a bit clearer? :slight_smile:

3 Likes

Thank you @josevalim for your answer. :slight_smile:

And yes, it does. :smiley: i will fix this issue with this method. thank you very much again!

However, i just have an idea about Date and Calendar in Elixir.

What if we isolate Date, DateTime and NaiveDateTime from Calendar.ISO. I mean there would not be a DateTime.utc_now/0. instead there will be a DateTime.utc_now/1 which has its first argument accepting a Calendar behaviour impl. (it can be defaulted as Calendar.ISO)

We can change Calendar behaviour so every new impl of it, should have a function like this:

@doc returns unix epoch from a given ISO date
Calendar.ISO.to_unix(Date.t | DateTime.t | NaiveDateTime.t) : Integer.t
-----------------------------------------------------------------------------
@doc returns unix epoch from a given Jalaali date
Jalaali.Calendar.to_unix(Date.t, DateTime.t, NaiveDateTime.t) : Integer.t

So we can convert any kind of Date to Unix Epoch and can use this to create any new kind of date without having to implement custom new Date, DateTime or NaiveDateTime.

Is this a bad idea?

PS: and all of Calendars should have from_unix(Integer.t) : NaiveDateTime.t

I don’t think so. The plan is to have functions that accept calendars as arguments as well as conversion functions to a fixed timestamp, although I am not sure if that timestamp will be the unix epoch.

2 Likes

@josevalim yes since we are dealing with only Dates and many of them may be behind 1970 unix epoch is overkill, i can suggest using Julian Day Calendar (JDN) as that fix timestamp. it simply represents number of days passed After Christ (AC or Positive integer) or Before Christ (BC or Negative integer)

In Jalaali i’m using JDN. whenever trying to convert from/to gregorian/jalaali im converting input date in Julian days and from Julian days i calculate specified date time.

1 Like

@josevalim Now that i think, i highly recommend using unix epoch as timestamp.

  • We already have Calendar.ISO.from_unix
  • Since in Date.utc_today and DateTime.utc_now, Elixir is using erlangs :os.system_time

I think if we use unix epoch we will have no overhead in this.

Am i wrong?

At the moment I don’t have enough background on supoorting multiple calendars to say if this should be our choice or not. For example, UNIX does not have leap seconds. So I don’t know if this is an issue or not.

1 Like

@josevalim sure it should be researched! but since leap seconds are added to each calendar differently, i don’t think UNIX will create a problem with that.

I don’t have enough background on that too, but because is my country more than several different calendars are being used, i have worked with many calendars programmatically. i can research on them and help in finding the best solution.

Anyways, is it possible for me to start working on this issue? I would be really happy :slight_smile:

2 Likes

Yes, please! Implementation is likely the easy part, researching and figuring out how other implementations work is the one that takes more time, so help is definitely appreciated.

3 Likes

You may want to consider looking at the Joda-Time Java library for inspiration. It supports several calendar systems. Although it doesn’t support Jalaali, there’s a fork that does.

I’m afraid I don’t have a lot of experience with conversions between calendar systems across time and space, but I know Joda is fairly respected.

2 Likes

@josevalim i will do my best on this. Thank you. :wink:

@jmitchell thank you very much for the hint! :slight_smile:

Let me clarify that I’m not at all an expert at time systems, but having a love of clocks, astronomy and weird human constructs here are my two cents:

  • To make precise timestamp conversions between two different calendars, it is useful to find a simple and unambigous (i.e. no discontinuities like leap days, daylight savings time, leap seconds etc.) ‘standard’ intermediate representation.
  • I might be wrong, but the TAI (International Atomic Time) seems to fit this description. This is the standard that is used in, amongst other places, space travel. It does not use leap days or leap seconds. It simply counts time (in SI-seconds) since the defined starting point. The timescale used by the GPS might be even easier to use (it it always exactly 19 seconds behind TAI, but otherwise is the same) as its starting point is equal to 1958-01-01 0:00 UTC.

Joda time indeed is a great reference; I really like their approach to Periods (a set of amounts, i.e. some days, some hours, some minutes, some seconds that are added in turn and thus keep in mind discontinuities like daylight savings time.) vs Durations (an exact amount of milliseconds.)

4 Likes

So far, Joda-Time is using epoch to achieve calendar (Chronology as they call it) conversion. Source

Probably this type of conversion does not support leap seconds. actually since leap seconds that adde to UTC do not have an algorithm, it may be impossible to support them in conversion.

Another issue that i have seen here is in DateTime.utc_now, Elixir uses erlang’s :os.system_time which does not count leap seconds either.

I personally don’t recognize this as issue because there is no documentation on how to decide whether or not this year contains a leap seconds. According to Wikipedia Insertion of each UTC leap second is usually decided about six months in advance by the International Earth Rotation and Reference Systems Service (IERS). (Source)
Even in Java, C# and other common langs, Leap seconds are not taken into account for their standard DateTimes Implementations.

I think (i need to research more about this) the advance of leap seconds is handled in OS by resync with an NTP. it should not be considered in Elixir’s standard Calendars or DateTimes.

If anyone has any ideas or believes i’m wrong about this, please tell me!

Researching continues…

2 Likes

Hey everyone

First of all i wanted to thank @Qqwy and @jmitchell for their useful posts.

Okay,

Here is what i finally found analyzing DateTime implementations of Java (Joda-Time), C#, Python:

  • Joda time is using UNIX timestamp to convert different Calendars (They call it chronologies) to each other (Link)
  • C# native DateTime only supports Gregorian calendar. convertors in its Globalization (Like PersianCalendar class) do not return a DateTime on convert. they only can return year, month, day of a given date to another calendar such as Jalaali, BUT it uses Gregorian day number as a timestamp
  • In python, there is nothing native for this problem, you should implement your new DateTime for it

NOTE: None of the implementations i see or read about has handled leap second problem, since leap seconds cannot be predicted because IERS will decide whether or not to add it (Link)

So it leads to these two decisions:

  1. Using UNIX as timestamp
  2. Using JDN (Julian day number) or GDN (Gregorian day number)

I vote for UNIX. because in Elixir, DateTime.utc_now or Date.today we are currently using :os.system_time so using UNIX wont add any overhead to code. but if we use JDN or GDN or anything else, we should afford a Date convert from UNIX to that timestamp.

So, What do you guys think?

(PS: @josevalim should i create an issue on github for this?)

1 Like

Have you had a chance to look at International Atomic Time (TAI) as suggested by @Qqwy? It seems preferable as a base measure of time because unlike other systems, it increases monotonically. As I understand it, TAI makes it possible to accurately describe “real” elapsed time, even when it differs from the time elapsed according to our clocks.

I see a couple potential downsides to TAI:

  1. as leap seconds are scheduled, we’d have to patch the conversion functions between it and UTC/UNIX time.
  2. generally other programming languages aren’t using it by default, and developers may expect elapsed times based on human clocks rather than “real” elapsed time.

Don’t hesitate to point out other concerns. I’m an amateur when it comes to timekeeping.

Should TAI be a first-class language feature or relegated to a library? UNIX time seems like the next best option if we don’t go with TAI.

I’ve posted a PR for discussion that uses the strategy used by Dershowitz and Rheingold in Calendrical,Calculations. This code only focuses on dates - not times - and uses the same convertable epoch approach. The PR is at https://github.com/elixir-lang/elixir/pull/5603. Comments and suggestions welcome.

The main advantage of this approach is that all of the calendar types in Calendrical Calculations that confirm to a {year, month, day} shape can be implemented using the Calendar behaviour. I added to the behaviour functions to convert to/from an integer date.

Also added a Date.diff/2 and protocol support to enumeration for a date range.

It would be pretty trivial to change from an integral number of days to a TAI or other time stamp. In fact that would help with some calendars that are based on start-of-day being noon rather than midnight and for calendars (like the Persian) that calculate leaps years based upon solar observation.