지구의 날짜를 기준으로 일자를 구해 화성 날짜로 변환 및 해당 월 달력을 생성하는 구현 문제입니다.
또한 ANSICode로 로딩바 구현도 해야 합니다.
이것만 보더라도 구현이 될 수 있게끔 전체 글을 읽고 정리해 보겠습니다. 요구사항을 파악하며 입출력 값도 자연스레 정의해 보겠습니다.
ANSICode를 사용해 구현합니다.ANSICode를 사용해 콘솔 화면에 진행 현황을 업데이트하는 Progress Bar를 직접 구현합니다.대략적으로 아래와 같이 생각했습니다.
ANSICode를 출력해 로딩바가 나오게 합니다.함수 지구 날짜 계산 (입력값)
날짜 변수 초기화
작년까지의 일자 계산(윤년까지) 및 날짜 변수 더하기
저번달까지의 일자 계산(2월-윤년 주의) 및 날짜 변수 더하기
오늘까지의 날짜 더하기
총 일자 return
함수 화성 날짜 계산 (지구 총 일자)
화성 연도, 월, 일 변수 초기화
올해 화성 년도 구하기
규칙을 찾아 몫을 구하기 => 668 + 669를 더한 값으로 몫을 구하고 *2를 하는 등
조건에 따라 +1을 해 올해 만들기
올해 화성 월 / 일 구하기
규칙을 찾아 몫을 구하기 => 28, 28, 28, 27이 반복 등
+1을 해 이번달 만들기
나머지로 오늘 날짜 구하기
{year, month, day} return
함수 화성 달력 출력
화성 달력 변수 초기화
오늘 화성 일자를 보고 윤년인지, 하루가 부족한 월인지를 확인해 최대 일자 정의
연도 월 배치해 화성 달력 변수에 push
요일 배치 'Su Lu Ma Me Jo Ve Sa' 달력 변수에 push
최대 일자까지 7일 씩 push
화성 달력 변수 return
함수 로딩 바 구현 및 출력
로딩, 지구일자, 화성일자, 달력 변수 초기화
함수 지구 날짜, 화성 날짜, 달력 계산
함수 지구 날짜 계산
함수 화성 날짜 계산
함수 화성 달력 출력
함수 로딩바 출력할 5초 동안 반복
로딩 += 10
로딩 10% 씩 업데이트
5초가 되면 = 로딩바 100%가 되면
지구날 ~ 화성년 문자열 출력
화성 달력 출력
비동기적으로 실행 => 로딩바가 시작되면 계산 실행
함수 지구 날짜, 화성 날짜, 달력 계산 실행
setInterval 사용해 함수 로딩바 출력할 5초 동안 반복 사용
이 의사코드를 바탕으로 실제 구현을 시작해보겠습니다.
흐름은 의사코드에 작성했으니 디테일 적인 부분만 작성했습니다.
const getEarthDay = (date) => {
let dayCount = 0;
const [year, month, day] = date.split('-').map(Number);
// 작년까지의 일자 수
dayCount += 365 * (year - 1) + Math.floor((year - 1) / 4);
// 이번 달까지의 일자 수
// prettier-ignore
const monthDay = [31, year % 4 ? 28 : 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
for (let i = 0; i < month - 1; i++) {
dayCount += monthDay[i];
}
// 오늘까지 일자 수
dayCount += day;
return dayCount;
};
const [year, month, day] = date.split('-').map(Number);split('-') 함수로 연도, 월, 일로 분리하고, map(Number)로 각 값을 숫자로 변환합니다.dayCount += 365 * (year - 1) + Math.floor((year - 1) / 4);-1했습니다.const monthDay = [31, year % 4 ? 28 : 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];for (let i = 0; i < month - 1; i++) { dayCount += monthDay[i]; }const getMarsDate = (earthDay) => {
let year = null;
let month = null;
let day = null;
const marsRegularYear = 668;
const marsLeapYear = 669;
const marsYearLength = marsRegularYear + marsLeapYear;
// 올해 화성 연도 구하기
const fullMarsCycles = Math.floor(earthDay / marsYearLength);
let remainingDays = earthDay % marsYearLength;
if (remainingDays >= marsRegularYear) {
year = fullMarsCycles * 2 + 1;
remainingDays -= marsRegularYear;
} else {
year = fullMarsCycles * 2;
}
// 올해 화성 월 / 일 구하기
for (let i = 1; i <= 24; i++) {
let curMonthDays = i % 6 === 0 ? 27 : 28;
if (i === 24 && year % 2 === 0) curMonthDays = 28;
if (remainingDays > curMonthDays) {
remainingDays -= curMonthDays;
} else {
month = i;
day = remainingDays;
break;
}
}
return { year, month, day };
};
const marsRegularYear = 668; const marsLeapYear = 669; const marsYearLength = marsRegularYear + marsLeapYear;const fullMarsCycles = Math.floor(earthDay / marsYearLength);let remainingDays = earthDay % marsYearLength; : 연도를 뽑아내고 남은 일자를 추출합니다.if (remainingDays >= marsRegularYear) ~~ : 2년 분을 추출했기 때문에 만약 668일 보다 많다면 1년이 더 추가됩니다.for (let i = 1; i <= 24; i++) { let curMonthDays = i % 6 === 0 ? 27 : 28; if (i === 24 && year % 2 === 0) curMonthDays = 28; if (remainingDays > curMonthDays) { remainingDays -= curMonthDays; } else { month = i; day = remainingDays; break; } }for (let i = 1; i <= 24; i++) : 24월까지 반복해 월, 일을 계산하려고 했습니다.curMonthDays : 현재 화성 월에서 27일인지, 28일인지 정해서 할당하는 변수입니다. 윤달일 시 쉽게 변경되게 설정했습니다.const makeMarsCalendar = (year, month, day) => {
const calendar = [];
let maxDay = month % 6 === 0 ? 27 : 28;
if (month === 24 && year % 2 === 0) maxDay = 28;
calendar.push(` ${year}년 ${month}월`);
calendar.push('Su Lu Ma Me Jo Ve Sa');
// 일자 찍어내기
let currentDay = 1;
for (let i = 0; i < 4; i++) {
const dayArr = [];
for (let j = 0; j < 7; j++) {
if (currentDay <= maxDay) {
if (currentDay === day) {
dayArr.push(`\x1b[31m${String(currentDay).padStart(2)}\x1b[39m`); // 오늘 날짜를 빨간색으로 하이라이트
} else {
dayArr.push(String(currentDay).padStart(2));
}
currentDay++;
} else {
dayArr.push(' ');
}
}
calendar.push(dayArr.join(' '));
}
return calendar;
};
let maxDay = month % 6 === 0 ? 27 : 28; if (month === 24 && year % 2 === 0) maxDay = 28;if (currentDay === day) : ANSICode를 사용해 현재 일자가 오늘 날짜와 같으면 하이라이트합니다.dayArr.push(String(currentDay).padStart(2)); : 그냥 넣었을 때, 1일과 10일 같이 자릿수가 차이나 보기 불편했습니다.padStart를 사용해 달력 자릿수를 정렬했습니다.dayArr를 join으로 묶어 push 했습니다.const displayMarsDateInfo = (input) => {
let loading = 0;
let earthDay = null;
let marsDate = null;
let marsCalendar = null;
const calculateDate = () => {
earthDay = getEarthDay(input);
marsDate = getMarsDate(earthDay);
marsCalendar = makeMarsCalendar(
marsDate.year,
marsDate.month,
marsDate.day,
);
};
const processLoading = () => {
// 로딩 진행 중
loading += 10;
console.log(`${'▓'.repeat(loading / 10)}${'░'.repeat(10 - loading / 10)} 화성까지 여행 ${loading}%`); // prettier-ignore
// 로딩이 끝나고 결과값 출력
if (loading === 100) {
clearInterval(loadingInterval);
console.log(
`지구날은 ${earthDay.toLocaleString('ko-KR')} => ${
marsDate.year
} 화성년 ${marsDate.month}월 ${marsDate.day}일\n`,
);
marsCalendar.forEach((line) => {
console.log(line);
});
}
};
// 로딩 진행 중 계산 실행 및 산출
calculateDate();
// 0.5초씩 반복하기, 재귀
const loadingInterval = setInterval(processLoading, 500);
};
console.log(${'▓'.repeat(loading / 10)}${'░'.repeat(10 - loading / 10)} 화성까지 여행 ${loading}%);calculateDate가 실행됩니다. 로딩이 시작되고 비동기적으로 출력이 나오는게 맞다고 생각해 분리했습니다.loading이 100이 되면 계산된 결과와 화성 달력을 출력합니다.const readline = require('readline');
// 지구 날짜 계산 함수
const getEarthDay = (date) => {
let dayCount = 0;
const [year, month, day] = date.split('-').map(Number);
// 작년까지의 일자 수
dayCount += 365 * (year - 1) + Math.floor((year - 1) / 4);
// 이번 달까지의 일자 수
// prettier-ignore
const monthDay = [31, year % 4 ? 28 : 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
for (let i = 0; i < month - 1; i++) {
dayCount += monthDay[i];
}
// 오늘까지 일자 수
dayCount += day;
return dayCount;
};
// 화성 날짜 계산 함수
const getMarsDate = (earthDay) => {
let year = null;
let month = null;
let day = null;
const marsRegularYear = 668;
const marsLeapYear = 669;
const marsYearLength = marsRegularYear + marsLeapYear;
// 올해 화성 연도 구하기
const fullMarsCycles = Math.floor(earthDay / marsYearLength);
let remainingDays = earthDay % marsYearLength;
if (remainingDays >= marsRegularYear) {
year = fullMarsCycles * 2 + 1;
remainingDays -= marsRegularYear;
} else {
year = fullMarsCycles * 2;
}
// 만약 아예 0이라면 그레고리든 화성이든 0년은 없기 때문에 1년으로 지정
if (fullMarsCycles === 0) {
year = 1;
}
// 올해 화성 월 / 일 구하기
for (let i = 1; i <= 24; i++) {
let curMonthDays = i % 4 === 0 ? 27 : 28;
if (i === 24 && year % 2 === 0) curMonthDays = 28;
if (remainingDays > curMonthDays) {
remainingDays -= curMonthDays;
} else {
month = i;
day = remainingDays;
break;
}
}
return { year, month, day };
};
// 화성 달력 출력 함수
const makeMarsCalendar = (year, month, day) => {
const calendar = [];
let maxDay = month % 6 === 0 ? 27 : 28;
if (month === 24 && year % 2 === 0) maxDay = 28;
calendar.push(` ${year}년 ${month}월`);
calendar.push('Su Lu Ma Me Jo Ve Sa');
// 일자 찍어내기
let currentDay = 1;
for (let i = 0; i < 4; i++) {
const dayArr = [];
for (let j = 0; j < 7; j++) {
if (currentDay <= maxDay) {
if (currentDay === day) {
dayArr.push(`\x1b[31m${String(currentDay).padStart(2)}\x1b[39m`); // 오늘 날짜를 빨간색으로 하이라이트
} else {
dayArr.push(String(currentDay).padStart(2));
}
currentDay++;
} else {
dayArr.push(' ');
}
}
calendar.push(dayArr.join(' '));
}
return calendar;
};
// 입력 및 출력 함수
const displayMarsDateInfo = (input) => {
let loading = 0;
let earthDay = null;
let marsDate = null;
let marsCalendar = null;
const calculateDate = () => {
earthDay = getEarthDay(input);
marsDate = getMarsDate(earthDay);
marsCalendar = makeMarsCalendar(
marsDate.year,
marsDate.month,
marsDate.day,
);
};
const processLoading = () => {
// 로딩 진행 중
loading += 10;
console.log(
`${'▓'.repeat(loading / 10)}${'░'.repeat(
10 - loading / 10,
)} 화성까지 여행 ${loading}%`,
);
// 로딩이 끝나고 결과값 출력
if (loading === 100) {
clearInterval(loadingInterval);
console.log(
`지구날은 ${earthDay.toLocaleString('ko-KR')} => ${
marsDate.year
} 화성년 ${marsDate.month}월 ${marsDate.day}일\n`,
);
marsCalendar.forEach((line) => {
console.log(line);
});
}
};
// 로딩 진행 중 계산 실행 및 산출
calculateDate();
// 0.5초씩 반복하기, 재귀
const loadingInterval = setInterval(processLoading, 500);
};
// input output 함수
(async () => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('지구 날짜를 입력하세요.(YYYY-MM-DD) : ', (input) => {
displayMarsDateInfo(input);
rl.close();
});
})();
// 예상 출력: 1106 화성년 8월 14일
// console.log(displayMarsDateInfo('2024-01-01'));
결과를 살펴보겠습니다.
프로세스는 잘 나옵니다. 거슬리는 건 3개입니다.
이 순서대로 디버깅을 실시해보겠습니다.
+ 이게.. 어떻게 해야 11일이 나와야하는지 잘 모르겠습니다. 이걸로 한 3시간 동안 고민하고 과정도 살펴봤는데 도무지 모르겠습니다. 더 개선할 예정입니다.
++ 제출 후 다른 동료분들도 비슷한 결과값이 나오는 걸 보고 제 문제만은 아닌가 생각해서 일단은 보류해놓은 상태입니다.
위 사진을 보면 8월 14일이 나와야하는데 11일이 나오고 있습니다. 지구 일수와 달력 형태는 맞는 걸 보니 화성 계산 날짜를 살펴보겠습니다.
const getMarsDate = (earthDay) => {
let year = null;
let month = null;
let day = null;
const marsRegularYear = 668;
const marsLeapYear = 669;
const marsYearLength = marsRegularYear + marsLeapYear;
console.log(`지구 일수: ${earthDay}`);
// 화성 연도 계산
const fullMarsCycles = Math.floor(earthDay / marsYearLength);
let remainingDays = earthDay % marsYearLength;
console.log(`전체 화성 주기: ${fullMarsCycles}, 남은 일수: ${remainingDays}`);
if (remainingDays >= marsRegularYear) {
year = fullMarsCycles * 2 + 1;
remainingDays -= marsRegularYear;
console.log(`화성 연도 (윤년): ${year}, 남은 일수: ${remainingDays}`);
} else {
year = fullMarsCycles * 2;
console.log(`화성 연도 (평년): ${year}, 남은 일수: ${remainingDays}`);
}
if (fullMarsCycles === 0) {
year = 1;
console.log(`화성 연도 (초기 0년 조정): ${year}`);
}
// 화성 월 및 일 계산
for (let i = 1; i <= 24; i++) {
let curMonthDays = i % 6 === 0 ? 27 : 28;
if (i === 24 && year % 2 === 0) curMonthDays = 28;
console.log(
`현재 화성 월: ${i}, 현재 월의 일수: ${curMonthDays}, 남은 일수: ${remainingDays}`,
);
if (remainingDays > curMonthDays) {
remainingDays -= curMonthDays;
} else {
month = i;
day = remainingDays + 1; // 1일을 더해서 날짜를 보정
console.log(
`화성 날짜 계산 완료 - 연도: ${year}, 월: ${month}, 일: ${day}`,
);
break;
}
}
return { year, month, day };
};
구글링을 해보니 stdout 메소드를 사용해 줄을 삭제하고, 다시 그리는 방법을 발견했습니다. 적용한 모습은 아래와 같습니다.
const processLoading = () => {
// 로딩 진행 중
loading += 10;
process.stdout.clearLine(); // 이전 줄 삭제
process.stdout.cursorTo(0); // 커서를 줄의 처음으로 이동
process.stdout.write(`${'▓'.repeat(loading / 10)}${'░'.repeat(10 - loading / 10)} 화성까지 여행 ${loading}%`); // prettier-ignore
// console.log(`${'▓'.repeat(loading / 10)}${'░'.repeat(10 - loading / 10)} 화성까지 여행 ${loading}%`);
// 로딩이 끝나고 결과값 출력
if (loading === 100) {
clearInterval(loadingInterval);
console.log(
`지구날은 ${earthDay.toLocaleString('ko-KR')} => ${
marsDate.year
} 화성년 ${marsDate.month}월 ${marsDate.day}일\n`,
);
marsCalendar.forEach((line) => {
console.log(line);
});
}
};
stdout 메소드의 clearLine, cursorTo, write를 사용해봤습니다.
+실수 : cursorTo를 쓰지 않았더니 커서가 밀려 이상하게 출력됐습니다. cursorTo를 붙여 출력되게 해봤습니다.
마음이 편해집니다. 아주 깔끔합니다.
사용자가 입력 시 의도대로 입력하지 않은 경우가 발생할 거 같아 불편했습니다. 의도대로 입력될려면 아래와 같습니다.
-의 조합으로 입력하기YYYY-MM-DD 형태로 입력하기위 같은 조건을 가진 유효성 검사를 input 입력 칸에 넣어보겠습니다.
// input output 함수
(async () => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('지구 날짜를 입력하세요.(YYYY-MM-DD) : ', (input) => {
const dateRegex = /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/;
if (!dateRegex.test(input)) {
console.log(
'잘못된 날짜 형식입니다. YYYY-MM-DD 형식, 숫자로 입력해주세요.',
);
rl.close();
return;
}
const [year, month, day] = input.split('-').map(Number);
// prettier-ignore
if (year < 1 || year > 9999 || month < 1 || month > 12 || day < 1 || day > 31) {
console.log(
'잘못된 날짜입니다. 1년 1월 1일부터 9999년 12월 31일까지의 날짜를 입력해주세요.',
);
rl.close();
return;
}
displayMarsDateInfo(year, month, day);
rl.close();
});
})();
다음과 같이 나옵니다. 에러 처리를 통해 입력 값을 잘못 넣어 NaN이 안나오게 했습니다.