需求

c/cpp 在一些嵌入式应用中,需要自己来实现对输入日期的校验和星期几的计算。

解决

  • 日期校验还好说,主要是闰年的判断。
  • 星期几的计算就比较麻烦了,一般是使用蔡勒公式。

蔡勒公式

1582年10月4日之后

w = (d + 1 + 2 * m + 3 * (m + 1) / 5 + y + y / 4 - y / 100 + y / 400) % 7

1582年10月4日或之前

w = (d + 1 + 2 * m + 3 * (m + 1) / 5 + y + y / 4 + 5) % 7

解释:

  • w:星期; w对7取模得:0-星期日,1-星期一,2-星期二,3-星期三,4-星期四,5-星期五,6-星期六
  • c:世纪(注:一般情况下,在公式中取值为已经过的世纪数,也就是年份除以一百的结果,而非正在进行的世纪,也就是现在常用的年份除以一百加一;不过如果年份是公元前的年份且非整百数的话,c应该等于所在世纪的编号,如公元前253年,是公元前3世纪,c就等于-3)
  • y:年(一般情况下是后两位数,如果是公元前的年份且非整百数,y应该等于 cMOD100+100)
  • m:月(m大于等于3,小于等于14,即在蔡勒公式中,某年的1、2月要看作上一年的13、14月来计算,比如2003年1月1日要看作2002年的13月1日来计算)
  • d:日

示例

[ ]代表取整,即只要整数部分。

// 中华人民共和国成立100周年纪念日那天(2049年10月1日)
w = y + [y / 4] + [c / 4] - 2c + [26 * (m + 1) / 10] + d - 1;
w = 49 + [49 / 4] + [20 / 4] - 2 × 20 + [26 × (10 + 1) / 10] + 1 - 1;
w = 54;
w % 7 = 5;

// 2049年10月1日(100周年国庆)
w = y + [y / 4] + [c / 4] - 2c + [26 * (m + 1) / 10] + d - 1;
w = 6 + [6 / 4] + [20 / 4] - 2 * 20 + [26 * (4 + 1) / 10] + 4 - 1;
w = -12;
w % 7 + 7 = 2;

代码实现

#ifndef CALENDAR_CALENDAR_H_
#define CALENDAR_CALENDAR_H_

#include <cstdint>

class Calendar {
public:
      enum Month {kJanuary = 0, kFebruary, kMarch, kApril, kMay, kJune,
	      kJuly, kAugust, kSeptember, kOctober, kNovember, kDecember};
      enum Weekend {kSunday = 0, kMonday, kTuesday, kWednesday,
	      kThursday, kFriday, kSaturday};

      struct Date {
	      uint16_t year = 0;
	      // 0 - 11
	      uint8_t month = 0;
	      // 0 - 30
	      uint8_t day = 0;
	      // 0 - 6
//		uint8_t weekend = 0;
      };

public:
      static bool IsLeapYear(uint16_t year);
      // check the date is right, do not have like 2-30
      static int8_t Check(Date& date);
      static Weekend GetWeekend(Date& date);

};

void CalendarTest();

#endif /* CALENDAR_CALENDAR_H_ */
#include "calendar.h"


bool Calendar::IsLeapYear(uint16_t year)
{
      if (((year % 4 == 0) && (year % 100) != 0) || (year % 400 == 0)) {
	      return true;
      } else {
	      return false;
      }
}

int8_t Calendar::Check(Date& date)
{
      if (date.year > 2099 || date.year < 1970) {
	      return -1;
      }

      int8_t ret = 0;

      if (date.month == kFebruary) {
	      if (IsLeapYear(date.year)) {
		      if (date.day > 28) {
			      date.day = 28;
			      ret = 1;
		      }
	      } else {
		      if (date.day > 27) {
			      date.day = 27;
			      ret = 1;
		      }
	      }
      }

      switch (date.month) {
      case kJanuary:
      case kMarch:
      case kMay:
      case kJuly:
      case kAugust:
      case kOctober:
      case kDecember:
	      if (date.day > 30) {
		      date.day = 30;
		      ret = 1;
	      }
	      break;

      case kApril:
      case kJune:
      case kSeptember:
      case kNovember:
	      if (date.day > 29) {
		      date.day = 29;
		      ret = 1;
	      }
	      break;

      default:
	      break;
      }

      return ret;
}

Calendar::Weekend Calendar::GetWeekend(Date& date)
{
      int year, month, day;
      year = date.year;
      month = date.month + 1;
      day = date.day + 1;

      if (date.month < 3) {
	      year -= 1;
	      month += 12;
      }

      int c = year / 100;
      int y = year % 100;
      int w = y + (y / 4) + (c / 4) - (2 * c) + (26 * (month + 1) / 10) + day - 1;
      w = (w % 7 + 7) % 7;

      return Calendar::Weekend(w);
}

void CalendarTest()
{
      // 2049-10-1, Friday
      Calendar::Date d1 = {2049, 9, 0};
      Calendar::Weekend w1 = Calendar::GetWeekend(d1);

      // 2006-4-4, Tuesday
      Calendar::Date d2 = {2006, 3, 3};
      Calendar::Weekend w2 = Calendar::GetWeekend(d2);
}

参考