末日演算法

白色風車發表於2018-08-05

末日演算法

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

相關文章