Recently, I happened to explain the development of the calendar component to my team members, and wrote down my own experience.

This is a screenshot of the calendar on Apple. According to what it says, there are Gregorian calendar, Lunar calendar, 24 solar terms and festivals.

Unfortunately, there is no formula for the conversion between the Gregorian calendar and the lunar calendar. The open source calendar components that can be found at this stage are basically stored in a calendar 1900-2100 run size information table, interested in the next lunar calendar can read the algorithm.

Festivals are relatively affected by regional factors and need regular maintenance, so in fact, the only things we can really calculate and use in the calendar are the Gregorian calendar and the 24 solar terms.

Problems to solve in writing a calendar

1. When does the week start?

This is because the beginning time of each week is different in different regions. Please refer to Zhihu. Is the first day of a week Monday or Sunday?

When using a physical calendar, it is true that some printed ones start on Monday, while when using electronic ones, most start on Sunday.

But this problem is easier to solve, first look at the head of the calendar.

As you can see from the screenshots, the overall order remains the same from the start of the day, and if you notice, this is much like a seamless scrolling component, where content rolls out of view and automatically adds to the end, like a circle.

It’s easy to get a seven-day week based on seamless scrolling.

Now that the principle is out, we can easily write code:

// In js, 0 is Sunday, and 0 to 6 is Monday to Sunday
const WEEKDAYS = [0.1.2.3.4.5.6]

// There is only 7 days in a week, there will not be 8 days, so just copy once
const DOUBLE_WEEKDAYS = WEEKDAYS.concat(WEEKDAYS)

/** * get the number of 7 days a week **@param FirstWeekDay Week start time * *@return Weeks array * /
function getWeekdays(firstWeekDay = 0) {
  if (firstWeekDay === 0) return WEEKDAYS
  return DOUBLE_WEEKDAYS.slice(firstWeekDay, firstWeekDay + 7)}Copy the code

After we implemented it, we basically met our expectations.

Let’s take this one step further and convert it to the head of the calendar to support internationalization

// Simplified Chinese
const zh = {
  // Full name
  weekdays: ['Sunday'.'Monday'.'Tuesday'.'Wednesday'.'Thursday'.'Friday'.'Saturday']./ / short name
  weekdaysShort: ['Sunday'.'Monday'.'on Tuesday'.'on Wednesday'.'on Thursday.'on Friday'.'on Saturday]./ / abbreviation
  weekdaysAbbr: ['day'.'一'.'二'.'三'.'four'.'five'.'六']}/ / English
const en = {
  // Full name
  weekdays: ['Sunday'.'Monday'.'Tuesday'.'Wednesday'.'Thursday'.'Friday'.'Saturday']./ / short name
  weekdaysShort: ['Sunday'.'Monday'.'Tuesday'.'Wednesday'.'Thursday'.'Friday'.'Saturday']./ / abbreviation
  weekdaysAbbr: ['Sun'.'Mon'.'Tue'.'Wed'.'Thu'.'Fri'.'Sat']}/** * get a list of names for a week **@param {Number[]}  Weekdays = 7 days a week * *@param {Object[]}* /
function getWeekHead(weekdays, locale) {
  return weekdays.map(day= > ({
    name: locale.weekdays[day],
    short: locale.weekdaysShort[day],
    abbr: locale.weekdaysAbbr[day],
    day: day
  }))
}
Copy the code

Open Chrome DevTools execution and the base can meet our expectations

The first day of the month is not the beginning of the week

This is very normal, the beginning of the week is not certain, let alone the number of days of the month is not fixed.

We can also see from the above picture that the end and beginning of the month are not fixed, so how should we deal with it?

Actually, this is also very easy to solve, let’s not think about it from the point of view of the program, let’s just think about it from the picture, is it possible to calculate the start time of the week of the first day of the month by subtracting the number of days in advance?

Maybe a little bit around, we use pictures to talk:

So once we know how to calculate, we can start preparing the data.

The first thing we need to know is the day of the week, this is in JS, directly by Date#getDay() to know the day of the week

But as I said, the beginning of the week is not fixed, so when we calculate, we need the function to know how many days we should subtract.

There may be some doubts here, the days of each month are not fixed, and there are embellish month problem; Counting the last day of the previous month is still a hassle, even if we know we need to reduce the number of days. How can we solve this problem?

In fact, this is very simple, in JS Date object already help us handle.

At this point we have all the conditions set, and the loop takes care of the rest.


/** * get month calendar **@param {*} Year years *@param {*} The month month *@param {Object} options
 * @param {Number[]} Options. Weekdays Array of 7 days a week *@param {Number} [options.firstWeekDay=0] Start time *@param {Number} [options. VisibleWeeksCount = 6] a single calendar shows the number of weeks * /
function getMonthCalendar(year, month, options) {
  const weekdays = options.weekdays
  const cursor = new Date(year, month - 1.1.0.0.0.0)

  const count = (options.visibleWeeksCount || 6) * 7

  // set the time to the beginning of the week
  cursor.setDate(cursor.getDate() - weekdays.indexOf(cursor.getDay()))

  const calendar = []

  let week = []
  for (let i = 0; i < count; i++) {
    if(! (i %7)) {
      week = calendar[i / 7] = []
    }

    week.push(
      // Copy time
      new Date(cursor)
    )

    cursor.setDate(cursor.getDate() + 1)}return calendar
}

function format(d) {
  return `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2.0)}-${d.getDate().toString().padStart(2.0)}`
}
Copy the code

Let’s look at the result of the execution

Try starting on Monday

So far, the dates and weeks are working as we expected. All that remains is rendering.

Render the calendar

In fact, when the data is ready, basically the calendar is almost complete, the rest is to stack on top of the function.

As long as the data is processed, we can use any framework and even run it in any JS environment that supports Date objects, such as nodeJS.

All the code is here: the complete code

withnodejs >= 12.xTry:


/** * Print calendar */
function printCalendar(year, month, firstWeekday) {
  const weekdays = getWeekdays(firstWeekday)
  const calendar = getMonthCalendar(year, month, { weekdays })
  const columns = getWeekHead(weekdays, zh).map((w) = > w.abbr)

  const rows = calendar.map((dates) = > {
    const row = {}
    columns.forEach((weekday, index) = > {
      const date = dates[index]
      if (isCurrentMonth(date)) {
        row[weekday] = date.getDate()
      }
    })
    return row
  })

  function isCurrentMonth(d) {
    return d.getFullYear() === year && d.getMonth() + 1 === month
  }

  const time = `  ${year}-${month.toString().padStart(2.'0')}`
  console.log(' ')
  console.group(time)
  console.table(rows, columns)
  console.groupEnd(time)
}
Copy the code

Print the calendar for 2021-01, starting on Sunday

printCalendar(2021.1.0)
Copy the code

Print the calendar for 2021-01, starting on Monday

printCalendar(2021.1.1)
Copy the code

Render to HTML

The end of the

Lunar calendar and holidays can be manually maintained by data, but solar terms can be maintained in components.

Writing a calendar, is also a more troublesome thing, fortunately, most front-end use of the calendar do not require the lunar calendar, 24 solar terms and festivals, otherwise the hair will have to drop several roots.

If you’re maintaining your own calendar data module, try the package I’ve published :@zhengxs/calendar-data, fork is welcome

Denver annual essay | 2020 technical way with me The campaign is under way…