internal/easterprogression.go (view raw)
1package bible
2
3import (
4 "fmt"
5 "time"
6
7 "maxwelljensen/bibel/pkg"
8)
9
10// EasterProgression handles calculating time until Easter
11type EasterProgression struct {
12 easterType string // "orthodox" or "latin"
13}
14
15// NewEasterProgression creates a new Easter progression calculator
16func NewEasterProgression(easterType string) *EasterProgression {
17 if easterType != "latin" && easterType != "orthodox" {
18 easterType = "orthodox" // Default to orthodox
19 }
20 return &EasterProgression{easterType: easterType}
21}
22
23// GetNextEasterDate calculates the date of the next Easter (Orthodox or Catholic)
24func (ep *EasterProgression) GetNextEasterDate() (time.Time, error) {
25 currentYear := time.Now().Year()
26
27 // Try current year
28 var easterDate time.Time
29 var err error
30
31 if ep.easterType == "latin" {
32 easterDate, err = eastertime.CatholicByYear(currentYear)
33 } else {
34 easterDate, err = eastertime.OrthodoxByYear(currentYear)
35 }
36
37 if err != nil {
38 return time.Time{}, err
39 }
40
41 now := time.Now()
42
43 // If Easter has already passed this year, try next year
44 if easterDate.Before(now) {
45 if ep.easterType == "latin" {
46 easterDate, err = eastertime.CatholicByYear(currentYear + 1)
47 } else {
48 easterDate, err = eastertime.OrthodoxByYear(currentYear + 1)
49 }
50
51 if err != nil {
52 return time.Time{}, err
53 }
54 }
55
56 return easterDate, nil
57}
58
59// GetTimeUntilEaster calculates the duration until the next Easter
60func (ep *EasterProgression) GetTimeUntilEaster() (time.Duration, error) {
61 easterDate, err := ep.GetNextEasterDate()
62 if err != nil {
63 return 0, err
64 }
65
66 return time.Until(easterDate), nil
67}
68
69// FormatEasterProgress formats the time until Easter as a human-readable string
70func (ep *EasterProgression) FormatEasterProgress() (string, error) {
71 duration, err := ep.GetTimeUntilEaster()
72 if err != nil {
73 return "", err
74 }
75
76 // Calculate days, hours, minutes
77 totalSeconds := int(duration.Seconds())
78 days := totalSeconds / (24 * 60 * 60)
79 remainingSeconds := totalSeconds % (24 * 60 * 60)
80 hours := remainingSeconds / (60 * 60)
81 remainingSeconds %= (60 * 60)
82 minutes := remainingSeconds / 60
83
84 var label string
85 if ep.easterType == "latin" {
86 label = "Roman Catholic Easter"
87 } else {
88 label = "Easter"
89 }
90
91 switch {
92 case days > 0:
93 return fmt.Sprintf("%s in %d days, %d hours, %d minutes", label, days, hours, minutes), nil
94 case hours > 0:
95 return fmt.Sprintf("%s in %d hours, %d minutes", label, hours, minutes), nil
96 default:
97 return fmt.Sprintf("%s in %d minutes", label, minutes), nil
98 }
99}
100
101// GetEasterProgressPercentage returns progress as a percentage (0.0 to 1.0)
102// Since Easter is an annual event, we calculate progress through the year
103func (ep *EasterProgression) GetEasterProgressPercentage() (float64, error) {
104 currentYear := time.Now().Year()
105
106 // Get Easter date for current year
107 var currentEaster time.Time
108 var err error
109
110 if ep.easterType == "latin" {
111 currentEaster, err = eastertime.CatholicByYear(currentYear)
112 } else {
113 currentEaster, err = eastertime.OrthodoxByYear(currentYear)
114 }
115
116 if err != nil {
117 return 0.0, err
118 }
119
120 // Get Easter date for previous year to calculate yearly cycle
121 var previousEaster time.Time
122 if ep.easterType == "latin" {
123 previousEaster, err = eastertime.CatholicByYear(currentYear - 1)
124 } else {
125 previousEaster, err = eastertime.OrthodoxByYear(currentYear - 1)
126 }
127
128 if err != nil {
129 return 0.0, err
130 }
131
132 now := time.Now()
133
134 // If current Easter has passed, we're between current Easter and next Easter
135 if currentEaster.Before(now) {
136 // Get next Easter
137 var nextEaster time.Time
138 if ep.easterType == "latin" {
139 nextEaster, err = eastertime.CatholicByYear(currentYear + 1)
140 } else {
141 nextEaster, err = eastertime.OrthodoxByYear(currentYear + 1)
142 }
143
144 if err != nil {
145 return 0.0, err
146 }
147
148 // Progress from current Easter to next Easter
149 totalDuration := nextEaster.Sub(currentEaster)
150 elapsedDuration := now.Sub(currentEaster)
151
152 return elapsedDuration.Seconds() / totalDuration.Seconds(), nil
153 } else {
154 // We're between previous Easter and current Easter
155 totalDuration := currentEaster.Sub(previousEaster)
156 elapsedDuration := now.Sub(previousEaster)
157
158 return elapsedDuration.Seconds() / totalDuration.Seconds(), nil
159 }
160}
161