moon_phase_tracker 1.3.2 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3fbc22c1b9f3481bd4585f49394696bea4bbef56302467bc0d9433bc17f6c179
4
- data.tar.gz: bd9d8566042eb8d1dccc4ab8ce66de2e6542e9103e3c2732ee45c0f467c98e3d
3
+ metadata.gz: 9c9587009682cc81b906d04054af17b88ae9863ebc56227457d507a2e484eb9c
4
+ data.tar.gz: 9a55ae1b9ffc3920f0fa7b9096bb5f107980efe55f646caf4d6f104890c51abb
5
5
  SHA512:
6
- metadata.gz: b5575c03ca0ea020a9b9332c0c624567dd9cd370192c0db29ef5578d28c0f98bfbc8c5d2811eeb3498d270b2e1ee8ef279028c0f4638ae4063ff7958abe02457
7
- data.tar.gz: ffe82390f6ad4deab517a8aaa0ef0e32541da030b0b0a10ba5d6991ae1749d2219069d5838cad8e05bff76f3c31913355536e766b50052fa590b177b046c2c7a
6
+ metadata.gz: '04509c13863e69615e2a4af359e997e9481fd3765e0950fe3a966a25d5f3bca4609607fbed3a1b5d2e32b375887e97ac4ec82d937224889988842b639ab26fc7'
7
+ data.tar.gz: 890dd9e2ec203dd22b512f50e7e5831ec7f18932ded680366627883c42307dcc3cc4f7e2009e44783b98e77791de51f34ca44512534375d7437b3b73f7c5a313
data/README.md CHANGED
@@ -1,23 +1,29 @@
1
- # MoonPhaseTracker 🌙
1
+ # 🌙 MoonPhaseTracker - Your Cosmic Calendar Companion
2
2
 
3
- A Ruby gem for tracking moon phases using the official US Naval Observatory (USNO) API. Perfect for creating lunar calendar-based scheduling applications.
3
+ *"Because every developer deserves to howl at the right moon."*
4
4
 
5
- **Expected accuracy: ~85-90% (suitable for lunar calendars)**
5
+ Ever wondered when to deploy your code for maximum lunar luck? Or perhaps you need to schedule that midnight debugging session during a proper New Moon? This delightful Ruby gem connects you to the cosmic dance above, using the official US Naval Observatory (USNO) API to bring celestial timing to your fingertips.
6
6
 
7
- ## Features
7
+ Whether you're building a werewolf scheduling app, a vampire calendar, or just want to impress your users with lunar-powered features, MoonPhaseTracker has got your back under every phase of the moon.
8
8
 
9
- - 🌑 Query moon phases by month, year, or from a specific date
10
- - 🌒 Complete 8-phase lunar cycle support (4 major + 4 intermediate phases)
11
- - 🌓 Simple and intuitive interface
12
- - 🌔 Accurate data from the official USNO Navy API
13
- - 🌕 Visual representation with moon phase emojis
14
- - 🌖 Interpolated intermediate phases for enhanced precision
15
- - 🌗 Visual indicators for calculated vs official phases
16
- - 🌘 Automatic caching to optimize requests
17
- - ⚡ Robust error handling
18
- - 🚦 Configurable rate limiting to respect API limits
9
+ **🎯 Accuracy: ~85-90%** *(Good enough for lunar calendars, probably too precise for howling)*
19
10
 
20
- ## Installation
11
+ ## ✨ What Makes This Gem Shine
12
+
13
+ - 🌑 **Time Travel Through Lunar Cycles** - Query moon phases by month, year, or from any date your heart desires
14
+ - 🌒 **The Full Lunar Symphony** - Complete 8-phase support (4 major crescendos + 4 melodic interludes)
15
+ - 🌓 **Simple as Moonlight** - An interface so intuitive, even a sleepy developer can use it at 3 AM
16
+ - 🌔 **Navy-Grade Precision** - Official USNO data because the Navy doesn't mess around with moon phases
17
+ - 🌕 **Emoji Magic** - Visual moon phases that spark joy in your terminal
18
+ - 🌖 **Mathematical Lunar Poetry** - Interpolated intermediate phases for when precision meets artistry
19
+ - 🌗 **Truth in Advertising** - Clear indicators showing official vs calculated phases (no moon phase imposters here!)
20
+ - 🌘 **Memory Like an Elephant** - Automatic caching because nobody likes waiting for the moon
21
+ - ⚡ **Bulletproof & Graceful** - Error handling that fails as elegantly as moonbeams
22
+ - 🚦 **API Etiquette** - Respectful rate limiting because even the Navy deserves a break
23
+
24
+ ## 🚀 Summoning the Lunar Powers
25
+
26
+ *Ready to add some celestial magic to your Ruby project?*
21
27
 
22
28
  Add this line to your application's Gemfile:
23
29
 
@@ -37,9 +43,13 @@ Or install it directly:
37
43
  gem install moon_phase_tracker
38
44
  ```
39
45
 
40
- ## Usage
46
+ ## 🎭 Lunar Adventures Await
47
+
48
+ *Time to dance with the moon phases like a cosmic choreographer!*
41
49
 
42
- ### Basic Usage (4 Major Phases)
50
+ ### 🌟 The Essential Four - Your Lunar Greatest Hits
51
+
52
+ *Start your journey with the moon's main characters: the celestial quartet that's been stealing hearts since ancient times.*
43
53
 
44
54
  ```ruby
45
55
  require 'moon_phase_tracker'
@@ -48,23 +58,26 @@ require 'moon_phase_tracker'
48
58
  phases = MoonPhaseTracker.phases_for_month(2025, 8)
49
59
  puts phases.first.to_s
50
60
  # => "🌑 New Moon - 2025-08-04 at 11:13"
61
+ # (Perfect timing for new beginnings!)
51
62
 
52
- # All phases for 2025
63
+ # All phases for 2025 - your yearly lunar roadmap
53
64
  year_phases = MoonPhaseTracker.phases_for_year(2025)
54
65
 
55
- # Next 6 phases from a specific date
66
+ # Next 6 phases from a specific date - peek into the future
56
67
  future_phases = MoonPhaseTracker.phases_from_date("2025-08-01", 6)
57
68
  ```
58
69
 
59
- ### Complete 8-Phase Lunar Cycle
70
+ ### 🎨 The Full Lunar Canvas - All 8 Phases in Their Glory
71
+
72
+ *For the lunar perfectionists who want the complete story, not just the highlights reel.*
60
73
 
61
74
  ```ruby
62
75
  require 'moon_phase_tracker'
63
76
 
64
- # All 8 phases for August 2025 (major + intermediate)
77
+ # All 8 phases for August 2025 - the complete lunar symphony
65
78
  all_phases = MoonPhaseTracker.all_phases_for_month(2025, 8)
66
79
  all_phases.each do |phase|
67
- indicator = phase.interpolated ? "~ " : " "
80
+ indicator = phase.interpolated ? "~ " : " " # ~ means "calculated with love"
68
81
  puts "#{indicator}#{phase}"
69
82
  end
70
83
  # => 🌑 New Moon - 2025-08-04 at 11:13
@@ -76,48 +89,96 @@ end
76
89
  # => 🌗 Last Quarter - 2025-08-26 at 09:26
77
90
  # => ~ 🌘 Waning Crescent - 2025-08-29 at 02:56
78
91
 
79
- # All 8 phases for entire year
92
+ # All 8 phases for entire year - your cosmic annual planner
80
93
  all_year_phases = MoonPhaseTracker.all_phases_for_year(2025)
81
94
 
82
- # All 8 phases from specific date (2 lunar cycles)
95
+ # All 8 phases from specific date (2 lunar cycles) - the extended edition
83
96
  extended_phases = MoonPhaseTracker.all_phases_from_date("2025-08-01", 2)
84
97
  ```
85
98
 
86
- ### Using the Tracker Class
99
+ ### 🔮 Instant Moon Phase - No API, No Waiting
100
+
101
+ *What phase is the moon right now? How bright is it? Pure math, instant answer.*
102
+
103
+ These methods use a synodic month calculation — no API calls, no network, no rate limits. Perfect for display UIs and real-time widgets.
104
+
105
+ ```ruby
106
+ require 'moon_phase_tracker'
107
+
108
+ # What's the moon doing right now?
109
+ phase = MoonPhaseTracker.current_phase
110
+ puts phase.to_s
111
+ # => "🌔 Waxing Gibbous - 2025-08-15 at 14:30"
112
+ puts phase.illumination # => 78.5 (percent)
113
+ puts phase.lunar_age # => 12.3 (days since last new moon)
114
+ puts phase.source # => :calculated
115
+
116
+ # Phase at any date/time - past or future
117
+ phase = MoonPhaseTracker.phase_at(Time.utc(2025, 6, 11, 7, 44))
118
+ puts phase.name # => "Full Moon"
119
+ puts phase.illumination # => ~100.0
120
+
121
+ # Just the illumination percentage
122
+ illum = MoonPhaseTracker.illumination(Date.today)
123
+ puts "#{illum.round(1)}% illuminated"
124
+
125
+ # Works with Date, Time, DateTime, or String
126
+ MoonPhaseTracker.phase_at(Date.new(2025, 1, 15))
127
+ MoonPhaseTracker.phase_at("2025-01-15")
128
+ MoonPhaseTracker.phase_at(DateTime.now)
129
+ ```
130
+
131
+ > **Accuracy note:** The synodic model gives the correct named phase ~99% of the time. Within ~6 hours of a phase boundary, it may differ from USNO API data. The API path gives precision for scheduling; the calculator path gives instant answers for display.
132
+
133
+ ### 🎯 The Tracker Class - Your Personal Lunar Assistant
134
+
135
+ *Think of it as your moon phase butler, always ready to serve up cosmic timing with a bow tie.*
87
136
 
88
137
  ```ruby
89
138
  tracker = MoonPhaseTracker::Tracker.new
90
139
 
91
- # Automatic formatting for display
140
+ # Automatic formatting for display - because presentation matters
92
141
  august_phases = tracker.phases_for_month(2025, 8)
93
142
  formatted = tracker.format_phases(august_phases, "August Phases")
94
143
  puts formatted
95
144
 
96
- # Next moon phase
145
+ # Next moon phase - your cosmic fortune telling
97
146
  next_phase = tracker.next_phase
98
147
  puts next_phase.to_s
99
148
 
100
- # Current month phases
149
+ # Current month phases - what's happening in your lunar neighborhood
101
150
  current_month = tracker.current_month_phases
151
+
152
+ # Instant phase at any date - no API call needed
153
+ phase = tracker.phase_at("2025-06-11")
154
+ puts phase.illumination # => Float (percent)
155
+
156
+ # Just the illumination number
157
+ illum = tracker.illumination("2025-06-11")
158
+
159
+ # Current phase right now
160
+ puts tracker.current_phase
102
161
  ```
103
162
 
104
- ### Working with Individual Phases
163
+ ### 🔍 Getting Personal with Moon Phases
164
+
165
+ *Each phase has its own personality - let's get to know them intimately.*
105
166
 
106
167
  ```ruby
107
168
  phase = phases.first
108
169
 
109
170
  # Phase information
110
171
  puts phase.name # "New Moon"
111
- puts phase.symbol # "🌑"
172
+ puts phase.symbol # "🌑" (isn't it beautiful?)
112
173
  puts phase.formatted_date # "2025-08-04"
113
- puts phase.formatted_time # "11:13"
114
- puts phase.interpolated # false (official USNO data)
174
+ puts phase.formatted_time # "11:13" (UTC - the moon doesn't do timezones)
175
+ puts phase.interpolated # false (certified genuine USNO data)
115
176
 
116
- # Working with interpolated phases
177
+ # Working with interpolated phases - the mathematically gifted ones
117
178
  interpolated_phase = all_phases.find(&:interpolated)
118
- puts interpolated_phase.interpolated # true (calculated)
179
+ puts interpolated_phase.interpolated # true (calculated with mathematical precision)
119
180
  puts interpolated_phase.name # "Waxing Crescent"
120
- puts interpolated_phase.symbol # "🌒"
181
+ puts interpolated_phase.symbol # "🌒" (still adorable)
121
182
 
122
183
  # Complete hash with all data
123
184
  details = phase.to_h
@@ -129,94 +190,191 @@ details = phase.to_h
129
190
  # symbol: "🌑",
130
191
  # iso_date: "2025-08-04",
131
192
  # utc_time: "2025-08-04T11:13:00Z",
132
- # interpolated: false
193
+ # interpolated: false,
194
+ # source: :api, # :api, :interpolated, or :calculated
195
+ # illumination: nil, # Float for calculated phases, nil for API
196
+ # lunar_age: nil # Float for calculated phases, nil for API
133
197
  # }
134
198
 
135
- # Date checks
136
- phase.in_month?(2025, 8) # => true
137
- phase.in_year?(2025) # => true
199
+ # Date checks - lunar detective work
200
+ phase.in_month?(2025, 8) # => true (August moon confirmed!)
201
+ phase.in_year?(2025) # => true (definitely a 2025 vintage)
138
202
  ```
139
203
 
140
- ### Error Handling
204
+ ### 🛡️ When the Moon Plays Hard to Get
205
+
206
+ *Even celestial bodies have bad days. Here's how to handle lunar tantrums gracefully.*
141
207
 
142
208
  ```ruby
143
209
  begin
144
210
  phases = MoonPhaseTracker.phases_for_month(2025, 8)
145
211
  rescue MoonPhaseTracker::NetworkError => e
146
- puts "Network error: #{e.message}"
212
+ puts "Network hiccup: #{e.message}" # The internet is having a moment
147
213
  rescue MoonPhaseTracker::APIError => e
