internal/dateprogression.go (view raw)
1package bible
2
3import (
4 "fmt"
5 "time"
6)
7
8// DateProgression handles calculating Bible position based on date
9type DateProgression struct {
10 bible *Bible
11 versesPerDay int
12}
13
14// NewDateProgression creates a new date progression calculator
15func NewDateProgression(bible *Bible) *DateProgression {
16 return &DateProgression{bible: bible, versesPerDay: 12}
17}
18
19// NewDateProgressionWithConfig creates a new date progression calculator with config
20func NewDateProgressionWithConfig(bible *Bible, versesPerDay int) *DateProgression {
21 if versesPerDay <= 0 {
22 versesPerDay = 12
23 }
24 return &DateProgression{bible: bible, versesPerDay: versesPerDay}
25}
26
27// GetPositionForDate calculates the verse range for a given date
28func (dp *DateProgression) GetPositionForDate(date time.Time) (*Bookmark, error) {
29 // Calculate day of year (1-366)
30 dayOfYear := date.YearDay()
31
32 // Each day shows configured number of verses
33 targetVerseOffset := (dayOfYear - 1) * dp.versesPerDay // Zero-indexed
34
35 // Walk through Gospels to find the position
36 return dp.findPositionForOffset(targetVerseOffset)
37}
38
39// findPositionForOffset finds the bookmark for a given cumulative verse offset
40func (dp *DateProgression) findPositionForOffset(offset int) (*Bookmark, error) {
41 // Iterate through books 40-43 (Matthew, Mark, Luke, John)
42 for book := 40; book <= 43; book++ {
43 // Find last chapter in this book
44 maxChapter := 0
45 for chapter := 1; ; chapter++ {
46 if dp.bible.CountVersesInChapter(book, chapter) == 0 {
47 maxChapter = chapter - 1
48 break
49 }
50 }
51
52 // Iterate through chapters
53 for chapter := 1; chapter <= maxChapter; chapter++ {
54 versesInChapter := dp.bible.CountVersesInChapter(book, chapter)
55
56 // Check if offset falls within this chapter
57 if offset < versesInChapter {
58 // Offset is within this chapter
59 firstVerse := offset + 1 // Convert from 0-indexed to 1-indexed
60
61 // Create initial bookmark (like AdvanceBookmark does)
62 secondVerse := min(firstVerse+dp.versesPerDay-1, versesInChapter)
63 bookmark := &Bookmark{
64 Book: BookIndex(book),
65 Chapter: chapter,
66 FirstVerse: firstVerse,
67 SecondVerse: secondVerse,
68 }
69
70 // Apply lookahead rule (like AdjustForLookahead)
71 return dp.applyLookahead(bookmark), nil
72 }
73
74 // Move to next chapter, subtracting this chapter's verses
75 offset -= versesInChapter
76 }
77 }
78
79 // If we've gone through all Gospels and offset is still positive, wrap
80 // around to beginning (start over) Calculate modulo offset within total
81 // Gospel verses
82 totalVerses := dp.GetTotalGospelVerses()
83 if offset >= 0 {
84 adjustedOffset := offset % totalVerses
85 // Recursively find position for adjusted offset
86 return dp.findPositionForOffset(adjustedOffset)
87 }
88
89 return nil, fmt.Errorf("could not find position for offset %d", offset)
90}
91
92// applyLookahead applies the lookahead rule (same as BookmarkManager.AdjustForLookahead)
93func (dp *DateProgression) applyLookahead(bookmark *Bookmark) *Bookmark {
94 versesInChapter := dp.bible.CountVersesInChapter(int(bookmark.Book), bookmark.Chapter)
95 versesRemaining := versesInChapter - bookmark.SecondVerse
96
97 // If we're not at the end of the chapter and less than dp.versesPerDay verses remain for NEXT snippet
98 if versesRemaining > 0 && versesRemaining < dp.versesPerDay {
99 return &Bookmark{
100 Book: bookmark.Book,
101 Chapter: bookmark.Chapter,
102 FirstVerse: bookmark.FirstVerse,
103 SecondVerse: versesInChapter,
104 }
105 }
106
107 // Otherwise return the original bookmark
108 return bookmark
109}
110
111// GetTotalGospelVerses returns the total number of verses in all four Gospels
112func (dp *DateProgression) GetTotalGospelVerses() int {
113 total := 0
114 for book := 40; book <= 43; book++ {
115 for chapter := 1; ; chapter++ {
116 verses := dp.bible.CountVersesInChapter(book, chapter)
117 if verses == 0 {
118 break
119 }
120 total += verses
121 }
122 }
123 return total
124}