지구의 날짜를 기준으로 일자를 구해 화성 날짜로 변환 및 해당 월 달력을 생성하는 구현 문제입니다.
또한 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
이 안나오게 했습니다.