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 if days > 0 {
92 return fmt.Sprintf("%s in %d days, %d hours, %d minutes", label, days, hours, minutes), nil
93 } else if hours > 0 {
94 return fmt.Sprintf("%s in %d hours, %d minutes", label, hours, minutes), nil
95 } else {
96 return fmt.Sprintf("%s in %d minutes", label, minutes), nil
97 }
98}
99
100// GetEasterProgressPercentage returns progress as a percentage (0.0 to 1.0)
101// Since Easter is an annual event, we calculate progress through the year
102func (ep *EasterProgression) GetEasterProgressPercentage() (float64, error) {
103 currentYear := time.Now().Year()
104
105 // Get Easter date for current year
106 var currentEaster time.Time
107 var err error
108
109 if ep.easterType == "latin" {
110 currentEaster, err = eastertime.CatholicByYear(currentYear)
111 } else {
112 currentEaster, err = eastertime.OrthodoxByYear(currentYear)
113 }
114
115 if err != nil {
116 return 0.0, err
117 }
118
119 // Get Easter date for previous year to calculate yearly cycle
120 var previousEaster time.Time
121 if ep.easterType == "latin" {
122 previousEaster, err = eastertime.CatholicByYear(currentYear - 1)
123 } else {
124 previousEaster, err = eastertime.OrthodoxByYear(currentYear - 1)
125 }
126
127 if err != nil {
128 return 0.0, err
129 }
130
131 now := time.Now()
132
133 // If current Easter has passed, we're between current Easter and next Easter
134 if currentEaster.Before(now) {
135 // Get next Easter
136 var nextEaster time.Time
137 if ep.easterType == "latin" {
138 nextEaster, err = eastertime.CatholicByYear(currentYear + 1)
139 } else {
140 nextEaster, err = eastertime.OrthodoxByYear(currentYear + 1)
141 }
142
143 if err != nil {
144 return 0.0, err
145 }
146
147 // Progress from current Easter to next Easter
148 totalDuration := nextEaster.Sub(currentEaster)
149 elapsedDuration := now.Sub(currentEaster)
150
151 return elapsedDuration.Seconds() / totalDuration.Seconds(), nil
152 } else {
153 // We're between previous Easter and current Easter
154 totalDuration := currentEaster.Sub(previousEaster)
155 elapsedDuration := now.Sub(previousEaster)
156
157 return elapsedDuration.Seconds() / totalDuration.Seconds(), nil
158 }
159}