148
- puts "API error: #{e.message}"
214
+ puts "API tantrum: #{e.message}" # The Navy API is feeling moody
149
215
  rescue MoonPhaseTracker::InvalidDateError => e
150
- puts "Invalid date: #{e.message}"
216
+ puts "Time travel error: #{e.message}" # That date doesn't exist in this dimension
217
+ end
218
+ ```
219
+
220
+ ### 🎪 Real-World Lunar Magic
221
+
222
+ *Because theory is nice, but seeing the moon phases in action is where the real magic happens.*
223
+
224
+ #### Hybrid Architecture: USNO for Scheduling, Calculator for Display
225
+
226
+ The API gives you exact dates. The calculator gives you instant answers. Use both — a background job populates a `lunar_phases` table with USNO data, and `LunarCalculator` handles the real-time display layer.
227
+
228
+ ```ruby
229
+ # app/jobs/sync_lunar_phases_job.rb
230
+ class SyncLunarPhasesJob < ApplicationJob
231
+ def perform(year = Date.current.year)
232
+ phases = MoonPhaseTracker.all_phases_for_year(year)
233
+
234
+ phases.each do |phase|
235
+ LunarPhase.upsert(
236
+ {
237
+ name: phase.name,
238
+ phase_type: phase.phase_type.to_s,
239
+ exact_at: phase.to_h[:utc_time],
240
+ source: phase.source.to_s
241
+ },
242
+ unique_by: :exact_at
243
+ )
244
+ end
245
+ end
246
+ end
247
+ ```
248
+
249
+ ```ruby
250
+ # app/models/lunar_phase.rb
251
+ class LunarPhase < ApplicationRecord
252
+ scope :upcoming, -> { where("exact_at > ?", Time.current).order(:exact_at) }
253
+ scope :next_full_moon, -> { upcoming.where(phase_type: "full_moon").first }
254
+ end
255
+ ```
256
+
257
+ ```ruby
258
+ # app/helpers/moon_helper.rb
259
+ module MoonHelper
260
+ def moon_badge(date = Date.current)
261
+ phase = MoonPhaseTracker.phase_at(date)
262
+ "#{phase.symbol} #{phase.illumination.round}%"
263
+ end
151
264
  end
152
265
  ```
153
266
 
154
- ### Practical Examples
267
+ Two data sources, each doing what they do best. The table owns scheduling precision (notifications, rituals, content triggers). The calculator owns display (emoji, illumination percentage, "what phase is it right now?"). They never step on each other's toes.
268
+
269
+ See the `examples/usage_example.rb` and `examples/eight_phases_example.rb` files for more usage examples.
155
270
 
156
- See the `examples/usage_example.rb` and `examples/eight_phases_example.rb` files for complete usage examples.
271
+ ## 📚 The Lunar Grimoire - Complete API Spellbook
157
272
 
158
- ## API Reference
273
+ *Your comprehensive guide to all the lunar incantations at your disposal.*
159
274
 
160
- ### Main Methods (4 Major Phases)
275
+ ### 🎭 The Core Cast - 4 Major Phase Methods
276
+
277
+ *The essential methods that'll get you 80% of your lunar needs covered.*
161
278
 
162
279
  - `MoonPhaseTracker.phases_for_month(year, month)` - Major phases for a specific month
163
280
  - `MoonPhaseTracker.phases_for_year(year)` - All major phases for a year
164
281
  - `MoonPhaseTracker.phases_from_date(date, num_phases)` - Major phases from a specific date
165
282
 
166
- ### 8-Phase Methods (Major + Intermediate)
283
+ ### 🔮 Instant Lookup - No API Required
284
+
285
+ *Pure math, zero latency. For when you need the moon right now.*
286
+
287
+ - `MoonPhaseTracker.phase_at(date)` - Phase at any date/time (returns `Phase` with illumination)
288
+ - `MoonPhaseTracker.illumination(date)` - Illumination percentage (returns `Float` 0..100)
289
+ - `MoonPhaseTracker.current_phase` - Phase right now (shortcut for `phase_at(Time.now.utc)`)
290
+
291
+ No API calls — pure math under the hood.
292
+
293
+ ### 🌈 The Full Spectrum - 8-Phase Methods for Lunar Completionists
294
+
295
+ *For those who believe in doing things thoroughly (and slightly obsessively).*
167
296
 
168
297
  - `MoonPhaseTracker.all_phases_for_month(year, month)` - All 8 phases for a specific month
169
298
  - `MoonPhaseTracker.all_phases_for_year(year)` - All 8 phases for a year
170
299
  - `MoonPhaseTracker.all_phases_from_date(date, num_cycles)` - All 8 phases from a specific date
171
300
 
172
- ### Complete Phase Types
301
+ ### 🎨 Meet the Lunar Cast - All 8 Phase Personalities
302
+
303
+ #### 🏆 The Main Characters (Official USNO Data)
304
+ *These are the A-listers - certified by the Navy, guaranteed to impress.*
305
+ - 🌑 `:new_moon` - New Moon *(The mysterious beginning)*
306
+ - 🌓 `:first_quarter` - First Quarter *(Half-lit and growing strong)*
307
+ - 🌕 `:full_moon` - Full Moon *(The showstopper, the main event)*
308
+ - 🌗 `:last_quarter` - Last Quarter *(Gracefully waning)*
309
+
310
+ #### 🎭 The Supporting Cast (Interpolated ~85-90% accuracy)
311
+ *The understudies that complete the story - mathematically calculated with love.*
312
+ - 🌒 `:waxing_crescent` - Waxing Crescent *(The optimistic youngster)*
313
+ - 🌔 `:waxing_gibbous` - Waxing Gibbous *(Almost there, building anticipation)*
314
+ - 🌖 `:waning_gibbous` - Waning Gibbous *(The wise elder, still radiant)*
315
+ - 🌘 `:waning_crescent` - Waning Crescent *(The gentle farewell)*
316
+
317
+ ### 🏷️ Phase Sources - Know Where Your Data Comes From
318
+
319
+ *Every phase carries a passport stamped with its origin story.*
173
320
 
174
- #### Major Phases (Official USNO Data)
175
- - 🌑 `:new_moon` - New Moon
176
- - 🌓 `:first_quarter` - First Quarter
177
- - 🌕 `:full_moon` - Full Moon
178
- - 🌗 `:last_quarter` - Last Quarter
321
+ | Source | Meaning | When |
322
+ |--------|---------|------|
323
+ | `:api` | Official USNO data | `phases_for_month`, `phases_for_year`, etc. |
324
+ | `:interpolated` | Calculated between two API phases | `all_phases_for_month`, `all_phases_for_year`, etc. |
325
+ | `:calculated` | Pure synodic math model | `phase_at`, `illumination`, `current_phase` |
179
326
 
180
- #### Intermediate Phases (Interpolated ~85-90% accuracy)
181
- - 🌒 `:waxing_crescent` - Waxing Crescent
182
- - 🌔 `:waxing_gibbous` - Waxing Gibbous
183
- - 🌖 `:waning_gibbous` - Waning Gibbous
184
- - 🌘 `:waning_crescent` - Waning Crescent
327
+ ```ruby
328
+ phase = MoonPhaseTracker.phase_at(Date.today)
329
+ phase.source # => :calculated
330
+ phase.illumination # => 65.3 (percent)
331
+ phase.lunar_age # => 10.2 (days since last new moon)
332
+ ```
333
+
334
+ ## ⚓ Our Trusted Cosmic Oracle
335
+
336
+ *We get our lunar wisdom from the finest source - the folks who navigate the seven seas by the stars.*
185
337
 
186
- ## Data Source
338
+ This gem uses the official US Naval Observatory (USNO) API - because when it comes to celestial navigation, you want the folks who've been steering ships by the stars for centuries:
187
339
 
188
- This gem uses the official US Naval Observatory (USNO) API:
189
340
  - **Base URL**: https://aa.usno.navy.mil/api/moon/phases/
190
- - **API Version**: 4.0.1
341
+ - **API Version**: 4.0.1 (battle-tested and Navy-approved)
191
342
  - **Documentation**: https://aa.usno.navy.mil/data/api
192
343
 
193
- All times are provided in Universal Time (UTC).
344
+ All times are provided in Universal Time (UTC) - because the moon doesn't care about your timezone preferences, and neither should precise astronomical data.
194
345
 
195
- ## Rate Limiting
346
+ ## 🚦 Playing Nice with the Navy - Rate Limiting with Style
196
347
 
197
- The gem implements respectful rate limiting to avoid overwhelming the USNO Navy API. By default, it allows **1 request per second** with a burst size of 1.
348
+ *Because even cosmic APIs need coffee breaks, and we're not monsters.*
198
349
 
199
- ### Default Rate Limiting
350
+ Think of rate limiting as the polite pause between questions when chatting with a wise oracle. We implement respectful rate limiting to keep the USNO Navy API happy (and responsive). By default, we're as patient as a monk - **1 request per second** with the restraint of a zen master.
351
+
352
+
353
+ ### 🧘 The Zen Approach - Default Rate Limiting
354
+
355
+ *Slow and steady wins the lunar race.*
200
356
 
201
357
  ```ruby
