Skip to content

Instantly share code, notes, and snippets.

@Innei
Last active September 22, 2024 11:40
Show Gist options
  • Save Innei/16568d126ae4449b11e4db209e9428f8 to your computer and use it in GitHub Desktop.
Save Innei/16568d126ae4449b11e4db209e9428f8 to your computer and use it in GitHub Desktop.
Calculation of overtime and vacation hours
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