为什么用「年×100+月」做比较

复制代码
// BatchUpdateEmployeeStatusBasedOnLeaveDate 批量更新员工状态
// 根据离职日期,将两个月前已离职但状态仍为正常的员工状态更新为离职
func (spSvc *StaffPayrollService) BatchUpdateEmployeeStatusBasedOnLeaveDate() (int, error) {
	// 1. 查询所有有离职日期且状态正常的员工
	query := `
		SELECT employeeid, leave_date, status
		FROM employee
		WHERE leave_date != ''
		AND status = '0'
	`

	type EmployeeInfo struct {
		EmployeeID string `db:"employeeid"`
		LeaveDate  string `db:"leave_date"`
		Status     string `db:"status"`
	}

	var employees []EmployeeInfo
	err := spSvc.db.Select(&employees, query)
	if err != nil {
		return 0, fmt.Errorf("failed to query employees: %w", err)
	}

	if len(employees) == 0 {
		return 0, nil // 没有需要检查的员工
	}

	// 2. 过滤出两个月前离职的员工
	// twoMonthsAgo := time.Now().AddDate(0, -2, 0)
	var employeesToUpdate []EmployeeInfo
	now := time.Now()
	target := now.AddDate(0, -2, 0)
	targetValue := target.Year()*100 + int(target.Month())
	for _, emp := range employees {
		// 解析 mm-dd-yy 格式的日期
		t, err := time.Parse("01-02-06", emp.LeaveDate)
		if err != nil {
			return 0, fmt.Errorf("解析 %s 失败: %w", emp.LeaveDate, err)
		}
		value := t.Year()*100 + int(t.Month())
		if value <= targetValue {
			employeesToUpdate = append(employeesToUpdate, emp)
		}
	}

	if len(employeesToUpdate) == 0 {
		return 0, nil // 没有需要更新的员工 
	}

	// 3. 批量更新状态
	employeeIDs := lo.Map(employeesToUpdate, func(emp EmployeeInfo, _ int) string {
		return emp.EmployeeID
	})

	sql, params, err := spSvc.dialect.Update("employee").
		Where(goqu.Ex{"employeeid": employeeIDs}).
		Set(goqu.Record{
			"status":     "1",
			"updated_at": goqu.L("now()"),
		}).ToSQL()
	if err != nil {
		return 0, err
	}

	// spSvc.logger.Debug("update status sql ", sql)
	_, err = spSvc.db.Exec(sql, params...)
	if err != nil {
		return 0, err
	}

	return len(employeeIDs), err
}
  1. 本质:把「(年, 月)」二元组压缩成保序的整数

比较的目标是「离职日期所在月份 ≤ 两个月前所在月份」,粒度只到月。年×100+月 把 (2026, 6) 变成整数 202606,从而可以用一次 <= 完成比较:

这个映射是严格保序的:(y1,m1) < (y2,m2) ⇔ y1*100+m1 < y2*100+m2。

  1. 为什么基数必须是 100(而不是 10)

关键在于 100 > 12(一年最多 12 个月),保证「年」和「月」的位权不重叠。如果换成 ×10 就错了:

复制代码
2025年12月 → 2025×10 + 12 = 20262
2026年01月 → 2026×10 + 1  = 20261
20262 > 20261  ❌ 时间上 2026-01 反而比 2025-12 "小"了,排序被破坏

任何基数 ≥ 13(如 年×12+月、年×100+月)都保序;选 100 是为了可读性------202606 一眼能读出"2026 年 06 月"。

  1. 为什么不直接比较字符串 leave_date

存储格式是 MM-DD-YY(如 06-30-26),这种格式不是字典序即时间序:

"01-02-06"(2006-01-02)vs "12-31-99"(1999-12-31):字符串比较 "01..." < "12...",会误判 2006 年早于 1999 年

所以必须先 time.Parse 解析出真实年月,再比较

  1. 为什么不用 time.Time.Before() 直接比较

原代码只想比较到「月」粒度。若用 t.Before(target):

time.Time 精确到时分秒,target = now.AddDate(0,-2,0) 带当天时刻,t 是解析出的 00:00:00

