- Published on
파이썬으로 윤년 계산 프로그램 만들기 실수 없이 구현하는 규칙과 테스트
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
윤년(leap year) 계산은 겉보기엔 단순한 if문 문제처럼 보이지만, 막상 구현하면 의외로 자주 틀립니다. 이유는 규칙이 한 줄로 끝나지 않기 때문입니다. 4로 나누어떨어지면 윤년만 적용하면 1900년 같은 반례에서 바로 깨지고, 100으로 나누어떨어지면 평년만 추가하면 2000년 같은 반례에서 또 깨집니다.
이 글에서는 윤년 규칙을 정확히 이해하고, 파이썬으로 재사용 가능한 함수 + CLI 프로그램으로 만들며, 테스트 케이스로 검증하는 흐름까지 다룹니다. 반복문/조건문 감각이 부족하다면 함께 보면 좋은 글로 파이썬 구구단 출력 프로그램 for문과 while문으로 끝내는 반복문 감각도 연결해두겠습니다.
윤년 규칙 한 번에 정리하기
그레고리력 기준 윤년 규칙은 다음 3단계로 요약됩니다.
- 연도가 4로 나누어떨어지면 윤년 후보
- 단, 100으로 나누어떨어지면 평년(윤년 아님)
- 단, 400으로 나누어떨어지면 다시 윤년
즉, 수학적으로는 아래 조건과 같습니다.
year % 400 == 0이면 윤년- 아니고
year % 100 == 0이면 평년 - 아니고
year % 4 == 0이면 윤년 - 그 외는 평년
여기서 중요한 포인트는 우선순위입니다. 400 규칙이 100 규칙보다 더 강합니다.
가장 안전한 구현: 읽기 쉬운 if-elif 체인
가장 실수 없는 구현은 조건을 규칙 순서대로 나열하는 것입니다.
def is_leap_year(year: int) -> bool:
"""Return True if year is a leap year in the Gregorian calendar."""
if year % 400 == 0:
return True
if year % 100 == 0:
return False
return year % 4 == 0
왜 이런 형태가 좋은가
- 규칙을 그대로 코드로 옮겨 가독성이 좋습니다.
- 디버깅할 때
100년 예외,400년 재예외가 눈에 잘 들어옵니다. - 성능도 O(1)이라 고민할 필요가 없습니다. 시간 복잡도에 관심이 있다면 알고리즘 Big-O 표기법 제대로 이해하기 시간 복잡도를 고려해야 하는 이유도 참고해보세요.
흔히 하는 실수와 반례
윤년은 반례로 검증하는 게 핵심입니다.
실수 1: 4로만 나누기
# 잘못된 예
return year % 4 == 0
- 1900년은
4로 나누어떨어지지만실제로는 평년입니다(100으로 나누어떨어지고 400으로는 안 나누어떨어짐).
실수 2: 괄호 우선순위가 애매한 한 줄 조건
# 읽기 어려워서 실수 유발 가능
return year % 4 == 0 and year % 100 != 0 or year % 400 == 0
파이썬에서 and가 or보다 우선이긴 하지만, 팀 코드에서 이런 표현은 리뷰할 때도 헷갈립니다. 아래처럼 괄호를 명시하거나, 아예 if-elif 체인으로 쓰는 걸 권장합니다.
return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
입력을 받는 윤년 계산 CLI 프로그램 만들기
이제 함수만 있는 게 아니라, 실제로 터미널에서 실행 가능한 작은 프로그램으로 만들어봅시다.
요구사항을 현실적으로 잡아보면:
- 사용자가 연도를 입력한다
- 숫자가 아니면 다시 입력받거나 에러를 출력한다
- 음수/0 같은 값은 정책을 정한다(일반적으로는 1 이상의 정수만 허용)
while로 입력 검증하기
def is_leap_year(year: int) -> bool:
if year % 400 == 0:
return True
if year % 100 == 0:
return False
return year % 4 == 0
def read_year() -> int:
while True:
raw = input("연도를 입력하세요 (예: 2024): ").strip()
try:
year = int(raw)
except ValueError:
print("숫자로 된 연도를 입력해야 합니다.")
continue
if year < 1:
print("1 이상의 정수를 입력하세요.")
continue
return year
def main() -> None:
year = read_year()
if is_leap_year(year):
print(f"{year}년은 윤년입니다.")
else:
print(f"{year}년은 평년입니다.")
if __name__ == "__main__":
main()
트러블슈팅
ValueError: invalid literal for int()- 사용자가
2024년처럼 문자를 섞어 입력했을 때 발생합니다. 위 코드처럼try/except로 처리하세요.
- 사용자가
- 공백 입력
.strip()을 써도 빈 문자열이면int('')에서 예외가 납니다. 결국except로 잡히니 UX 측면에서 메시지를 명확히 하면 됩니다.
테스트 케이스로 검증하기 (가장 중요한 단계)
윤년 로직은 반드시 테스트로 잠그는 게 좋습니다. 특히 100/400 예외는 사람 눈으로 놓치기 쉽습니다.
최소 필수 테스트 세트
- 1996 → 윤년 (4로 나눔)
- 1999 → 평년
- 1900 → 평년 (100으로 나눔, 400은 아님)
- 2000 → 윤년 (400으로 나눔)
- 2024 → 윤년
- 2100 → 평년
간단한 assert 기반 테스트
별도 프레임워크 없이도 빠르게 검증할 수 있습니다.
def test_is_leap_year():
assert is_leap_year(1996) is True
assert is_leap_year(1999) is False
assert is_leap_year(1900) is False
assert is_leap_year(2000) is True
assert is_leap_year(2024) is True
assert is_leap_year(2100) is False
if __name__ == "__main__":
test_is_leap_year()
print("OK")
프로젝트가 커지면 pytest 같은 도구로 옮기는 게 좋지만, 윤년 같은 유틸 함수는 위 방식만으로도 충분히 안정성을 확보할 수 있습니다.
Best Practice 정리
1) 규칙을 코드 구조로 드러내기
- 한 줄 트릭보다
if -> if -> return구조가 유지보수에 유리합니다.
2) 입력 검증은 함수로 분리하기
read_year()처럼 입력/검증 로직을 분리하면 테스트와 재사용이 쉬워집니다.
3) 반례 중심 테스트를 먼저 작성하기
- 1900, 2000은 윤년 문제의 대표적인 함정입니다.
- 테스트가 있으면 리팩터링 중 실수해도 바로 잡힙니다.
결론 핵심 요약
- 윤년 규칙은 4로 나눔 → 100년 예외 → 400년 재예외 순서가 핵심입니다.
- 파이썬 구현은 읽기 쉬운 if-elif(또는 if-if-return) 구조가 가장 안전합니다.
- 반드시 1900(평년), 2000(윤년) 같은 반례로 테스트해 정확도를 보장하세요.
이 정도까지 갖추면 단순한 연습문제를 넘어, 실제 서비스 코드에서도 실수 없이 재사용 가능한 윤년 계산 유틸리티가 됩니다.