Last active
February 7, 2024 12:07
-
-
Save eiel/984683766143d02739bcae38c67e2304 to your computer and use it in GitHub Desktop.
誕生日から何年何日経過したか計算する
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function isLeapYear(year) { | |
if (year % 400 === 0) { | |
return true; | |
} | |
if (year % 100 === 0) { | |
return false; | |
} | |
if (year % 4 === 0) { | |
return true; | |
} | |
return false; | |
} | |
function checkIsLeapYear(year, expected) { | |
const result = isLeapYear(year); | |
if (result === expected) { | |
return; | |
} | |
throw new Error(`isLeapYear year: ${year} expected: ${expected} actual: ${result}`); | |
} | |
checkIsLeapYear(1, false); | |
checkIsLeapYear(4, true); | |
checkIsLeapYear(100, false); | |
checkIsLeapYear(200, false); | |
checkIsLeapYear(400, true); | |
checkIsLeapYear(401, false); | |
checkIsLeapYear(404, true); | |
checkIsLeapYear(800, true); | |
checkIsLeapYear(801, false); | |
//////////////////////////////////////////////////////////////////////////////// | |
// Day 年月日で日付を表現する | |
//////////////////////////////////////////////////////////////////////////////// | |
/** | |
* @param date {Date} | |
* @returns {{month: number, year: number, type: 'day', day: number}} | |
*/ | |
function dateToDay(date) { | |
return { | |
type: 'day', | |
year: date.getFullYear(), | |
month: date.getMonth() + 1, | |
day: date.getDate(), | |
} | |
} | |
function checkDateToDay(date, expected) { | |
const result = dateToDay(date); | |
if (result.day === expected.day && result.month === expected.month && result.year === expected.year) { | |
return; | |
} | |
throw new Error(`date: ${date} expect: ${JSON.stringify(expected)} actual: ${JSON.stringify(result)}`); | |
} | |
checkDateToDay(new Date(Date.parse('Sat Jan 31 2023 00:00:00 GMT+0900')), { year: 2023, month: 1, day: 31}); | |
checkDateToDay(new Date(Date.parse('Sat Dec 1 2023 00:00:00 GMT+0900')), { year: 2023, month: 12, day: 1}); | |
/** | |
* 月ごとの日数のマスター | |
* @type {number[]} | |
*/ | |
const daysOfMonth = [ | |
31, | |
28, | |
31, | |
30, | |
31, | |
30, | |
31, | |
31, | |
30, | |
31, | |
30, | |
31, | |
]; | |
/** | |
* 月のはじめの日がその年の何日目か | |
* | |
* 閏年には未対応 | |
*/ | |
const fistYearDaysOfMonth = daysOfMonth.reduce((acc, day, index) => { | |
const month = index+1; | |
const beforeMonth = acc[month]; | |
const nextMonth = month+1; | |
return { | |
...acc, | |
[nextMonth]: day + beforeMonth, | |
} | |
}, { 1: 0 }); | |
/** | |
* 閏年において月のはじめの日がその年の何日目か | |
* @type {{string: number}} | |
*/ | |
const fistYearDaysOfMonthInLeapYear = daysOfMonth.reduce((acc, day, index) => { | |
const month = index+1; | |
const beforeMonth = acc[month]; | |
const nextMonth = month+1; | |
return { | |
...acc, | |
// 2月の場合閏年考慮する | |
[nextMonth]: day + beforeMonth + (month === 2 ? 1 : 0), | |
} | |
}, { 1: 0 }); | |
/** | |
* その年の何日目かを返す | |
* @param date {{ year: number, month: number, day: number}} | |
* @returns {number} | |
*/ | |
function dayOfYear(date) { | |
if (isLeapYear(date.year)) { | |
return date.day + fistYearDaysOfMonthInLeapYear[date.month]; | |
} | |
return date.day + fistYearDaysOfMonth[date.month]; | |
} | |
function checkDayOfYear(day, expected) { | |
const result = dayOfYear(day); | |
if (result === expected) { | |
return; | |
} | |
throw new Error(`dayOfYear day: ${JSON.stringify(day)} expect: ${expected} result: ${result}`); | |
} | |
checkDayOfYear({ year: 2023, month: 1, day: 7}, 7); | |
checkDayOfYear({ year: 2023, month: 1, day: 8}, 8); | |
checkDayOfYear({ year: 2023, month: 2, day: 1}, 32); | |
checkDayOfYear({ year: 2023, month: 3, day: 1}, 60); | |
checkDayOfYear({ year: 2024, month: 2, day: 1}, 32); | |
checkDayOfYear({ year: 2024, month: 3, day: 1}, 61); | |
//////////////////////////////////////////////////////////////////////////////// | |
// YearDay 年とその年で何日目かで日付を表現する | |
//////////////////////////////////////////////////////////////////////////////// | |
/** | |
* | |
* @param day {{ month: number, month: number, day: number}} | |
* @returns {{year: number, type: 'yearDay', dayOfYear: number}} | |
*/ | |
function dayToYearDay(day) { | |
return { | |
type: 'yearDay', | |
year: day.year, | |
dayOfYear: dayOfYear(day), | |
}; | |
} | |
function checkDayToYearDay(day, expected) { | |
const result = dayToYearDay(day); | |
if (result.year === expected.year && result.dayOfYear === expected.dayOfYear) { | |
return; | |
} | |
throw new Error(`day: ${JSON.stringify(day)} expect: ${JSON.stringify(expected)} actual: ${JSON.stringify(result)}`); | |
} | |
checkDayToYearDay({ year: 2023, month: 1, day: 7 }, { year: 2023, dayOfYear: 7 }); | |
checkDayToYearDay({ year: 2024, month: 3, day: 1 }, { year: 2024, dayOfYear: 61 }); | |
//////////////////////////////////////////////////////////////////////////////// | |
// ユースケース | |
//////////////////////////////////////////////////////////////////////////////// | |
/** | |
* 誕生日から特定の日付までの年数と日数を返す | |
* @param birthday_date {Date} | |
* @param target_date {Date} | |
* @returns {{year: number, day: number}} | |
*/ | |
function birthdayDuration(birthday_date, target_date) { | |
const targetYearDay = dayToYearDay(dateToDay(target_date)); | |
const birthday = dateToDay(birthday_date) | |
const birthdayYearDayInThisYear = dayToYearDay({ ...dateToDay(birthday_date), year: targetYearDay.year }); | |
const isAfterBirthday = targetYearDay.dayOfYear >= birthdayYearDayInThisYear.dayOfYear; | |
if (isAfterBirthday) { | |
return { year: targetYearDay.year - birthday.year, day: targetYearDay.dayOfYear - birthdayYearDayInThisYear.dayOfYear }; | |
} | |
const birthdayLastYear = dayToYearDay( { ...dateToDay(birthday_date), year: targetYearDay.year - 1 }) | |
const lastYearDay = isLeapYear(targetYearDay.year - 1) ? 366 : 365; | |
return { year: targetYearDay.year - birthday.year - 1, day: lastYearDay - birthdayLastYear.dayOfYear + targetYearDay.dayOfYear } | |
} | |
function check(birthday, date, expect) { | |
const result = birthdayDuration(birthday, date); | |
if (result.year === expect.year && result.day === expect.day) { | |
return | |
} | |
throw new Error(`birthday: ${birthday}, date: ${date}: expect: ${JSON.stringify(expect)}, result: ${JSON.stringify(result)}`); | |
} | |
check(new Date(1984, 0, 7), new Date(2023, 0, 7), { year: 39, day: 0 }); | |
check(new Date(1984, 0, 7), new Date(2023, 0, 8), { year: 39, day: 1 }); | |
check(new Date(1984, 0, 7), new Date(2023, 1, 7), { year: 39, day: 31 }); | |
check(new Date(1984, 0, 7), new Date(2023, 2, 1), { year: 39, day: 53 }); | |
check(new Date(1984, 0, 7), new Date(2023, 0, 6), { year: 38, day: 364 }); | |
check(new Date(1984, 0, 7), new Date(2022, 11, 31), { year: 38, day: 358 }); | |
check(new Date(1984, 0, 7), new Date(2022, 0, 7), { year: 38, day: 0 }); | |
check(new Date(1984, 1, 29), new Date(2024, 1, 28), { year: 39, day: 364 }); | |
check(new Date(1984, 1, 29), new Date(2024, 1, 29), { year: 40, day: 0 }); | |
check(new Date(1984, 1, 29), new Date(2024, 2, 1), { year: 40, day: 1 }); | |
check(new Date(1984, 1, 29), new Date(2023, 2, 1), { year: 39, day: 0 }); | |
check(new Date(1984, 2, 1), new Date(2025, 2, 1), { year: 41, day: 0 }); | |
check(new Date(1984, 2, 1), new Date(2025, 1, 28), { year: 40, day: 364 }); | |
console.log('success'); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment