Skip to content

Instantly share code, notes, and snippets.

@eiel
Last active February 7, 2024 12:07
Show Gist options
  • Save eiel/984683766143d02739bcae38c67e2304 to your computer and use it in GitHub Desktop.
Save eiel/984683766143d02739bcae38c67e2304 to your computer and use it in GitHub Desktop.
誕生日から何年何日経過したか計算する
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