fix: resolve go vet error and dayStart logic in ComputeDailyTotals
- Fix 'string(d)' int-to-rune conversion in rpg_test.go by using fmt.Sprintf - Fix ComputeDailyTotals dayStart logic: initial state should be StateWorking when dayStart > 0, correctly counting work from dayStart to first out event - Update README project structure to reflect new domain/handler/repo layout - Implement missing achievements: rpg_pioneer (on RPG enable) and salary_setter (on first rate set) - Add tryUnlockAchievement helper to Handler
This commit is contained in:
326
internal/domain/dateutil.go
Normal file
326
internal/domain/dateutil.go
Normal file
@@ -0,0 +1,326 @@
|
||||
package domain
|
||||
|
||||
import "fmt"
|
||||
|
||||
var GregMonthDays = []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
|
||||
var JalaliMonthDays = []int{31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29}
|
||||
|
||||
var GregMonthNames = []string{
|
||||
"January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December",
|
||||
}
|
||||
var JalaliMonthNames = []string{
|
||||
"Farvardin", "Ordibehesht", "Khordad", "Tir", "Mordad", "Shahrivar",
|
||||
"Mehr", "Aban", "Azar", "Dey", "Bahman", "Esfand",
|
||||
}
|
||||
var HijriMonthNames = []string{
|
||||
"Muharram", "Safar", "Rabi' I", "Rabi' II", "Jumada I", "Jumada II",
|
||||
"Rajab", "Sha'ban", "Ramadan", "Shawwal", "Dhu al-Qa'dah", "Dhu al-Hijjah",
|
||||
}
|
||||
|
||||
func IsGregorianLeap(year int) bool {
|
||||
return year%4 == 0 && (year%100 != 0 || year%400 == 0)
|
||||
}
|
||||
|
||||
func IsJalaliLeap(year int) bool {
|
||||
r := year % 33
|
||||
return r == 1 || r == 5 || r == 9 || r == 13 || r == 17 || r == 22 || r == 26 || r == 30
|
||||
}
|
||||
|
||||
func MonthLenGreg(year, month int) int {
|
||||
if month == 2 && IsGregorianLeap(year) {
|
||||
return 29
|
||||
}
|
||||
return GregMonthDays[month-1]
|
||||
}
|
||||
|
||||
func MonthLenJalali(year, month int) int {
|
||||
if month == 12 && IsJalaliLeap(year) {
|
||||
return 30
|
||||
}
|
||||
return JalaliMonthDays[month-1]
|
||||
}
|
||||
|
||||
func IsHijriLeap(year int) bool {
|
||||
switch year % 30 {
|
||||
case 2, 5, 7, 10, 13, 16, 18, 21, 24, 27, 29:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func MonthLenHijri(year, month int) int {
|
||||
if month == 12 && IsHijriLeap(year) {
|
||||
return 30
|
||||
}
|
||||
if month%2 == 1 {
|
||||
return 30
|
||||
}
|
||||
return 29
|
||||
}
|
||||
|
||||
const jalaliEpochJDN = 1948320
|
||||
|
||||
func GregorianToJDN(gy, gm, gd int) int {
|
||||
a := (14 - gm) / 12
|
||||
y := gy + 4800 - a
|
||||
m := gm + 12*a - 3
|
||||
return gd + (153*m+2)/5 + 365*y + y/4 - y/100 + y/400 - 32045
|
||||
}
|
||||
|
||||
func JDNToGregorian(jdn int) (int, int, int) {
|
||||
f := jdn + 1401 + ((4*jdn+274277)/146097)*3/4 - 38
|
||||
e := 4*f + 3
|
||||
g := (e % 1461) / 4
|
||||
h := 5*g + 2
|
||||
day := (h%153)/5 + 1
|
||||
month := ((h/153 + 2) % 12) + 1
|
||||
year := e/1461 - 4716 + (14-month)/12
|
||||
return year, month, day
|
||||
}
|
||||
|
||||
func GregorianToJalali(gy, gm, gd int) (int, int, int) {
|
||||
jdn := GregorianToJDN(gy, gm, gd)
|
||||
days := jdn - jalaliEpochJDN
|
||||
if days < 0 {
|
||||
return 0, 0, 0
|
||||
}
|
||||
jy := 1
|
||||
for {
|
||||
yearDays := 365
|
||||
if IsJalaliLeap(jy) {
|
||||
yearDays = 366
|
||||
}
|
||||
if days < yearDays {
|
||||
break
|
||||
}
|
||||
days -= yearDays
|
||||
jy++
|
||||
}
|
||||
jm := 1
|
||||
for mm := 0; mm < 12; mm++ {
|
||||
md := MonthLenJalali(jy, mm+1)
|
||||
if days < md {
|
||||
break
|
||||
}
|
||||
days -= md
|
||||
jm++
|
||||
}
|
||||
jd := days + 1
|
||||
return jy, jm, jd
|
||||
}
|
||||
|
||||
func JalaliToGregorian(jy, jm, jd int) (int, int, int) {
|
||||
days := 0
|
||||
for y := 1; y < jy; y++ {
|
||||
if IsJalaliLeap(y) {
|
||||
days += 366
|
||||
} else {
|
||||
days += 365
|
||||
}
|
||||
}
|
||||
for m := 1; m < jm; m++ {
|
||||
days += MonthLenJalali(jy, m)
|
||||
}
|
||||
days += jd - 1
|
||||
jdn := jalaliEpochJDN + days
|
||||
return JDNToGregorian(jdn)
|
||||
}
|
||||
|
||||
func HijriToGregorian(hy, hm, hd int) (int, int, int) {
|
||||
days := (hy-1)*354 + (hy-1)*11/30
|
||||
for m := 1; m < hm; m++ {
|
||||
days += MonthLenHijri(hy, m)
|
||||
}
|
||||
days += hd - 1
|
||||
jdn := 1948440 + days
|
||||
f := jdn + 1401 + ((4*jdn+274277)/146097)*3/4 - 38
|
||||
e := 4*f + 3
|
||||
g := (e % 1461) / 4
|
||||
h := 5*g + 2
|
||||
day := (h%153)/5 + 1
|
||||
month := ((h/153 + 2) % 12) + 1
|
||||
year := e/1461 - 4716 + (14-month)/12
|
||||
return year, month, day
|
||||
}
|
||||
|
||||
func GregorianToHijri(gy, gm, gd int) (int, int, int) {
|
||||
jdn := GregorianToJDN(gy, gm, gd)
|
||||
days := jdn - 1948440
|
||||
hy := 1
|
||||
for {
|
||||
yearDays := 354
|
||||
if IsHijriLeap(hy) {
|
||||
yearDays = 355
|
||||
}
|
||||
if days < yearDays {
|
||||
break
|
||||
}
|
||||
days -= yearDays
|
||||
hy++
|
||||
}
|
||||
hm := 1
|
||||
for mm := 1; mm <= 12; mm++ {
|
||||
md := MonthLenHijri(hy, mm)
|
||||
if days < md {
|
||||
break
|
||||
}
|
||||
days -= md
|
||||
hm++
|
||||
}
|
||||
hd := days + 1
|
||||
return hy, hm, hd
|
||||
}
|
||||
|
||||
func DayOfWeekGregorian(gy, gm, gd int) int {
|
||||
if gm < 3 {
|
||||
gm += 12
|
||||
gy--
|
||||
}
|
||||
return (gd + (13*(gm+1))/5 + gy + gy/4 - gy/100 + gy/400) % 7
|
||||
}
|
||||
|
||||
type CalDay struct {
|
||||
DayNum int
|
||||
Date string
|
||||
}
|
||||
|
||||
type CalMonth struct {
|
||||
Title string
|
||||
WeekDays []string
|
||||
Days []CalDay
|
||||
}
|
||||
|
||||
func BuildCalendarMonth(cal string, year, month int) CalMonth {
|
||||
var title string
|
||||
var weekDays []string
|
||||
var totalDays int
|
||||
|
||||
if cal == "jalali" {
|
||||
title = fmt.Sprintf("%s %d", JalaliMonthNames[month-1], year)
|
||||
weekDays = []string{"Sh", "Ye", "Do", "Se", "Ch", "Pa", "Jo"}
|
||||
totalDays = MonthLenJalali(year, month)
|
||||
gy, gm, gd := JalaliToGregorian(year, month, 1)
|
||||
dow := DayOfWeekGregorian(gy, gm, gd)
|
||||
startOffset := dow
|
||||
var days []CalDay
|
||||
for i := 0; i < startOffset; i++ {
|
||||
days = append(days, CalDay{DayNum: 0, Date: ""})
|
||||
}
|
||||
for d := 1; d <= totalDays; d++ {
|
||||
gy, gm, gd = JalaliToGregorian(year, month, d)
|
||||
days = append(days, CalDay{DayNum: d, Date: fmt.Sprintf("%04d-%02d-%02d", gy, gm, gd)})
|
||||
}
|
||||
return CalMonth{Title: title, WeekDays: weekDays, Days: days}
|
||||
}
|
||||
|
||||
if cal == "hijri" {
|
||||
title = fmt.Sprintf("%s %d", HijriMonthNames[month-1], year)
|
||||
weekDays = []string{"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"}
|
||||
totalDays = MonthLenHijri(year, month)
|
||||
gy, gm, gd := HijriToGregorian(year, month, 1)
|
||||
dow := DayOfWeekGregorian(gy, gm, gd)
|
||||
startOffset := (dow + 5) % 7
|
||||
var days []CalDay
|
||||
for i := 0; i < startOffset; i++ {
|
||||
days = append(days, CalDay{DayNum: 0, Date: ""})
|
||||
}
|
||||
for d := 1; d <= totalDays; d++ {
|
||||
gy, gm, gd = HijriToGregorian(year, month, d)
|
||||
days = append(days, CalDay{DayNum: d, Date: fmt.Sprintf("%04d-%02d-%02d", gy, gm, gd)})
|
||||
}
|
||||
return CalMonth{Title: title, WeekDays: weekDays, Days: days}
|
||||
}
|
||||
|
||||
title = fmt.Sprintf("%s %d", GregMonthNames[month-1], year)
|
||||
weekDays = []string{"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"}
|
||||
totalDays = MonthLenGreg(year, month)
|
||||
dow := DayOfWeekGregorian(year, month, 1)
|
||||
startOffset := (dow + 5) % 7
|
||||
var days []CalDay
|
||||
for i := 0; i < startOffset; i++ {
|
||||
days = append(days, CalDay{DayNum: 0, Date: ""})
|
||||
}
|
||||
for d := 1; d <= totalDays; d++ {
|
||||
days = append(days, CalDay{DayNum: d, Date: fmt.Sprintf("%04d-%02d-%02d", year, month, d)})
|
||||
}
|
||||
return CalMonth{Title: title, WeekDays: weekDays, Days: days}
|
||||
}
|
||||
|
||||
func ParseGregorianDateKey(key string) (int, int, int) {
|
||||
var y, m, d int
|
||||
fmt.Sscanf(key, "%04d-%02d-%02d", &y, &m, &d)
|
||||
return y, m, d
|
||||
}
|
||||
|
||||
func FormatDateForCalendar(date, calType string) string {
|
||||
y, m, d := ParseGregorianDateKey(date)
|
||||
switch calType {
|
||||
case "jalali":
|
||||
jy, jm, jd := GregorianToJalali(y, m, d)
|
||||
return fmt.Sprintf("%04d-%02d-%02d", jy, jm, jd)
|
||||
case "hijri":
|
||||
hy, hm, hd := GregorianToHijri(y, m, d)
|
||||
return fmt.Sprintf("%04d-%02d-%02d", hy, hm, hd)
|
||||
default:
|
||||
return date
|
||||
}
|
||||
}
|
||||
|
||||
func UserDateToGregorian(dateStr, cal string) string {
|
||||
y, m, d := ParseGregorianDateKey(dateStr)
|
||||
switch cal {
|
||||
case "jalali":
|
||||
y, m, d = JalaliToGregorian(y, m, d)
|
||||
case "hijri":
|
||||
y, m, d = HijriToGregorian(y, m, d)
|
||||
}
|
||||
return fmt.Sprintf("%04d-%02d-%02d", y, m, d)
|
||||
}
|
||||
|
||||
func MonthGregorianRange(cal string, year, month int) (start, end string) {
|
||||
switch cal {
|
||||
case "jalali":
|
||||
gy, gm, gd := JalaliToGregorian(year, month, 1)
|
||||
start = fmt.Sprintf("%04d-%02d-%02d", gy, gm, gd)
|
||||
lastDay := MonthLenJalali(year, month)
|
||||
gy, gm, gd = JalaliToGregorian(year, month, lastDay)
|
||||
end = fmt.Sprintf("%04d-%02d-%02d", gy, gm, gd)
|
||||
case "hijri":
|
||||
gy, gm, gd := HijriToGregorian(year, month, 1)
|
||||
start = fmt.Sprintf("%04d-%02d-%02d", gy, gm, gd)
|
||||
lastDay := MonthLenHijri(year, month)
|
||||
gy, gm, gd = HijriToGregorian(year, month, lastDay)
|
||||
end = fmt.Sprintf("%04d-%02d-%02d", gy, gm, gd)
|
||||
default:
|
||||
start = fmt.Sprintf("%04d-%02d-%02d", year, month, 1)
|
||||
lastDay := MonthLenGreg(year, month)
|
||||
end = fmt.Sprintf("%04d-%02d-%02d", year, month, lastDay)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func FormatMonthTitle(cal string, year, month int) string {
|
||||
switch cal {
|
||||
case "jalali":
|
||||
return fmt.Sprintf("%s %d", JalaliMonthNames[month-1], year)
|
||||
case "hijri":
|
||||
return fmt.Sprintf("%s %d", HijriMonthNames[month-1], year)
|
||||
default:
|
||||
return fmt.Sprintf("%s %d", GregMonthNames[month-1], year)
|
||||
}
|
||||
}
|
||||
|
||||
func NavMonth(year, month int) (prevY, prevM, nextY, nextM int) {
|
||||
prevY, prevM = year, month-1
|
||||
if prevM < 1 {
|
||||
prevM = 12
|
||||
prevY--
|
||||
}
|
||||
nextY, nextM = year, month+1
|
||||
if nextM > 12 {
|
||||
nextM = 1
|
||||
nextY++
|
||||
}
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user