末日算法

2199年7月2日是星期几?根据日期计算当天是星期几是一个有趣的问题,通常的思路是:选择任意一天作为我们计算的基础,比如2018年8月5日星期日,然后计算出目标日期与基础日期相差多少天,再推算目标日期是星期几。在没有遇见末日算法之前,我大概不会深入思考这个问题,然后按照上述思路草草解决。但是末日算法给我们提供了一种新的思路或者说优化了前述的算法。

末日算法假设每一年2月的最后一天是末日,因此每一年的3月7日,4月4日,5月9日,6月6日,7月11日,8月8日,9月5日,10月10日,11月7日,12月12日的星期必然和末日的星期相同,因为这些日期和末日相差的天数正好是7的倍数。除此之外,因为平年一年365天,闰年一年366天,所以每过一个平年,末日的星期数加1,每过一个闰年,末日的星期数加2(365 % 7 = 1, 366 % 7 = 2)。这样一来,只要我们知道某一年的末日是星期几,就可以按照末日算法的思路快速地计算出目标日期是星期几。

虽然末日算法最后还是要计算时间差,但是它极大地节省了计算量,也许这点计算量在现今的计算环境下算不了什么,但它让我看到了有意思的想法,如果我没有遇见末日算法,我可能不会继续深入思考这个问题,编程的秘密不仅仅是机械地解决问题,更在于理解逻辑和生活的本质。

const weekList = [
  '星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'
];

const doomBase = {
  year : 2000,
  week : 2
};

const searchMap = {
  '3'  : 7,
  '4'  : 4,
  '5'  : 9,
  '6'  : 6,
  '7'  : 11,
  '8'  : 8,
  '9'  : 5,
  '10' : 10,
  '11' : 7,
  '12' : 12
};

function isLeapYear( year ) {
  return ( year % 4 === 0 && year % 100 !== 0 ) || year % 400 === 0;
}

function positiveModulo( a, b ) {
  let result;

  result = a % b;

  if ( result < 0 ) {
    result += b;
  }

  return result;
}

function getDoomDay( year ) {
  let i, result;

  result = doomBase.week;

  if ( year < doomBase.year ) {
    for ( i = doomBase.year - 1; i >= year; i-- ) {
      result--;

      if ( isLeapYear( i + 1 ) ) {
        result--;
      }
    }
  } else {
    for ( i = doomBase.year + 1; i <= year; i++ ) {
      result++;

      if ( isLeapYear( i ) ) {
        result++;
      }
    }
  }

  result = positiveModulo( result, 7 );

  return result;
}

function getWeek( year, month, day ) {
  let offset, result;

  const doomDay = getDoomDay( year );

  if ( month < 3 ) {
    if ( isLeapYear( year ) ) {
      offset = day - 29 - ( 2 - month ) * 31;
    } else {
      offset = day - 28 - ( 2 - month ) * 31;
    }
  } else {
    offset = day - searchMap[ month ];
  }

  result = doomDay + offset;
  result = positiveModulo( result, 7 );

  return result;
}

function isValidateDate( year, month, day ) {
  let result;

  result = true;

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

  dayCountMap[ '2' ] = isLeapYear( year ) ? 29 : 28;

  if ( month < 1 || month > 12 ) {
    result = false;
  }

  if ( day > dayCountMap[ month ] ) {
    result = false;
  }

  return result;
}

function getDateString( year, month, day ) {
  return year + '年' + month + '月' + day + '日';
}

const testData = [
  [ 2199, 7, 2  ],
  [ 1994, 7, 5  ],
  [ 1994, 5, 27 ],
  [ 1994, 2, 28 ],
  [ 1994, 1, 1  ],
  [ 1994, 1, 31 ],
  [ 1994, 2, 1  ],
  [ 1994, 0, 1  ],
  [ 1994, 2, 29 ]
];

const testFunction = () => {
  testData.map( ( item ) => {
    if ( !isValidateDate( ...item ) ) {
      console.log( getDateString( ...item ) + '不是合法的日期!' );
    } else {
      console.log(
        getDateString( ...item ) + '是' + weekList[ getWeek( ...item ) ]
      );
    }
  } );
};

testFunction();

enter image description here