202
- # Default: 1 request per second
358
+ # Default: 1 request per second - the patient approach
203
359
  tracker = MoonPhaseTracker::Tracker.new
204
360
 
205
- # Check current rate limit configuration
361
+ # Check current rate limit configuration - know thy limits
206
362
  puts tracker.rate_limit_info
207
363
  # => {:requests_per_second=>1.0, :burst_size=>1, :available_tokens=>1}
208
364
 
209
- # Multiple requests will be automatically rate limited
365
+ # Multiple requests will be automatically rate limited - watch the magic
210
366
  start = Time.now
211
- tracker.phases_for_year(2025) # Immediate
212
- tracker.phases_for_year(2024) # Waits ~1 second
367
+ tracker.phases_for_year(2025) # Immediate (first one's free!)
368
+ tracker.phases_for_year(2024) # Waits ~1 second (patience, young padawan)
213
369
  puts "Total time: #{Time.now - start}s" # ~1.0 seconds
214
370
  ```
215
371
 
216
- ### Custom Rate Limiting
372
+ ### ⚡ Need for Speed? - Custom Rate Limiting
373
+
374
+ *For when you want to live life in the fast lane (but still be respectful).*
217
375
 
218
376
  ```ruby
219
- # Create custom rate limiter: 3 requests per second, burst of 2
377
+ # Create custom rate limiter: 3 requests per second, burst of 2 - living a little
220
378
  rate_limiter = MoonPhaseTracker::RateLimiter.new(
221
379
  requests_per_second: 3.0,
222
380
  burst_size: 2
@@ -224,16 +382,18 @@ rate_limiter = MoonPhaseTracker::RateLimiter.new(
224
382
 
225
383
  tracker = MoonPhaseTracker::Tracker.new(rate_limiter: rate_limiter)
226
384
 
227
- # Burst requests are immediate, then rate limited
228
- tracker.phases_for_year(2025) # Immediate
229
- tracker.phases_for_year(2024) # Immediate (burst)
230
- tracker.phases_for_year(2023) # Waits ~0.33 seconds
385
+ # Burst requests are immediate, then rate limited - controlled excitement
386
+ tracker.phases_for_year(2025) # Immediate (wheee!)
387
+ tracker.phases_for_year(2024) # Immediate (double wheee!)
388
+ tracker.phases_for_year(2023) # Waits ~0.33 seconds (and... breathe)
231
389
  ```
232
390
 
233
- ### Environment Variable Configuration
391
+ ### 🌍 Set It and Forget It - Environment Variables
392
+
393
+ *Configure once, smile forever.*
234
394
 
235
395
  ```ruby
236
- # Set via environment variables
396
+ # Set via environment variables - the lazy developer's paradise
237
397
  ENV["MOON_PHASE_RATE_LIMIT"] = "2.5"
238
398
  ENV["MOON_PHASE_BURST_SIZE"] = "3"
239
399
 
@@ -242,20 +402,24 @@ puts tracker.rate_limit_info
242
402
  # => {:requests_per_second=>2.5, :burst_size=>3, :available_tokens=>3}
243
403
  ```
244
404
 
245
- ### Disabling Rate Limiting
405
+ ### 🏁 Living Dangerously - Disabling Rate Limiting
406
+
407
+ *For the rebels and the reckless (use responsibly, dear lunar adventurer).*
246
408
 
247
409
  ```ruby
248
- # Disable rate limiting completely
410
+ # Disable rate limiting completely - for the speed demons
249
411
  ENV["MOON_PHASE_RATE_LIMIT"] = "0"
250
412
 
251
413
  tracker = MoonPhaseTracker::Tracker.new
252
- puts tracker.rate_limit_info # => nil
414
+ puts tracker.rate_limit_info # => nil (no limits, no safety net!)
253
415
 
254
- # Or pass nil explicitly
416
+ # Or pass nil explicitly - the explicit rebel
255
417
  tracker = MoonPhaseTracker::Tracker.new(rate_limiter: nil)
256
418
  ```
257
419
 
258
- ### Rate Limit Monitoring
420
+ ### 🔬 Under the Hood - Rate Limit Monitoring
421
+
422
+ *For the control freaks and performance enthusiasts among us.*
259
423
 
260
424
  ```ruby
261
425
  rate_limiter = MoonPhaseTracker::RateLimiter.new(
@@ -263,18 +427,20 @@ rate_limiter = MoonPhaseTracker::RateLimiter.new(
263
427
  burst_size: 2
264
428
  )
265
429
 
266
- # Check if request can proceed without waiting
267
- puts rate_limiter.can_proceed? # => true
430
+ # Check if request can proceed without waiting - the crystal ball
431
+ puts rate_limiter.can_proceed? # => true (or false if you've been naughty)
268
432
 
269
- # Check current token status
433
+ # Check current token status - your digital wallet
270
434
  puts rate_limiter.configuration
271
435
  # => {:requests_per_second=>1.0, :burst_size=>2, :available_tokens=>2}
272
436
 
273
- # Manual rate limiting control
274
- rate_limiter.throttle # Waits if necessary and consumes token
437
+ # Manual rate limiting control - take the wheel
438
+ rate_limiter.throttle # Waits if necessary and consumes token (om nom nom)
275
439
  ```
276
440
 
277
- ### Token Bucket Algorithm
441
+ ### 🪣 The Magic Behind the Curtain - Token Bucket Algorithm
442
+
443
+ *The elegant dance of digital tokens that keeps everything flowing smoothly.*
278
444
 
279
445
  The rate limiter uses a token bucket algorithm:
280
446
 
@@ -283,20 +449,28 @@ The rate limiter uses a token bucket algorithm:
283
449
  - **Refill Rate**: How quickly tokens are replenished
284
450
  - **Thread Safe**: Handles concurrent requests safely
285
451
 
286
- ## Development
452
+ ## 🛠️ Join the Lunar Development Cult
287
453
 
288
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
454
+ *Want to contribute to the cosmic cause? We welcome fellow moon enthusiasts!*
289
455
 
290
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
456
+ After checking out the repo, run `bin/setup` to install dependencies (like preparing your lunar laboratory). Then, run `rake spec` to run the tests (because even moon phases need quality control). You can also run `bin/console` for an interactive prompt that will allow you to experiment (perfect for midnight coding sessions under the actual moon).
291
457
 
292
- ## Contributing
458
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org) (sharing lunar magic with the world!).
293
459
 
294
- Bug reports and pull requests are welcome on GitHub at https://github.com/dklima/moon_phase_tracker. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/dklima/moon_phase_tracker/blob/main/CODE_OF_CONDUCT.md).
460
+ ## 🤝 Become a Lunar Contributor
295
461
 
296
- ## License
462
+ *Every great gem needs a community of stargazers and code wizards.*
463
+
464
+ Bug reports and pull requests are welcome on GitHub at https://github.com/dklima/moon_phase_tracker. Found a bug? Think of it as discovering a new lunar crater - exciting stuff! This project is intended to be a safe, welcoming space for collaboration, where all contributors can shine as bright as a Full Moon. Contributors are expected to adhere to the [code of conduct](https://github.com/dklima/moon_phase_tracker/blob/main/CODE_OF_CONDUCT.md).
465
+
466
+ ## 📜 Legal Lunar Stuff
467
+
468
+ *Even moon phases need proper paperwork.*
297
469
 
298
470
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
299
471
 
300
- ## Code of Conduct
472
+ ## 🌟 Lunar Community Guidelines
473
+
474
+ *We believe in creating a space as welcoming as moonlight on a summer evening.*
301
475
 
302
- Everyone interacting in the MoonPhaseTracker project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/dklima/moon_phase_tracker/blob/main/CODE_OF_CONDUCT.md).
476
+ Everyone interacting in the MoonPhaseTracker project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/dklima/moon_phase_tracker/blob/main/CODE_OF_CONDUCT.md). Together, we create a community as harmonious as the lunar cycles themselves.
data/Rakefile CHANGED
@@ -1,8 +1,8 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler/gem_tasks'
4
- require 'rspec/core/rake_task'
5
-
6
- RSpec::Core::RakeTask.new(:spec)
7
-
8
- task default: :spec
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -1,100 +1,100 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Add the lib directory to the load path
5
- $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
6
-
7
- require 'moon_phase_tracker'
8
-
9
- puts '=== Moon Phase Tracker - 8 Phases Example ==='
10
- puts 'Demonstrating all 8 lunar phases (4 major + 4 intermediate)'
11
- puts ''
12
-
13
- begin
14
- # Example 1: Get all 8 phases for August 2025
15
- puts '🌙 All phases for August 2025:'
16
- puts '=' * 40
17
- phases = MoonPhaseTracker.all_phases_for_month(2025, 8)
18
-
19
- if phases.any?
20
- phases.each do |phase|
21
- prefix = phase.interpolated ? '~ ' : ' '
22
- puts "#{prefix}#{phase}"
23
- end
24
-
25
- major_count = phases.count { |p| !p.interpolated }
26
- interpolated_count = phases.count(&:interpolated)
27
-
28
- puts ''
29
- puts "Total: #{phases.size} phases (#{major_count} major, #{interpolated_count} interpolated)"
30
- puts '~ indicates interpolated phases'
31
- else
32
- puts 'No phases found for this period.'
33
- end
34
-
35
- puts ''
36
- puts '-' * 50
37
- puts ''
38
-
39
- # Example 2: Compare 4 vs 8 phases
40
- puts '🔍 Comparison: 4 Major Phases vs 8 Complete Phases'
41
- puts '=' * 55
42
-
43
- major_phases = MoonPhaseTracker.phases_for_month(2025, 8)
44
- all_phases = MoonPhaseTracker.all_phases_for_month(2025, 8)
45
-
46
- puts "Major phases only (#{major_phases.size}):"
47
- major_phases.each { |phase| puts " #{phase}" }
48
-
49
- puts ''
50
- puts "All phases including interpolated (#{all_phases.size}):"
51
- all_phases.each do |phase|
52
- prefix = phase.interpolated ? '~ ' : ' '
53
- puts "#{prefix}#{phase}"
54
- end
55
-
56
- puts ''
57
- puts '-' * 50
58
- puts ''
59
-
60
- # Example 3: Show phase symbols
61
- puts '🎭 Phase Symbols Reference'
62
- puts '=' * 30
63
- MoonPhaseTracker::Phase::PHASE_SYMBOLS.each do |type, symbol|
64
- name = type.to_s.split('_').map(&:capitalize).join(' ')
65
- puts "#{symbol} #{name}"
66
- end
67
-
68
- puts ''
69
- puts '-' * 50
70
- puts ''
71
-
72
- # Example 4: Get phases from a specific date with 8 phases
73
- puts '📅 8 Phases from a specific date (2025-08-01, 2 cycles)'
74
- puts '=' * 60
75
- date_phases = MoonPhaseTracker.all_phases_from_date('2025-08-01', 2)
76
-
77
- if date_phases.any?
78
- date_phases.each do |phase|
79
- prefix = phase.interpolated ? '~ ' : ' '
80
- puts "#{prefix}#{phase}"
81
- end
82
-
83
- major_count = date_phases.count { |p| !p.interpolated }
84
- interpolated_count = date_phases.count(&:interpolated)
85
-
86
- puts ''
87
- puts "Total: #{date_phases.size} phases over 2 lunar cycles"
88
- puts "(#{major_count} major, #{interpolated_count} interpolated)"
89
- end
90
- rescue MoonPhaseTracker::Error => e
91
- puts "Error: #{e.message}"
92
- rescue StandardError => e
93
- puts "Unexpected error: #{e.message}"
94
- puts 'This example requires an active internet connection to fetch moon phase data.'
95
- end
96
-
97
- puts ''
98
- puts 'Note: This example uses real astronomical data from the US Naval Observatory.'
99
- puts 'Interpolated phases (~) are calculated estimates between official phases.'
100
- puts 'All dates are in ISO 8601 format (YYYY-MM-DD) and times are in UTC.'
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Add the lib directory to the load path
5
+ $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
6
+
7
+ require 'moon_phase_tracker'
8
+
9
+ puts '=== Moon Phase Tracker - 8 Phases Example ==='
10
+ puts 'Demonstrating all 8 lunar phases (4 major + 4 intermediate)'
11
+ puts ''
12
+
13
+ begin
14
+ # Example 1: Get all 8 phases for August 2025
15
+ puts '🌙 All phases for August 2025:'
16
+ puts '=' * 40
17
+ phases = MoonPhaseTracker.all_phases_for_month(2025, 8)
18
+
19
+ if phases.any?
20
+ phases.each do |phase|
21
+ prefix = phase.interpolated ? '~ ' : ' '
22
+ puts "#{prefix}#{phase}"
23
+ end
24
+
25
+ major_count = phases.count { |p| !p.interpolated }
26
+ interpolated_count = phases.count(&:interpolated)
27
+
28
+ puts ''
29
+ puts "Total: #{phases.size} phases (#{major_count} major, #{interpolated_count} interpolated)"
30
+ puts '~ indicates interpolated phases'
31
+ else
32
+ puts 'No phases found for this period.'
33
+ end
34
+
35
+ puts ''
36
+ puts '-' * 50
37
+ puts ''
38
+
39
+ # Example 2: Compare 4 vs 8 phases
40
+ puts '🔍 Comparison: 4 Major Phases vs 8 Complete Phases'
41
+ puts '=' * 55
42
+
43
+ major_phases = MoonPhaseTracker.phases_for_month(2025, 8)
44
+ all_phases = MoonPhaseTracker.all_phases_for_month(2025, 8)
45
+
46
+ puts "Major phases only (#{major_phases.size}):"
47
+ major_phases.each { |phase| puts " #{phase}" }
48
+
49
+ puts ''
50
+ puts "All phases including interpolated (#{all_phases.size}):"
51
+ all_phases.each do |phase|
52
+ prefix = phase.interpolated ? '~ ' : ' '
53
+ puts "#{prefix}#{phase}"
54
+ end
55
+
56
+ puts ''
57
+ puts '-' * 50
58
+ puts ''
59
+
60
+ # Example 3: Show phase symbols
61
+ puts '🎭 Phase Symbols Reference'
62
+ puts '=' * 30
63
+ MoonPhaseTracker::Phase::PHASE_SYMBOLS.each do |type, symbol|
64
+ name = type.to_s.split('_').map(&:capitalize).join(' ')
65
+ puts "#{symbol} #{name}"
66
+ end
67
+
68
+ puts ''
69
+ puts '-' * 50
70
+ puts ''
71
+
72
+ # Example 4: Get phases from a specific date with 8 phases
73
+ puts '📅 8 Phases from a specific date (2025-08-01, 2 cycles)'
74
+ puts '=' * 60
75
+ date_phases = MoonPhaseTracker.all_phases_from_date('2025-08-01', 2)
76
+
77
+ if date_phases.any?
78
+ date_phases.each do |phase|
79
+ prefix = phase.interpolated ? '~ ' : ' '
80
+ puts "#{prefix}#{phase}"
81
+ end
82
+
83
+ major_count = date_phases.count { |p| !p.interpolated }
84
+ interpolated_count = date_phases.count(&:interpolated)
85
+
86
+ puts ''
87
+ puts "Total: #{date_phases.size} phases over 2 lunar cycles"
88
+ puts "(#{major_count} major, #{interpolated_count} interpolated)"
89
+ end
90
+ rescue MoonPhaseTracker::Error => e
91
+ puts "Error: #{e.message}"
92
+ rescue StandardError => e
93
+ puts "Unexpected error: #{e.message}"
94
+ puts 'This example requires an active internet connection to fetch moon phase data.'
95
+ end
96
+
97
+ puts ''
98
+ puts 'Note: This example uses real astronomical data from the US Naval Observatory.'
99
+ puts 'Interpolated phases (~) are calculated estimates between official phases.'
100
+ puts 'All dates are in ISO 8601 format (YYYY-MM-DD) and times are in UTC.'
@@ -1,74 +1,74 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require_relative '../lib/moon_phase_tracker'
5
-
6
- puts '=== Moon Phase Tracker - Usage Examples =='
7
- puts
8
-
9
- tracker = MoonPhaseTracker::Tracker.new
10
-
11
- begin
12
- puts '1. Moon phases for August 2025:'
13
- puts '-' * 40
14
- august_phases = MoonPhaseTracker.phases_for_month(2025, 8)
15
- puts tracker.format_phases(august_phases, "#{MoonPhaseTracker::Tracker.month_name(8)} 2025 Phases")
16
- puts
17
-
18
- puts '2. All moon phases in 2025 (first 8):'
19
- puts '-' * 50
20
- year_phases = MoonPhaseTracker.phases_for_year(2025)
21
- puts tracker.format_phases(year_phases.first(8), 'First 8 phases of 2025')
22
- puts
23
-
24
- puts '3. Next 6 phases starting from 2025-08-01:'
25
- puts '-' * 45
26
- future_phases = MoonPhaseTracker.phases_from_date('2025-08-01', 6)
27
- puts tracker.format_phases(future_phases, 'Upcoming phases')
28
- puts
29
-
30
- puts '4. Current month phases:'
31
- puts '-' * 25
32
- current_phases = tracker.current_month_phases
33
- if current_phases.any?
34
- puts tracker.format_phases(current_phases, 'Current month phases')
35
- else
36
- puts 'No phases found for the current month.'
37
- end
38
- puts
39
-
40
- puts '5. Next moon phase:'
41
- puts '-' * 25
42
- next_phase = tracker.next_phase
43
- if next_phase
44
- puts next_phase
45
- puts "Details: #{next_phase.to_h}"
46
- else
47
- puts 'Could not retrieve the next phase.'
48
- end
49
- puts
50
-
51
- puts '6. Different phase representations:'
52
- puts '-' * 42
53
- if august_phases.any?
54
- phase = august_phases.first
55
- puts "String: #{phase}"
56
- puts "Hash: #{phase.to_h}"
57
- puts "Symbol: #{phase.symbol}"
58
- puts "Formatted date: #{phase.formatted_date}"
59
- puts "Formatted time: #{phase.formatted_time}"
60
- end
61
- rescue MoonPhaseTracker::NetworkError => e
62
- puts "Network error: #{e.message}"
63
- puts 'Please check your internet connection.'
64
- rescue MoonPhaseTracker::APIError => e
65
- puts "API error: #{e.message}"
66
- puts 'The service may be temporarily unavailable.'
67
- rescue MoonPhaseTracker::InvalidDateError => e
68
- puts "Date error: #{e.message}"
69
- rescue StandardError => e
70
- puts "Unexpected error: #{e.message}"
71
- end
72
-
73
- puts
74
- puts '=== End of Examples ==='
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/moon_phase_tracker'
5
+
6
+ puts '=== Moon Phase Tracker - Usage Examples =='
7
+ puts
8
+
9
+ tracker = MoonPhaseTracker::Tracker.new
10
+
11
+ begin
12
+ puts '1. Moon phases for August 2025:'
13
+ puts '-' * 40
14
+ august_phases = MoonPhaseTracker.phases_for_month(2025, 8)
15
+ puts tracker.format_phases(august_phases, "#{MoonPhaseTracker::Tracker.month_name(8)} 2025 Phases")
16
+ puts
17
+
18
+ puts '2. All moon phases in 2025 (first 8):'
19
+ puts '-' * 50
20
+ year_phases = MoonPhaseTracker.phases_for_year(2025)
21
+ puts tracker.format_phases(year_phases.first(8), 'First 8 phases of 2025')
22
+ puts
23
+
24
+ puts '3. Next 6 phases starting from 2025-08-01:'
25
+ puts '-' * 45
26
+ future_phases = MoonPhaseTracker.phases_from_date('2025-08-01', 6)
27
+ puts tracker.format_phases(future_phases, 'Upcoming phases')
28
+ puts
29
+
30
+ puts '4. Current month phases:'
31
+ puts '-' * 25
32
+ current_phases = tracker.current_month_phases
33
+ if current_phases.any?
34
+ puts tracker.format_phases(current_phases, 'Current month phases')
35
+ else
36
+ puts 'No phases found for the current month.'
37
+ end
38
+ puts
39
+
40
+ puts '5. Next moon phase:'
41
+ puts '-' * 25
42
+ next_phase = tracker.next_phase
43
+ if next_phase
44
+ puts next_phase
45
+ puts "Details: #{next_phase.to_h}"
46
+ else
47
+ puts 'Could not retrieve the next phase.'
48
+ end
49
+ puts
50
+
51
+ puts '6. Different phase representations:'
52
+ puts '-' * 42
53
+ if august_phases.any?
54
+ phase = august_phases.first
55
+ puts "String: #{phase}"
56
+ puts "Hash: #{phase.to_h}"
57
+ puts "Symbol: #{phase.symbol}"
58
+ puts "Formatted date: #{phase.formatted_date}"
59
+ puts "Formatted time: #{phase.formatted_time}"
60
+ end
61
+ rescue MoonPhaseTracker::NetworkError => e
62
+ puts "Network error: #{e.message}"
63
+ puts 'Please check your internet connection.'
64
+ rescue MoonPhaseTracker::APIError => e
65
+ puts "API error: #{e.message}"
66
+ puts 'The service may be temporarily unavailable.'
67
+ rescue MoonPhaseTracker::InvalidDateError => e
68
+ puts "Date error: #{e.message}"
69
+ rescue StandardError => e
70
+ puts "Unexpected error: #{e.message}"
71
+ end
72
+
73
+ puts
74
+ puts '=== End of Examples ==='
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MoonPhaseTracker
4
+ class LunarCalculator
5
+ SYNODIC_MONTH = 29.530588853
6
+ KNOWN_NEW_MOON_JD = 2451550.26 # Jan 6, 2000 18:14 UTC
7
+
8
+ PHASE_BOUNDARIES = [
9
+ [ 0.0, "New Moon" ],
10
+ [ 0.0625, "Waxing Crescent" ],
11
+ [ 0.1875, "First Quarter" ],
12
+ [ 0.3125, "Waxing Gibbous" ],
13
+ [ 0.4375, "Full Moon" ],
14
+ [ 0.5625, "Waning Gibbous" ],
15
+ [ 0.6875, "Last Quarter" ],
16
+ [ 0.8125, "Waning Crescent" ]
17
+ ].freeze
18
+
19
+ def phase_at(date)
20
+ time = coerce_to_time(date)
21
+ fraction = cycle_position(time)
22
+ age = lunar_age(time)
23
+ illum = illumination_from_fraction(fraction)
24
+ name = classify_phase(fraction)
25
+
26
+ Phase.from_calculation(
27
+ name: name,
28
+ date: time.utc.to_date,
29
+ time: time.utc.strftime("%H:%M"),
30
+ illumination: illum,
31
+ lunar_age: age
32
+ )
33
+ end
34
+
35
+ def illumination(date)
36
+ fraction = cycle_position(coerce_to_time(date))
37
+ illumination_from_fraction(fraction)
38
+ end
39
+
40
+ def lunar_age(date)
41
+ time = coerce_to_time(date)
42
+ jd = to_julian_date(time)
43
+ days_since = jd - KNOWN_NEW_MOON_JD
44
+ days_since % SYNODIC_MONTH
45
+ end
46
+
47
+ def cycle_position(date)
48
+ time = coerce_to_time(date)
49
+ jd = to_julian_date(time)
50
+ days_since = jd - KNOWN_NEW_MOON_JD
51
+ (days_since % SYNODIC_MONTH) / SYNODIC_MONTH
52
+ end
53
+
54
+ private
55
+
56
+ def coerce_to_time(input)
57
+ case input
58
+ when Time then input.utc
59
+ when DateTime then input.to_time.utc
60
+ when Date then Time.utc(input.year, input.month, input.day, 12)
61
+ when String then coerce_to_time(Date.parse(input))
62
+ else raise ArgumentError, "Expected Date, Time, DateTime, or String. Got #{input.class}"
63
+ end
64
+ rescue Date::Error => e
65
+ raise ArgumentError, "Cannot parse date string: #{input.inspect} (#{e.message})"
66
+ end
67
+
68
+ def to_julian_date(time)
69
+ utc = time.utc
70
+ y = utc.year
71
+ m = utc.month
72
+ d = utc.day + (utc.hour + utc.min / 60.0 + utc.sec / 3600.0) / 24.0
73
+
74
+ if m <= 2
75
+ y -= 1
76
+ m += 12
77
+ end
78
+
79
+ a = (y / 100).floor
80
+ b = 2 - a + (a / 4).floor
81
+
82
+ (365.25 * (y + 4716)).floor + (30.6001 * (m + 1)).floor + d + b - 1524.5
83
+ end
84
+
85
+ def illumination_from_fraction(fraction)
86
+ ((1 - Math.cos(2 * Math::PI * fraction)) / 2.0 * 100).round(2)
87
+ end
88
+
89
+ def classify_phase(fraction)
90
+ PHASE_BOUNDARIES.reverse_each do |threshold, name|
91
+ return name if fraction >= threshold
92
+ end
93
+ PHASE_BOUNDARIES.first.last
94
+ end
95
+ end
96
+ end
@@ -36,7 +36,10 @@ module MoonPhaseTracker
36
36
  symbol: phase_attributes[:symbol],
37
37
  iso_date: phase_attributes[:date]&.iso8601,
38
38
  utc_time: phase_attributes[:time]&.utc&.iso8601,
39
- interpolated: phase_attributes[:interpolated]
39
+ interpolated: phase_attributes[:interpolated],
40
+ source: phase_attributes[:source],
41
+ illumination: phase_attributes[:illumination],
42
+ lunar_age: phase_attributes[:lunar_age]
40
43
  }
41
44
  end
42
45
  end
@@ -18,10 +18,15 @@ module MoonPhaseTracker
18
18
  nil
19
19
  end
20
20
 
21
- def self.parse_time(time_string)
21
+ def self.parse_time(time_string, date = nil)
22
22
  return nil unless time_string
23
23
 
24
- Time.parse("#{time_string} UTC")
24
+ if date
25
+ hour, minute = time_string.split(":").map(&:to_i)
26
+ Time.utc(date.year, date.month, date.day, hour, minute)
27
+ else
28
+ Time.parse("#{time_string} UTC")
29
+ end
25
30
  rescue ArgumentError
26
31
  nil
27
32
  end
@@ -9,14 +9,29 @@ module MoonPhaseTracker
9
9
  class Phase
10
10
  include Comparable
11
11
 
12
- attr_reader :name, :date, :time, :phase_type, :interpolated
12
+ attr_reader :name, :date, :time, :phase_type, :interpolated,
13
+ :source, :illumination, :lunar_age
13
14
 
14
- def initialize(phase_data, interpolated: false)
15
+ def initialize(phase_data, interpolated: false, source: :api, illumination: nil, lunar_age: nil)
15
16
  @name = phase_data["phase"]
16
17
  @phase_type = Mapper.map_phase_type(@name)
17
18
  @date = Parser.build_date(phase_data)
18
- @time = Parser.parse_time(phase_data["time"])
19
+ @time = Parser.parse_time(phase_data["time"], @date)
19
20
  @interpolated = interpolated
21
+ @source = source
22
+ @illumination = illumination
23
+ @lunar_age = lunar_age
24
+ end
25
+
26
+ def self.from_calculation(name:, date:, time:, illumination:, lunar_age:)
27
+ phase_data = {
28
+ "phase" => name,
29
+ "year" => date.year,
30
+ "month" => date.month,
31
+ "day" => date.day,
32
+ "time" => time
33
+ }
34
+ new(phase_data, source: :calculated, illumination: illumination, lunar_age: lunar_age)
20
35
  end
21
36
 
22
37
  def formatted_date
@@ -42,7 +57,10 @@ module MoonPhaseTracker
42
57
  date: @date,
43
58
  time: @time,
44
59
  symbol: symbol,
45
- interpolated: @interpolated
60
+ interpolated: @interpolated,
61
+ source: @source,
62
+ illumination: @illumination,
63
+ lunar_age: @lunar_age
46
64
  }
47
65
 
48
66
  Formatter.build_hash_representation(phase_attributes)
@@ -15,7 +15,7 @@ module MoonPhaseTracker
15
15
  estimated_date = calculate_estimated_date(last_phase)
16
16
  phase_data = build_estimated_phase_data(estimated_date, last_phase)
17
17
 
18
- Phase.new(phase_data)
18
+ Phase.new(phase_data, source: :interpolated)
19
19
  rescue Date::Error, ArgumentError => e
20
20
  warn "Failed to estimate next cycle phase: #{e.class}"
21
21
  nil
@@ -60,7 +60,7 @@ module MoonPhaseTracker
60
60
  return nil unless intermediate_datetime
61
61
 
62
62
  phase_data = build_phase_data(intermediate_datetime, config[:name])
63
- Phase.new(phase_data, interpolated: true)
63
+ Phase.new(phase_data, interpolated: true, source: :interpolated)
64
64
  rescue Date::Error, ArgumentError => e
65
65
  warn "Failed to create intermediate phase: #{e.class}"
66
66
  nil
@@ -43,6 +43,20 @@ module MoonPhaseTracker
43
43
  phases_from_date(Date.today, 1).first
44
44
  end
45
45
 
46
+ def phase_at(date)
47
+ parsed = @date_parser.parse(date)
48
+ calculator.phase_at(parsed)
49
+ end
50
+
51
+ def illumination(date)
52
+ parsed = @date_parser.parse(date)
53
+ calculator.illumination(parsed)
54
+ end
55
+
56
+ def current_phase
57
+ calculator.phase_at(Time.now.utc)
58
+ end
59
+
46
60
  def current_month_phases
47
61
  today = Date.today
48
62
  phases_for_month(today.year, today.month)
@@ -77,6 +91,10 @@ module MoonPhaseTracker
77
91
 
78
92
  private
79
93
 
94
+ def calculator
95
+ @calculator ||= LunarCalculator.new
96
+ end
97
+
80
98
  MONTH_NAMES = %w[
81
99
  January February March April May June
82
100
  July August September October November December
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MoonPhaseTracker
4
- VERSION = "1.3.2"
4
+ VERSION = "1.4.0"
5
5
  end
@@ -5,6 +5,7 @@ require_relative "moon_phase_tracker/rate_limiter"
5
5
  require_relative "moon_phase_tracker/client"
6
6
  require_relative "moon_phase_tracker/phase"
7
7
  require_relative "moon_phase_tracker/phase_calculator"
8
+ require_relative "moon_phase_tracker/lunar_calculator"
8
9
  require_relative "moon_phase_tracker/tracker"
9
10
 
10
11
  module MoonPhaseTracker
@@ -39,4 +40,20 @@ module MoonPhaseTracker
39
40
  def self.all_phases_from_date(date, num_cycles = 3)
40
41
  Tracker.new.all_phases_from_date(date, num_cycles)
41
42
  end
43
+
44
+ def self.calculator
45
+ @calculator ||= LunarCalculator.new
46
+ end
47
+
48
+ def self.phase_at(date)
49
+ calculator.phase_at(date)
50
+ end
51
+
52
+ def self.illumination(date)
53
+ calculator.illumination(date)
54
+ end
55
+
56
+ def self.current_phase
57
+ calculator.phase_at(Time.now.utc)
58
+ end
42
59
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: moon_phase_tracker
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.2
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel K Lima
@@ -56,6 +56,7 @@ files:
56
56
  - examples/usage_example.rb
57
57
  - lib/moon_phase_tracker.rb
58
58
  - lib/moon_phase_tracker/client.rb
59
+ - lib/moon_phase_tracker/lunar_calculator.rb
59
60
  - lib/moon_phase_tracker/phase.rb
60
61
  - lib/moon_phase_tracker/phase/comparator.rb
61
62
  - lib/moon_phase_tracker/phase/formatter.rb