Creating a Calendar Day Converter from Gregorian to Persian

Using JavaScript to create a Gregorian to Persian calendar day converter

Let’s figure out how we can create an interactive interface that converts a Gregorian Month and Day to a Persian Month and Day, and vice versa. In the process, we’ll review some of the key Javascript concepts and methods for DOM manipulation.

Skip down to see the finished converter

Why?

“But Vahid, I love your devotion to your culture and all, but what’s in it for me? I’m not trying to conduct business in Iran or win the prize for Most Useless Piece of Knowledge Ever."

First, touché and a little touched, too. Second, hang with me. This is a great opportunity to practice some basic DOM manipulation techniques and think about algorithm structure.

Plus, who doesn’t like solving a good puzzle, ammirite?

Project Scope

But let’s begin with being clear about what we’re not building, so that our project remains in scope and doable within a couple hours.

We are not building an exact converter, and it should not be used as such. Our calendar converter won’t convert a specifc day from a specific year. It will only work for normal, common years (i.e. not leap years).

Why this constraint? Well, having to convert a specific day from a specific year is very tricky and difficult, for at least three main reasons.

First, countries occasionally change calendar systems entirely, which means an algorithm must be aware of the exact day when each new calendar change took effect, and be able to handle any date prior to it using the old system, and subsequent to it using the new system.

Second, countries and calendar systems handle leap years differently, and at different times. Some add a leap month instead of a leap day. Even if they do have a leap day (mostly) every four or five years, as the Gregorian and Persian calendars do, when they consider a year to be a leap year might be different, which has ramifications for the conversion.

For example, we are currently (as of late January 2021) in the 11th month of the year 1399 of the Persian calendar (the “Solar Hejri” calendar). Year 1399 is a leap year that will end on March 20, 2021. Being a leap year means that the last month, Esfand, has 30 days instead of its normal 29 days. The Gregorian year 2020 that just ended was also a leap year, but that meant that February 2020 had 29 days instead of the normal 28–yet February 2020 belonged to the previous year of the Persian calendar, i.e. year 1398.

So yes, it’s complicated.

Finally, converting a specific day from a specific year is outside the scope of this project because the concept of years is itself different across the two systems. For example, Nawruz (the Persian new year) begins in March, sometimes starting on the 19th, 20th, or 21st.

General Outline

Here’s an overview of our approach to this project.

  1. Understanding the project – how the Persian calendar works relative to the Gregorian calendar. This will help us figure out our algorithm.
  2. Algorithm to convert a Gregorian calendar month and day into a Gregorian n-th day of the year
  3. Algorithm to convert this Gregorian n-th day into a Persian n-th day
  4. Algorithm to convert the Persian n-th day to a Persian calendar month and day
  5. Setup the DOM elements and trigger functions

Here’s the final product. Select any month and day, and its corresponding day will be reflected.


Gregorian to Persian Calendar Converter

Gregorian Persian
Month Day Year n-th Day Month Day Year n-th Day

Let’s get started.

1. Understanding the project

Let’s first understand how the Persian calendar works, so we can get a sense of the conversions we’ll need to make.

Thankfully there are more similarities than differences, which makes this converter feasible.

Here’s what the Persian calendar’s normal year looks like (i.e. non-leap year).

Month Name Number of Days
1 Farvardin 31
2 Ordibehesht 31
3 Khordad 31
4 Tir 31
5 Mordad 31
6 Shahrivar 31
7 Mehr 30
8 Aban 30
9 Azar 30
10 Dey 30
11 Bahman 30
12 Esfand 29
  • There are 12 months, and they each contain 30 or 31 days, with the exception, as previously mentioned, of the last month Esfand which has 29 days during a normal year.

  • The first 6 months all have 31 days, followed by 5 months of 30 days, then Esfand

  • During our “normal year” for the purposes of this exercise, let’s assume that Farvardin 1 is on March 21st. Let’s also assume that the last day of our Persian calendar is Esfand 29, which falls on March 20th. In reality this isn’t always the case, of course–but adding this assumption will reduce this project’s scope.

2. Convert A Gregorian calendar month and day

Our first algorithm step is to figure out how many days into the Gregorian year we are, because we could then more easily convert this more objective reference point into the corresponding Persian reference point.

For example, on February 10, we’d be 31 + 10, or 41 days into the Gregorian year (i.e. the 41st day).

To extract this into a function that works for any day of any month, we need to first store how many days each month has. A constant that is an object would be perfect for this, since each key can be a month, with that month’s number of days as the value.

Our function would need to take two parameters (i.e. month and day), and return the n’th day of the year. To do that, we could iterate through every month from 1 to the argument month, and, using our constant, add the days from each month into a counter.

We also need to make sure we add the argument day to this counter and not count the max days possible for that month.

For example, our constant would look like:

const GREG_MONTH_DAYS = {
  1: 31,
  2: 28,
  3: 31,
  4: 30,
  5: 31,
  6: 30,
  7: 31,
  8: 31,
  9: 30,
  10: 31,
  11: 30,
  12: 31,
};

Our function might look something like this:

function gregCalToDays(month, day) {
  let gregDays = day > GREG_MONTH_DAYS[month] ? GREG_MONTH_DAYS[month] : day;
  for (let m = 1; m < month; m += 1) {
    gregDays += GREG_MONTH_DAYS[m];
  }
  return gregDays;
}

