all repos — bibel @ e61fc65a61631125f782ff5d302389fe7c4a9228

Unnamed repository; edit this file 'description' to name the repository.

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	readingMode  ReadingMode
 13}
 14
 15// NewDateProgression creates a new date progression calculator
 16func NewDateProgression(bible *Bible) *DateProgression {
 17	return &DateProgression{bible: bible, versesPerDay: 12, readingMode: ReadingModeEvangelion}
 18}
 19
 20// NewDateProgressionWithConfig creates a new date progression calculator with config
 21func NewDateProgressionWithConfig(bible *Bible, versesPerDay int) *DateProgression {
 22	if versesPerDay <= 0 {
 23		versesPerDay = 12
 24	}
 25	return &DateProgression{bible: bible, versesPerDay: versesPerDay, readingMode: ReadingModeEvangelion}
 26}
 27
 28// NewDateProgressionWithReadingMode creates a new date progression calculator with reading mode
 29func NewDateProgressionWithReadingMode(bible *Bible, versesPerDay int, readingMode ReadingMode) *DateProgression {
 30	if versesPerDay <= 0 {
 31		versesPerDay = 12
 32	}
 33	return &DateProgression{bible: bible, versesPerDay: versesPerDay, readingMode: readingMode}
 34}
 35
 36// GetPositionForDate calculates the verse range for a given date
 37func (dp *DateProgression) GetPositionForDate(date time.Time) (*Bookmark, error) {
 38	// Calculate day of year (1-366)
 39	dayOfYear := date.YearDay()
 40
 41	// Each day shows configured number of verses
 42	targetVerseOffset := (dayOfYear - 1) * dp.versesPerDay // Zero-indexed
 43
 44	// Walk through appropriate books based on reading mode
 45	return dp.findPositionForOffset(targetVerseOffset)
 46}
 47
 48// findPositionForOffset finds the bookmark for a given cumulative verse offset
 49func (dp *DateProgression) findPositionForOffset(offset int) (*Bookmark, error) {
 50	// Determine which books to iterate through based on reading mode
 51	startBook, endBook := dp.getBookRange()
 52	
 53	// Iterate through books in the range
 54	for book := startBook; book <= endBook; book++ {
 55		// Find last chapter in this book
 56		maxChapter := 0
 57		for chapter := 1; ; chapter++ {
 58			if dp.bible.CountVersesInChapter(book, chapter) == 0 {
 59				maxChapter = chapter - 1
 60				break
 61			}
 62		}
 63		if maxChapter == 0 {
 64			continue // Book has no chapters? Shouldn't happen
 65		}
 66
 67		// Iterate through chapters
 68		for chapter := 1; chapter <= maxChapter; chapter++ {
 69			versesInChapter := dp.bible.CountVersesInChapter(book, chapter)
 70
 71			// Check if offset falls within this chapter
 72			if offset < versesInChapter {
 73				// Offset is within this chapter
 74				firstVerse := offset + 1 // Convert from 0-indexed to 1-indexed
 75
 76				// Create initial bookmark (like AdvanceBookmark does)
 77				secondVerse := min(firstVerse+dp.versesPerDay-1, versesInChapter)
 78				bookmark := &Bookmark{
 79					Book:        BookIndex(book),
 80					Chapter:     chapter,
 81					FirstVerse:  firstVerse,
 82					SecondVerse: secondVerse,
 83				}
 84
 85				// Apply lookahead rule (like AdjustForLookahead)
 86				return dp.applyLookahead(bookmark), nil
 87			}
 88
 89			// Move to next chapter, subtracting this chapter's verses
 90			offset -= versesInChapter
 91		}
 92	}
 93
 94	// If we've gone through all books and offset is still positive, wrap
 95	// around to beginning (start over) Calculate modulo offset within total verses
 96	totalVerses := dp.GetTotalVersesForMode()
 97	if totalVerses == 0 {
 98		return nil, fmt.Errorf("no verses found for reading mode %s. The Bible data file may not contain books for this reading mode", dp.readingMode)
 99	}
100	if offset >= 0 {
101		adjustedOffset := offset % totalVerses
102		// Recursively find position for adjusted offset
103		return dp.findPositionForOffset(adjustedOffset)
104	}
105
106	return nil, fmt.Errorf("could not find position for offset %d", offset)
107}
108
109// getBookRange returns the start and end book numbers for the current reading mode
110func (dp *DateProgression) getBookRange() (startBook, endBook int) {
111	switch dp.readingMode {
112	case ReadingModeEvangelion:
113		return 40, 43 // Matthew, Mark, Luke, John
114	case ReadingModeNewTestament:
115		return 40, 66 // Matthew through Revelation
116	case ReadingModeOldTestament:
117		return 1, 39 // Genesis through Malachi
118	case ReadingModeBible:
119		return 1, 66 // Entire Bible
120	default:
121		return 40, 43 // Default to Evangelion
122	}
123}
124
125// applyLookahead applies the lookahead rule (same as BookmarkManager.AdjustForLookahead)
126func (dp *DateProgression) applyLookahead(bookmark *Bookmark) *Bookmark {
127	versesInChapter := dp.bible.CountVersesInChapter(int(bookmark.Book), bookmark.Chapter)
128	versesRemaining := versesInChapter - bookmark.SecondVerse
129
130	// If we're not at the end of the chapter and less than dp.versesPerDay verses remain for NEXT snippet
131	if versesRemaining > 0 && versesRemaining < dp.versesPerDay {
132		return &Bookmark{
133			Book:        bookmark.Book,
134			Chapter:     bookmark.Chapter,
135			FirstVerse:  bookmark.FirstVerse,
136			SecondVerse: versesInChapter,
137		}
138	}
139
140	// Otherwise return the original bookmark
141	return bookmark
142}
143
144// GetTotalVersesForMode returns the total number of verses for the current reading mode
145func (dp *DateProgression) GetTotalVersesForMode() int {
146	total := 0
147	startBook, endBook := dp.getBookRange()
148	
149	for book := startBook; book <= endBook; book++ {
150		for chapter := 1; ; chapter++ {
151			verses := dp.bible.CountVersesInChapter(book, chapter)
152			if verses == 0 {
153				break
154			}
155			total += verses
156		}
157	}
158	return total
159}
160
161// GetTotalGospelVerses returns the total number of verses in all four Gospels
162// Deprecated: Use GetTotalVersesForMode instead
163func (dp *DateProgression) GetTotalGospelVerses() int {
164	// Calculate Gospels total (books 40-43)
165	total := 0
166	for book := 40; book <= 43; book++ {
167		for chapter := 1; ; chapter++ {
168			verses := dp.bible.CountVersesInChapter(book, chapter)
169			if verses == 0 {
170				break
171			}
172			total += verses
173		}
174	}
175	return total
176}