边界会不一致:今天 8 月 25 日 → target = 6 月 25 日 15:00。离职 6 月 30 日:原逻辑 202606 ≤ 202606 → 处理;Before 比较 6-30 00:00 < 6-25 15:00 → 不处理

要做到等价,得把 target 规范化到「目标月 1 号」再取下月边界,反而更绕

整数压缩写法简单、无时区干扰、边界语义清晰(整月粒度)。

5、其他对比方法

方法 1:直接比较 Year/Month(展开写)

复制代码
if t.Year() < target.Year() ||
   (t.Year() == target.Year() && t.Month() <= target.Month()) {
	employeesToUpdate = append(employeesToUpdate, emp)
}

可读性最好,但代码长。语义与原逻辑完全等价(整数压缩就是它的简写)。

方法 2:格式化为 YYYY-MM 定宽字符串再比较

复制代码
yearMonth := fmt.Sprintf("%04d-%02d", t.Year(), int(t.Month()))
targetYearMonth := fmt.Sprintf("%04d-%02d", target.Year(), int(target.Month()))
if yearMonth <= targetYearMonth { ... }

定宽 + 左对齐 + 左数字右补齐,字符串字典序与整数序等价。可读性好,但多一次格式化开销。

方法 3:年×12+月(数学上最小基数)

复制代码
monthIndex := t.Year()*12 + int(t.Month()) - 1  // 或 t.Year()*12 + int(t.Month())

与 ×100 等价保序,但数字不直观(2026-06 → 24318)。

方法 4:把过滤下沉到 SQL(推荐,避免全表拉到 Go 里)

MySQL 有专门的 PERIOD_DIFF 周期函数,一条语句完成,连 Go 端解析都不需要:

复制代码
UPDATE employee
SET status = '1', updated_at = NOW()
WHERE leave_date != ''
  AND status = '0'
  AND PERIOD_DIFF(
        DATE_FORMAT(NOW(), '%Y%m'),
        DATE_FORMAT(STR_TO_DATE(leave_date, '%m-%d-%y'), '%Y%m')
      ) >= 2;

或更直观地用日期减法:

复制代码
AND STR_TO_DATE(leave_date, '%m-%d-%y') <= DATE_SUB(CURDATE(), INTERVAL 2 MONTH)

注意:STR_TO_DATE 的 %y 两位年转换规则与 Go 不同(MySQL 对 %y 的规则是 00-69 → 2000-2069,70-99 → 1970-1999),若数据里含 2069-2099 年的日期会解析偏差,需确认数据范围。全 SQL 化后还能顺手 SELECT COUNT(*) 拿影响行数。

方法 5:封装 YearMonth 类型(语义化,适合多处复用)

复制代码
type YearMonth struct{ Year, Month int }

func (ym YearMonth) Less(other YearMonth) bool {
	return ym.Year < other.Year || (ym.Year == other.Year && ym.Month < other.Month)
}
相关推荐
闲研随记12 小时前
RL算法学习:ArgMaxRL
算法·llm·强化学习·rl
飞Link12 小时前
零基础:离散数据积分与 Python 实现保姆级教程
python·算法
淡海水12 小时前
11-03-Unity-List-T-和Dictionary-TKey-TValue-的性能调优实战
算法·unity·c#·list·dictionary
土司大王12 小时前
LeetCode hot100——394.字符串解码:Java 双栈模拟
java·算法·leetcode
a1879272183112 小时前
【算法】双指针与滑动窗口(三):相向双指针——比较、排除、收缩
算法·leetcode·双指针·滑动窗口·原理·相向双指针·算法讲解
AI 小老六13 小时前
Agent 记忆系统难在取舍
人工智能·算法·架构·agent·memory·harness
大熊背13 小时前
《Color constancy by characterization of illumination chromaticity》之色度色域最大化算法(一)
数码相机·算法·白平衡·色度色域
不会就选b13 小时前
算法日常・每日刷题--<贪心>13
算法
带多刺的玫瑰13 小时前
Leecode#35刷题之搜索插入位置
java·python·算法
我不会起名字32213 小时前
万字总结Golang入门项目
数据结构·经验分享·算法·golang·项目实战·复盘