Note that line 2 of our above function uses a ternary operator to make sure that the day passed as an argument to our function is not greater than the max days available for that month.

3. Convert Gregorian n-th day to a Persian n-th day

Now that we have a single integer that represents the day of the Gregorian year (i.e. from 1 to 365), how can we convert this to the Persian n’th day?

Well, we know that day 1 of the Persian year starts on March 21. That’s the 80th day of the Gregorian year.

So we could write a function that takes the Gregorian n’th day, and subtracts 79, and we’re done, right?

Not quite yet. What if the day in question is before March 21, i.e. is less than or equal to 79? In that case, we’d be dealing with the ending Persian year days, and we’d need to add 286 days.

Our function, then, might look something like this:

function gregToPersDays(gregDays) {
  let persDays = gregDays > 79 ? gregDays - 79 : gregDays + 286;
  return persDays;
}

Notice that we declare persDays, initialize it with a value, and then immediately return it on the next line, so we could instead just directly return the value currently assigned to persDays, without having to use memory to create the variable.

However, this way offers more descriptive code that clearly defines what the function is returning, which makes it easier to debug later. There’s no time wasted on thinking about what (gregDays > 79) ? gregDays - 79 : gregDays + 286 means, for example.

4. Convert Persian n-th day to month and day

With our Persian n-th day, we can now work backwards and use the reverse process we used earlier to calculate the Gregorian n’th day. We can gradually subtract the amount of days each Persian month has, until we reach a month that has more days than our remaining day, which would reveal the calendar month and day.

We’d need a similar constant that would capture how many days are in each Persian month:

const PERS_MONTH_DAYS = {
  1: 31,
  2: 31,
  3: 31,
  4: 31,
  5: 31,
  6: 31,
  7: 30,
  8: 30,
  9: 30,
  10: 30,
  11: 30,
  12: 29,
};

A function could look like this:

function persDaysToCal(persDays) {
  let persMonth;
  let persDay = persDays;

  for (let idx = 1; idx <= 12; idx += 1) {
    persMonth = idx;
    if (PERS_MONTH_DAYS[idx] >= persDay) break;
    persDay -= PERS_MONTH_DAYS[idx];
  }
  return [persMonth, persDay];
}

Notice that our function returns the month and day as an array. This will later allow us to use destructuring assignment to capture the results of running the function into two separate variables.

For example, we could do:

let [persMonth, persDay] = persDaysToCal(59);

5. Setup the DOM elements and trigger functions

So far, our functions work for converting from a Gregorian month and day to a Persian month and day. But what about the other way around?

I’ll let you have fun figuring that one out yourself. You can view the source code for this page if you get stuck.

Our last step is to link all of our functions together, and have a way to output them to the page, and update any values (i.e. the max days available for a selected month).

My solution was to use a <select> element for the month, and an <input type="number" /> element for the day, for both the Gregorian and Persian sides. Make sure to give each element a unique id so that your JS code can properly find and update them as needed.

We can then use the following JS methods and keys:

Method() or Key Purpose
document.querySelector() Find our DOM element we want to get data from or to
setAttribute() Add an attribute to an element (e.g. a max day)
document.createElement() Create a new DOM element, which can then be appended to any existing element
appendChild() Append a new element within an existing element
addEventListener() Add an event listener to invoke a callback when an event is triggered (e.g. a click)
value Return (or set) the value of an input/select/textArea element
textContent Return (or set) the text inside a node (including an element)
innerHTML Return (or set) the HTML inside an element

Our function that combines it all could be:

function convertPers() {
  let gregMonthSelect = document.querySelector("#gregMonth");
  let gregDayInput = document.querySelector("#gregDay");
  let gregYearDayDiv = document.querySelector("#gregYearDay");

  let persMonthSelect = document.querySelector("#persMonth");
  let persDayInput = document.querySelector("#persDay");
  let persYearDayDiv = document.querySelector("#persYearDay");

  let gregMonthVal = Number(gregMonthSelect.value);
  let gregDayVal = Number(gregDayInput.value);

  let gregDays = gregCalToDays(gregMonthVal, gregDayVal);
  let persDays = gregToPersDays(gregDays);
  let [persMonth, persDay] = persDaysToCal(persDays);

  gregDayInput.setAttribute("max", GREG_MONTH_DAYS[gregMonthVal]);
  if (gregDayVal > GREG_MONTH_DAYS[gregMonthVal]) {
    gregDayInput.value = GREG_MONTH_DAYS[gregMonthVal];
  }

  gregYearDayDiv.textContent = gregDays;
  persMonthSelect.value = persMonth;
  persDayInput.value = persDay;
  persYearDayDiv.textContent = persDays;
}

To trigger this function, we would add an event listener to both the <select> and <input type="number" /> elements on the Gregorian side, passing it the convertPers function as a callback.

We’d have to do a similar process to be able to go from the Persian to the Gregorian calendar.

And that’s it!!

For Further Exploration

How can we use these techniques so that the calendar default state when we load the page shows today’s date?

Hint: look into the instance methods available to objects of the Date class.

Vahid Dejwakh
Vahid Dejwakh
Software Engineer at Microsoft;
Lover of good music and poetry

Vahid writes about interesting ideas at the intersection of software, system design, data, philosophy, psychology, policy, and business. He enjoys coffee and has a palate for spicy and diverse foods.

comments powered by Disqus

Related