all repos — bibel @ 9410cd8d1fb198ef5a049c595cb51e4f5587e5e7

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 offset >= 0 {
 98		adjustedOffset := offset % totalVerses
 99		// Recursively find position for adjusted offset
100		return dp.findPositionForOffset(adjustedOffset)
101	}
102
103	return nil, fmt.Errorf("could not find position for offset %d", offset)
104}
105
106// getBookRange returns the start and end book numbers for the current reading mode
107func (dp *DateProgression) getBookRange() (startBook, endBook int) {
108	switch dp.readingMode {
109	case ReadingModeEvangelion:
110		return 40, 43 // Matthew, Mark, Luke, John
111	case ReadingModeNewTestament:
112		return 40, 66 // Matthew through Revelation
113	case ReadingModeOldTestament:
114		return 1, 39 // Genesis through Malachi
115	case ReadingModeBible:
116		return 1, 66 // Entire Bible
117	default:
118		return 40, 43 // Default to Evangelion
119	}
120}
121
122// applyLookahead applies the lookahead rule (same as BookmarkManager.AdjustForLookahead)
123func (dp *DateProgression) applyLookahead(bookmark *Bookmark) *Bookmark {
124	versesInChapter := dp.bible.CountVersesInChapter(int(bookmark.Book), bookmark.Chapter)
125	versesRemaining := versesInChapter - bookmark.SecondVerse
126
127	// If we're not at the end of the chapter and less than dp.versesPerDay verses remain for NEXT snippet
128	if versesRemaining > 0 && versesRemaining < dp.versesPerDay {
129		return &Bookmark{
130			Book:        bookmark.Book,
131			Chapter:     bookmark.Chapter,
132			FirstVerse:  bookmark.FirstVerse,
133			SecondVerse: versesInChapter,
134		}
135	}
136
137	// Otherwise return the original bookmark
138	return bookmark
139}
140
141// GetTotalVersesForMode returns the total number of verses for the current reading mode
142func (dp *DateProgression) GetTotalVersesForMode() int {
143	total := 0
144	startBook, endBook := dp.getBookRange()
145	
146	for book := startBook; book <= endBook; book++ {
147		for chapter := 1; ; chapter++ {
148			verses := dp.bible.CountVersesInChapter(book, chapter)
149			if verses == 0 {
150				break
151			}
152			total += verses
153		}
154	}
155	return total
156}
157
158// GetTotalGospelVerses returns the total number of verses in all four Gospels
159// Deprecated: Use GetTotalVersesForMode instead
160func (dp *DateProgression) GetTotalGospelVerses() int {
161	// Calculate Gospels total (books 40-43)
162	total := 0
163	for book := 40; book <= 43; book++ {
164		for chapter := 1; ; chapter++ {
165			verses := dp.bible.CountVersesInChapter(book, chapter)
166			if verses == 0 {
167				break
168			}
169			total += verses
170		}
171	}
172	return total
173}