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.
- Understanding the project – how the Persian calendar works relative to the Gregorian calendar. This will help us figure out our algorithm.
- Algorithm to convert a Gregorian calendar month and day into a Gregorian n-th day of the year
- Algorithm to convert this Gregorian n-th day into a Persian n-th day
- Algorithm to convert the Persian n-th day to a Persian calendar month and day
- 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.