为什么用「年×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)
}
相关推荐
leavesleo1 小时前
DeepSeek 偷偷给你取外号?拆一下 AI 人格化背后的技术
算法
liliangcsdn2 小时前
skewness收益偏度取负因子背后逻辑的探索
人工智能·算法·机器学习
鹿角片ljp2 小时前
LeetCode 300. 最长递增子序列|从 DP O (n²) 到贪心 + 二分 O (n log n)
算法·动态规划
叠层归一研究院2 小时前
缝合维度:时空3+1结构的拓扑起源
人工智能·算法·机器学习·agi
YXXY3132 小时前
FloodFill算法
算法
OPEN-F2 小时前
C++入门教程:数组、字符串与指针入门
数据结构·c++·算法
林森lsjs3 小时前
零基础吃透二叉树:定义、遍历与高频算法 —数据结构柒
java·开发语言·数据结构·算法·二叉树
船厂电气自动化ai大模型3 小时前
AI大模型与数学·第56课 快速傅里叶变换FFT:DFT高效优化算法,图像、音频、扩散模型工程加速核心工具
数据结构·人工智能·深度学习·算法·机器学习
203号居民3 小时前
【无标题】LeetCode hot 100 —54. 螺旋矩阵
算法