Files
junhong_cmp_fiber/internal/service/package/utils.go
break c1c78d5008
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Failing after 4m22s
修复加油包独立有效期和流量重置
2026-09-22 16:36:07 +08:00

167 lines
5.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package packagepkg
import (
"time"
"github.com/break/junhong_cmp_fiber/pkg/constants"
)
// CalculateExpiryTime 计算套餐过期时间
// calendarType: 套餐周期类型(natural_month=自然月,by_day=按天)
// activatedAt: 激活时间
// durationMonths: 套餐时长(月数,calendar_type=natural_month 时使用)
// durationDays: 套餐天数(calendar_type=by_day 时使用,兼容每月折算天数)
// 返回:过期时间(当天 23:59:59)
func CalculateExpiryTime(calendarType string, activatedAt time.Time, durationMonths, durationDays int) time.Time {
var expiryDate time.Time
if calendarType == constants.PackageCalendarTypeNaturalMonth {
// 自然月套餐:activated_at 月份 + N 个月,月末 23:59:59
// 计算目标年月
targetYear := activatedAt.Year()
targetMonth := activatedAt.Month() + time.Month(durationMonths)
// 处理月份溢出
for targetMonth > 12 {
targetMonth -= 12
targetYear++
}
// 获取目标月份的最后一天(下个月的第0天就是本月最后一天)
expiryDate = time.Date(targetYear, targetMonth+1, 0, 23, 59, 59, 0, activatedAt.Location())
} else {
// 按天套餐:activated_at + N 天,23:59:59
effectiveDays := calculateByDayValidityDays(durationMonths, durationDays)
expiryDate = activatedAt.AddDate(0, 0, effectiveDays)
expiryDate = time.Date(expiryDate.Year(), expiryDate.Month(), expiryDate.Day(), 23, 59, 59, 0, expiryDate.Location())
}
return expiryDate
}
// CalculateAddonExpiryTime 计算加油包到期时间,并在主套餐更早到期时截断。
// 返回值中的 independent 表示加油包自身有效期未晚于主套餐,因而按自身规则到期。
func CalculateAddonExpiryTime(activatedAt time.Time, calendarType string, durationMonths, durationDays int, mainExpiresAt *time.Time) (expiresAt time.Time, independent bool) {
addonExpiresAt := CalculateExpiryTime(calendarType, activatedAt, durationMonths, durationDays)
if mainExpiresAt != nil && mainExpiresAt.Before(addonExpiresAt) {
return *mainExpiresAt, false
}
return addonExpiresAt, true
}
// CalculateValidityDays 计算套餐展示有效天数。
// 按天套餐优先返回折算后的总天数;自然月套餐按 baseTime 推算到目标月月末。
func CalculateValidityDays(calendarType string, baseTime time.Time, durationMonths, durationDays int) int {
if calendarType == constants.PackageCalendarTypeNaturalMonth {
return calculateNaturalMonthValidityDays(baseTime, durationMonths)
}
return calculateByDayValidityDays(durationMonths, durationDays)
}
func calculateByDayValidityDays(durationMonths, durationDays int) int {
if durationDays <= 0 {
if durationMonths <= 0 {
return 0
}
return durationMonths * 30
}
if durationMonths > 1 && durationDays <= 31 {
return durationMonths * durationDays
}
return durationDays
}
func calculateNaturalMonthValidityDays(baseTime time.Time, durationMonths int) int {
if baseTime.IsZero() || durationMonths <= 0 {
return 0
}
expiresAt := CalculateExpiryTime(constants.PackageCalendarTypeNaturalMonth, baseTime, durationMonths, 0)
startDate := time.Date(baseTime.Year(), baseTime.Month(), baseTime.Day(), 0, 0, 0, 0, baseTime.Location())
endDate := time.Date(expiresAt.Year(), expiresAt.Month(), expiresAt.Day(), 0, 0, 0, 0, expiresAt.Location())
days := int(endDate.Sub(startDate).Hours()/24) + 1
if days < 0 {
return 0
}
return days
}
// CalculateNextResetTime 计算下次流量重置时间
// dataResetCycle: 流量重置周期(daily/monthly/yearly/none)
// calendarType: 套餐周期类型(natural_month/by_day),影响月重置逻辑
// currentTime: 当前时间
// activatedAt: 套餐激活时间(按天套餐月重置时使用)
// 返回:下次重置时间(00:00:00)
func CalculateNextResetTime(dataResetCycle, calendarType string, currentTime, activatedAt time.Time) *time.Time {
if dataResetCycle == constants.PackageDataResetNone {
// 不重置
return nil
}
var nextResetTime time.Time
switch dataResetCycle {
case constants.PackageDataResetDaily:
// 日重置:明天 00:00:00
nextResetTime = time.Date(
currentTime.Year(),
currentTime.Month(),
currentTime.Day()+1,
0, 0, 0, 0,
currentTime.Location(),
)
case constants.PackageDataResetMonthly:
if calendarType == constants.PackageCalendarTypeNaturalMonth {
// 自然月套餐:每月1号 00:00:00 重置
year := currentTime.Year()
month := currentTime.Month()
// 如果当前日期 >= 1号(即已过重置点),则下次重置为下个月1号
if currentTime.Day() >= 1 {
month++
if month > 12 {
month = 1
year++
}
}
nextResetTime = time.Date(year, month, 1, 0, 0, 0, 0, currentTime.Location())
} else {
// 按天套餐:从激活日期开始,每30天重置一次
// 计算从激活到现在经过了多少个30天周期
daysSinceActivation := int(currentTime.Sub(activatedAt).Hours() / 24)
cyclesPassed := daysSinceActivation / 30
// 下次重置时间 = 激活时间 + (已过周期数+1) * 30天
nextResetTime = activatedAt.AddDate(0, 0, (cyclesPassed+1)*30)
nextResetTime = time.Date(
nextResetTime.Year(),
nextResetTime.Month(),
nextResetTime.Day(),
0, 0, 0, 0,
nextResetTime.Location(),
)
}
case constants.PackageDataResetYearly:
// 年重置:每年1月1日 00:00:00
year := currentTime.Year()
// 如果当前日期已经过了1月1日,则使用明年
jan1ThisYear := time.Date(year, 1, 1, 0, 0, 0, 0, currentTime.Location())
if currentTime.After(jan1ThisYear) || currentTime.Equal(jan1ThisYear) {
year++
}
nextResetTime = time.Date(year, 1, 1, 0, 0, 0, 0, currentTime.Location())
default:
return nil
}
return &nextResetTime
}