Last active
September 22, 2024 11:40
-
-
Save Innei/16568d126ae4449b11e4db209e9428f8 to your computer and use it in GitHub Desktop.
Calculation of overtime and vacation hours
This file contains hidden or 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
import { execSync } from "node:child_process" | |
import fs from "node:fs" | |
import dayjs, { Dayjs } from "dayjs" | |
import customParseFormat from "dayjs/plugin/customParseFormat.js" | |
import isSameOrAfter from "dayjs/plugin/isSameOrAfter.js" | |
import isSameOrBefore from "dayjs/plugin/isSameOrBefore.js" | |
import weekday from "dayjs/plugin/weekday.js" | |
dayjs.extend(customParseFormat) | |
dayjs.extend(isSameOrBefore) | |
dayjs.extend(isSameOrAfter) | |
dayjs.extend(weekday) | |
const scriptInfo = ` | |
统计周末和节假日加班情况的脚本 | |
时间范围:本脚本统计的是 2024 年全年的周末和法定节假日的加班情况 | |
调休天数计算逻辑: | |
1. 每个周末或节假日的加班,根据提交次数判断调休天数 | |
2. 如果当天提交次数少于 5 次,记为半天调休 | |
3. 如果当天提交次数达到或超过 5 次,记为一天调休 | |
4. 最终的调休天数是所有加班日调休天数的总和 | |
注意:本脚本仅统计在 Git 仓库中有提交记录的加班情况,可能无法完全反映实际的加班状况 | |
` | |
console.log(scriptInfo) | |
// 2024 年中国法定节假日列表(包括周末调休) | |
const holidays2024: Set<string> = new Set([ | |
"2024-01-01", // 元旦 | |
"2024-02-10", | |
"2024-02-11", | |
"2024-02-12", | |
"2024-02-13", | |
"2024-02-14", | |
"2024-02-15", | |
"2024-02-16", | |
"2024-02-17", // 春节 | |
"2024-04-04", | |
"2024-04-05", | |
"2024-04-06", // 清明节 | |
"2024-05-01", | |
"2024-05-02", | |
"2024-05-03", | |
"2024-05-04", | |
"2024-05-05", // 劳动节 | |
"2024-06-08", | |
"2024-06-09", | |
"2024-06-10", // 端午节 | |
"2024-09-15", | |
"2024-09-16", | |
"2024-09-17", // 中秋节 | |
"2024-10-01", | |
"2024-10-02", | |
"2024-10-03", | |
"2024-10-04", | |
"2024-10-05", | |
"2024-10-06", | |
"2024-10-07", // 国庆节 | |
]) | |
// 2024 年需要补班的周末 | |
const workWeekends2024: Set<string> = new Set([ | |
"2024-02-04", | |
"2024-02-18", | |
"2024-04-07", | |
"2024-04-28", | |
"2024-05-11", | |
"2024-09-14", | |
"2024-10-12", | |
]) | |
// 节日名称映射 | |
const holidayNames: { [key: string]: string } = { | |
"2024-01-01": "元旦", | |
"2024-02-10": "春节", | |
"2024-02-11": "春节", | |
"2024-02-12": "春节", | |
"2024-02-13": "春节", | |
"2024-02-14": "春节", | |
"2024-02-15": "春节", | |
"2024-02-16": "春节", | |
"2024-02-17": "春节", | |
"2024-04-04": "清明节", | |
"2024-04-05": "清明节", | |
"2024-04-06": "清明节", | |
"2024-05-01": "劳动节", | |
"2024-05-02": "劳动节", | |
"2024-05-03": "劳动节", | |
"2024-05-04": "劳动节", | |
"2024-05-05": "劳动节", | |
"2024-06-08": "端午节", | |
"2024-06-09": "端午节", | |
"2024-06-10": "端午节", | |
"2024-09-15": "中秋节", | |
"2024-09-16": "中秋节", | |
"2024-09-17": "中秋节", | |
"2024-10-01": "国庆节", | |
"2024-10-02": "国庆节", | |
"2024-10-03": "国庆节", | |
"2024-10-04": "国庆节", | |
"2024-10-05": "国庆节", | |
"2024-10-06": "国庆节", | |
"2024-10-07": "国庆节", | |
} | |
// 中文星期几 | |
const weekdaysCN: string[] = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"] | |
interface Commit { | |
date: Dayjs | |
message: string | |
insertions: number | |
deletions: number | |
hash: string | |
githubLink: string | |
} | |
interface GroupedCommit { | |
weekday: string | |
holiday: string | |
commits: Array<{ | |
message: string | |
insertions: number | |
deletions: number | |
githubLink: string | |
}> | |
totalInsertions: number | |
totalDeletions: number | |
} | |
function isWeekendOrHoliday(date: Dayjs): boolean { | |
const formattedDate = date.format("YYYY-MM-DD") | |
const dayOfWeek = date.day() | |
return ( | |
((dayOfWeek === 0 || dayOfWeek === 6) && !workWeekends2024.has(formattedDate)) || | |
holidays2024.has(formattedDate) | |
) | |
} | |
function getGitUserName(): string { | |
return execSync("git config user.name").toString().trim() | |
} | |
function getGitHubRepoUrl(): string { | |
const remoteUrl = execSync("git config --get remote.origin.url").toString().trim() | |
return remoteUrl.replace(/\.git$/, "").replace(/^git@github\.com:/, "https://github.com/") | |
} | |
function getCommits(userName: string): Commit[] { | |
const gitLog = execSync( | |
`git log --author="${userName}" --pretty=format:"%ad|%H" --date=iso`, | |
).toString() | |
const repoUrl = getGitHubRepoUrl() | |
return gitLog | |
.split("\n") | |
.map((line) => { | |
const [date, hash] = line.split("|") | |
const commitDate = dayjs(date) | |
if (isWeekendOrHoliday(commitDate)) { | |
const diffStats = execSync(`git show --stat --oneline ${hash} | tail -n 1`) | |
.toString() | |
.trim() | |
const message = execSync(`git show -s --format=%s ${hash}`).toString().trim() | |
const match = diffStats.match( | |
/(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?/, | |
) | |
const insertions = match && match[2] ? parseInt(match[2]) : 0 | |
const deletions = match && match[3] ? parseInt(match[3]) : 0 | |
const githubLink = `${repoUrl}/commit/${hash}` | |
return { date: commitDate, message, insertions, deletions, hash, githubLink } | |
} | |
return null | |
}) | |
.filter((commit): commit is Commit => commit !== null) | |
} | |
function groupCommitsByDay(commits: Commit[]): { [key: string]: GroupedCommit } { | |
const groupedCommits: { [key: string]: GroupedCommit } = {} | |
commits.forEach(({ date, message, insertions, deletions, githubLink }) => { | |
const dayKey = date.format("YYYY-MM-DD") | |
if (!groupedCommits[dayKey]) { | |
groupedCommits[dayKey] = { | |
weekday: weekdaysCN[date.day()], | |
holiday: holidayNames[dayKey] || "", | |
commits: [], | |
totalInsertions: 0, | |
totalDeletions: 0, | |
} | |
} | |
groupedCommits[dayKey].commits.push({ message, insertions, deletions, githubLink }) | |
groupedCommits[dayKey].totalInsertions += insertions | |
groupedCommits[dayKey].totalDeletions += deletions | |
}) | |
return groupedCommits | |
} | |
function calculateVacationDays(groupedCommits: { [key: string]: GroupedCommit }): number { | |
return Object.values(groupedCommits).reduce((total, { commits }) => { | |
return total + (commits.length < 5 ? 0.5 : 1) | |
}, 0) | |
} | |
const userName = getGitUserName() | |
const commits = getCommits(userName) | |
const groupedCommits = groupCommitsByDay(commits) | |
console.log(`社畜 "${userName}" 的周末和节假日提交统计(按天):`) | |
for (const [day, { weekday, holiday, commits, totalInsertions, totalDeletions }] of Object.entries( | |
groupedCommits, | |
)) { | |
const holidayInfo = holiday ? ` (${holiday})` : "" | |
const vacationDay = commits.length < 5 ? "0.5" : "1" | |
console.log( | |
`\n${day} ${weekday}${holidayInfo} (${commits.length} commits, 需休假 ${vacationDay} 天, +${totalInsertions} -${totalDeletions}):`, | |
) | |
commits.forEach(({ message, insertions, deletions, githubLink }) => { | |
console.log(`- ${message} (+${insertions} -${deletions}) ${githubLink}`) | |
}) | |
} | |
const totalOvertimeDays = Object.keys(groupedCommits).length | |
const totalOvertimeCommits = Object.values(groupedCommits).reduce((a, b) => a + b.commits.length, 0) | |
const totalVacationDays = calculateVacationDays(groupedCommits) | |
const totalInsertions = Object.values(groupedCommits).reduce((a, b) => a + b.totalInsertions, 0) | |
const totalDeletions = Object.values(groupedCommits).reduce((a, b) => a + b.totalDeletions, 0) | |
console.log(`\n总计加班天数:${totalOvertimeDays} 天`) | |
console.log(`总计周末和节假日提交次数:${totalOvertimeCommits} 次`) | |
console.log(`总计需要休假天数:${totalVacationDays} 天`) | |
console.log(`总计代码变更:+${totalInsertions} -${totalDeletions}`) | |
// 导出为 CSV | |
const csvContent: (string | number)[][] = [ | |
["脚本信息", scriptInfo.trim()], | |
[], | |
["日期", "星期", "节假日", "提交次数", "需休假天数", "新增行数", "删除行数", "提交信息"], | |
] | |
for (const [day, { weekday, holiday, commits, totalInsertions, totalDeletions }] of Object.entries( | |
groupedCommits, | |
)) { | |
const baseRow: (string | number)[] = [ | |
day, | |
weekday, | |
holiday, | |
commits.length, | |
commits.length < 5 ? 0.5 : 1, | |
totalInsertions, | |
totalDeletions, | |
] | |
commits.forEach(({ message, insertions, deletions, githubLink }, index) => { | |
if (index === 0) { | |
csvContent.push([...baseRow, `${message} (+${insertions} -${deletions}) ${githubLink}`]) | |
} else { | |
csvContent.push([ | |
"", | |
"", | |
"", | |
"", | |
"", | |
"", | |
"", | |
`${message} (+${insertions} -${deletions}) ${githubLink}`, | |
]) | |
} | |
}) | |
} | |
csvContent.push([]) | |
csvContent.push(["总计加班天数", totalOvertimeDays]) | |
csvContent.push(["总计周末和节假日提交次数", totalOvertimeCommits]) | |
csvContent.push(["总计需要休假天数", totalVacationDays]) | |
csvContent.push(["总计代码变更", `+${totalInsertions} -${totalDeletions}`]) | |
const csvString = csvContent.map((row) => row.map((cell) => `"${cell}"`).join(",")).join("\n") | |
fs.writeFileSync("overtime_commits.csv", csvString) | |
console.log("\nCSV 文件已导出为 overtime_commits.csv") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment