No release in over 3 years
A Ruby gem to track moon phases using the US Naval Observatory API. Shows moon phases for specific dates, months, or years for lunar calendar scheduling.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

~> 2.7
~> 0.4
 Project Readme

🌙 MoonPhaseTracker - Your Cosmic Calendar Companion

"Because every developer deserves to howl at the right moon."

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.

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.

🎯 Accuracy: ~85-90% (Good enough for lunar calendars, probably too precise for howling)

✨ What Makes This Gem Shine

  • 🌑 Time Travel Through Lunar Cycles - Query moon phases by month, year, or from any date your heart desires
  • 🌒 The Full Lunar Symphony - Complete 8-phase support (4 major crescendos + 4 melodic interludes)
  • 🌓 Simple as Moonlight - An interface so intuitive, even a sleepy developer can use it at 3 AM
  • 🌔 Navy-Grade Precision - Official USNO data because the Navy doesn't mess around with moon phases
  • 🌕 Emoji Magic - Visual moon phases that spark joy in your terminal
  • 🌖 Mathematical Lunar Poetry - Interpolated intermediate phases for when precision meets artistry
  • 🌗 Truth in Advertising - Clear indicators showing official vs calculated phases (no moon phase imposters here!)
  • 🌘 Memory Like an Elephant - Automatic caching because nobody likes waiting for the moon
  • Bulletproof & Graceful - Error handling that fails as elegantly as moonbeams
  • 🚦 API Etiquette - Respectful rate limiting because even the Navy deserves a break

🚀 Summoning the Lunar Powers

Ready to add some celestial magic to your Ruby project?

Add this line to your application's Gemfile:

gem 'moon_phase_tracker'

And then execute:

bundle install

Or install it directly:

gem install moon_phase_tracker

🎭 Lunar Adventures Await

Time to dance with the moon phases like a cosmic choreographer!

🌟 The Essential Four - Your Lunar Greatest Hits

Start your journey with the moon's main characters: the celestial quartet that's been stealing hearts since ancient times.

require 'moon_phase_tracker'

# Moon phases for August 2025 (4 major phases only)
phases = MoonPhaseTracker.phases_for_month(2025, 8)
puts phases.first.to_s
# => "🌑 New Moon - 2025-08-04 at 11:13"
# (Perfect timing for new beginnings!)

# All phases for 2025 - your yearly lunar roadmap
year_phases = MoonPhaseTracker.phases_for_year(2025)

# Next 6 phases from a specific date - peek into the future
future_phases = MoonPhaseTracker.phases_from_date("2025-08-01", 6)

🎨 The Full Lunar Canvas - All 8 Phases in Their Glory

For the lunar perfectionists who want the complete story, not just the highlights reel.

require 'moon_phase_tracker'

# All 8 phases for August 2025 - the complete lunar symphony
all_phases = MoonPhaseTracker.all_phases_for_month(2025, 8)
all_phases.each do |phase|
  indicator = phase.interpolated ? "~ " : "  "  # ~ means "calculated with love"
  puts "#{indicator}#{phase}"
end
# =>   🌑 New Moon - 2025-08-04 at 11:13
# => ~ 🌒 Waxing Crescent - 2025-08-08 at 03:16
# =>   🌓 First Quarter - 2025-08-12 at 15:19
# => ~ 🌔 Waxing Gibbous - 2025-08-15 at 16:53
# =>   🌕 Full Moon - 2025-08-19 at 18:26
# => ~ 🌖 Waning Gibbous - 2025-08-22 at 13:56 
# =>   🌗 Last Quarter - 2025-08-26 at 09:26
# => ~ 🌘 Waning Crescent - 2025-08-29 at 02:56

# All 8 phases for entire year - your cosmic annual planner
all_year_phases = MoonPhaseTracker.all_phases_for_year(2025)

# All 8 phases from specific date (2 lunar cycles) - the extended edition
extended_phases = MoonPhaseTracker.all_phases_from_date("2025-08-01", 2)

Filtering Intermediate Phases

To extract only the 4 calculated intermediate phases from a month:

require 'moon_phase_tracker'

begin
  all_phases = MoonPhaseTracker.all_phases_for_month(2025, 8)
rescue MoonPhaseTracker::NetworkError, MoonPhaseTracker::APIError => e
  puts "API unavailable: #{e.message}"
  all_phases = []
end

interpolated = all_phases.select(&:interpolated)

interpolated.each do |phase|
  puts "#{phase.symbol} #{phase.name}#{phase.formatted_date} (source: #{phase.source})"
end
# => 🌒 Waxing Crescent — 2025-08-08 (source: interpolated)
# => 🌔 Waxing Gibbous — 2025-08-15 (source: interpolated)
# => 🌖 Waning Gibbous — 2025-08-22 (source: interpolated)
# => 🌘 Waning Crescent — 2025-08-29 (source: interpolated)

Edge cases to handle:

  • A month at the boundary of the USNO data range may return fewer than 4 major phases, which means fewer interpolated phases (or none)
  • all_phases_for_month can return between 4 and 8 phases depending on how many intermediate phases fall within the calendar month
  • The interpolated attribute is always false for API phases and true for calculated intermediates — there's no ambiguous state

Dependencies: The gem requires Ruby >= 3.2.0. Install via gem install moon_phase_tracker or add gem 'moon_phase_tracker' to your Gemfile. No external services needed for LunarCalculator methods; only the phases_for_* and all_phases_for_* methods require network access to the USNO API.

🔮 Instant Moon Phase - No API, No Waiting

What phase is the moon right now? How bright is it? Pure math, instant answer.

These methods use a synodic month calculation — no API calls, no network, no rate limits. Perfect for display UIs and real-time widgets.

require 'moon_phase_tracker'

# What's the moon doing right now?
phase = MoonPhaseTracker.current_phase
puts phase.to_s
# => "🌔 Waxing Gibbous - 2025-08-15 at 14:30"
puts phase.illumination  # => 78.5 (percent)
puts phase.lunar_age     # => 12.3 (days since last new moon)
puts phase.source        # => :calculated

# Phase at any date/time - past or future
phase = MoonPhaseTracker.phase_at(Time.utc(2025, 6, 11, 7, 44))
puts phase.name          # => "Full Moon"
puts phase.illumination  # => ~100.0

# Just the illumination percentage
illum = MoonPhaseTracker.illumination(Date.today)
puts "#{illum.round(1)}% illuminated"

# Works with Date, Time, DateTime, or String
MoonPhaseTracker.phase_at(Date.new(2025, 1, 15))
MoonPhaseTracker.phase_at("2025-01-15")
MoonPhaseTracker.phase_at(DateTime.now)

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.

🎯 The Tracker Class - Your Personal Lunar Assistant

Think of it as your moon phase butler, always ready to serve up cosmic timing with a bow tie.

tracker = MoonPhaseTracker::Tracker.new

# Automatic formatting for display - because presentation matters
august_phases = tracker.phases_for_month(2025, 8)
formatted = tracker.format_phases(august_phases, "August Phases")
puts formatted

# Next moon phase - your cosmic fortune telling
next_phase = tracker.next_phase
puts next_phase.to_s

# Current month phases - what's happening in your lunar neighborhood
current_month = tracker.current_month_phases

# Instant phase at any date - no API call needed
phase = tracker.phase_at("2025-06-11")
puts phase.illumination  # => Float (percent)

# Just the illumination number
illum = tracker.illumination("2025-06-11")

# Current phase right now
puts tracker.current_phase

🔍 Getting Personal with Moon Phases

Each phase has its own personality - let's get to know them intimately.

phase = phases.first

# Phase information
puts phase.name          # "New Moon"
puts phase.symbol        # "🌑" (isn't it beautiful?)
puts phase.formatted_date # "2025-08-04"
puts phase.formatted_time # "11:13" (UTC - the moon doesn't do timezones)
puts phase.interpolated   # false (certified genuine USNO data)

# Working with interpolated phases - the mathematically gifted ones
interpolated_phase = all_phases.find(&:interpolated)
puts interpolated_phase.interpolated  # true (calculated with mathematical precision)
puts interpolated_phase.name         # "Waxing Crescent"
puts interpolated_phase.symbol       # "🌒" (still adorable)

# Complete hash with all data
details = phase.to_h
# => {
#   name: "New Moon",
#   phase_type: :new_moon,
#   date: "2025-08-04",
#   time: "11:13",
#   symbol: "🌑",
#   iso_date: "2025-08-04",
#   utc_time: "2025-08-04T11:13:00Z",
#   interpolated: false,
#   source: :api,           # :api, :interpolated, or :calculated
#   illumination: nil,      # Float for calculated phases, nil for API
#   lunar_age: nil          # Float for calculated phases, nil for API
# }

# Date checks - lunar detective work
phase.in_month?(2025, 8)  # => true (August moon confirmed!)
phase.in_year?(2025)      # => true (definitely a 2025 vintage)

🛡️ When the Moon Plays Hard to Get

Even celestial bodies have bad days. Here's how to handle lunar tantrums gracefully.

All errors inherit from MoonPhaseTracker::Error, so you can rescue broadly or specifically:

Error Class Raised When Example Trigger
NetworkError Connection timeout, DNS failure, socket error USNO API is down, no internet
APIError Non-200 response or invalid JSON from the API 500 server error, malformed response
InvalidDateError Input validation fails before any API call Month 13, year 1500, unparseable string
begin
  phases = MoonPhaseTracker.phases_for_month(2025, 8)
rescue MoonPhaseTracker::NetworkError => e
  puts "Network hiccup: #{e.message}"
rescue MoonPhaseTracker::APIError => e
  puts "API tantrum: #{e.message}"
rescue MoonPhaseTracker::InvalidDateError => e
  puts "Time travel error: #{e.message}"
end

Since LunarCalculator is pure math (no network), it makes a natural fallback when the API is unavailable:

def moon_phases_for(year, month)
  MoonPhaseTracker.all_phases_for_month(year, month)
rescue MoonPhaseTracker::NetworkError, MoonPhaseTracker::APIError
  # API down — generate approximate phases from the calculator
  first_day = Date.new(year, month, 1)
  last_day = first_day.next_month - 1
  calculator = MoonPhaseTracker::LunarCalculator.new

  (first_day..last_day).each_with_object([]) do |date, phases|
    phase = calculator.phase_at(date)
    prev = phases.last
    phases << phase if prev.nil? || prev.name != phase.name
  end
end

The fallback returns one Phase per named transition in the month. The source field will be :calculated instead of :api, so downstream code can distinguish degraded results.

🎪 Real-World Lunar Magic

Because theory is nice, but seeing the moon phases in action is where the real magic happens.

Hybrid Architecture: USNO for Scheduling, Calculator for Display

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.

# app/jobs/sync_lunar_phases_job.rb
class SyncLunarPhasesJob < ApplicationJob
  def perform(year = Date.current.year)
    phases = MoonPhaseTracker.all_phases_for_year(year)

    phases.each do |phase|
      LunarPhase.upsert(
        {
          name: phase.name,
          phase_type: phase.phase_type.to_s,
          exact_at: phase.to_h[:utc_time],
          source: phase.source.to_s
        },
        unique_by: :exact_at
      )
    end
  end
end
# app/models/lunar_phase.rb
class LunarPhase < ApplicationRecord
  scope :upcoming, -> { where("exact_at > ?", Time.current).order(:exact_at) }
  scope :next_full_moon, -> { upcoming.where(phase_type: "full_moon").first }
end
# app/helpers/moon_helper.rb
module MoonHelper
  def moon_badge(date = Date.current)
    phase = MoonPhaseTracker.phase_at(date)
    "#{phase.symbol} #{phase.illumination.round}%"
  end
end

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.

See the examples/usage_example.rb and examples/eight_phases_example.rb files for more usage examples.

📚 The Lunar Grimoire - Complete API Spellbook

Your comprehensive guide to all the lunar incantations at your disposal.

🎭 The Core Cast - 4 Major Phase Methods

The essential methods that'll get you 80% of your lunar needs covered.

  • MoonPhaseTracker.phases_for_month(year, month) - Major phases for a specific month
  • MoonPhaseTracker.phases_for_year(year) - All major phases for a year
  • MoonPhaseTracker.phases_from_date(date, num_phases) - Major phases from a specific date

🔮 Instant Lookup - No API Required

Pure math, zero latency. For when you need the moon right now.

  • MoonPhaseTracker.phase_at(date) - Phase at any date/time (returns Phase with illumination)
  • MoonPhaseTracker.illumination(date) - Illumination percentage (returns Float 0..100)
  • MoonPhaseTracker.current_phase - Phase right now (shortcut for phase_at(Time.now.utc))

No API calls — pure math under the hood.

🌈 The Full Spectrum - 8-Phase Methods for Lunar Completionists

For those who believe in doing things thoroughly (and slightly obsessively).

  • MoonPhaseTracker.all_phases_for_month(year, month) - All 8 phases for a specific month
  • MoonPhaseTracker.all_phases_for_year(year) - All 8 phases for a year
  • MoonPhaseTracker.all_phases_from_date(date, num_cycles) - All 8 phases from a specific date

🎨 Meet the Lunar Cast - All 8 Phase Personalities

🏆 The Main Characters (Official USNO Data)

These are the A-listers - certified by the Navy, guaranteed to impress.

  • 🌑 :new_moon - New Moon (The mysterious beginning)
  • 🌓 :first_quarter - First Quarter (Half-lit and growing strong)
  • 🌕 :full_moon - Full Moon (The showstopper, the main event)
  • 🌗 :last_quarter - Last Quarter (Gracefully waning)

🎭 The Supporting Cast (Interpolated ~85-90% accuracy)

The understudies that complete the story - mathematically calculated with love.

  • 🌒 :waxing_crescent - Waxing Crescent (The optimistic youngster)
  • 🌔 :waxing_gibbous - Waxing Gibbous (Almost there, building anticipation)
  • 🌖 :waning_gibbous - Waning Gibbous (The wise elder, still radiant)
  • 🌘 :waning_crescent - Waning Crescent (The gentle farewell)

🏷️ Phase Sources - Know Where Your Data Comes From

Every phase carries a passport stamped with its origin story.

Source Meaning When
:api Official USNO data phases_for_month, phases_for_year, etc.
:interpolated Calculated between two API phases all_phases_for_month, all_phases_for_year, etc.
:calculated Pure synodic math model phase_at, illumination, current_phase
phase = MoonPhaseTracker.phase_at(Date.today)
phase.source        # => :calculated
phase.illumination  # => 65.3 (percent)
phase.lunar_age     # => 10.2 (days since last new moon)

🔬 Accuracy & Validation

Interpolated Phases — Known Limitations

The 4 intermediate phases (Waxing Crescent, Waxing Gibbous, Waning Gibbous, Waning Crescent) are not from the USNO API. They're calculated by splitting the time interval between two consecutive major phases in half.

This approach assumes uniform phase spacing within each quarter of the lunar cycle. In reality, the Moon's orbit is elliptical — it moves faster at perigee and slower at apogee. The practical consequences:

Limitation Impact Magnitude
Phase timing error Interpolated dates can drift from true astronomical timing Up to ~12 hours
Illumination gap The calculator's cosine model smooths over orbital eccentricity ~2-5% near quarter phases
Boundary misclassification Near phase transitions, the named phase may differ from USNO Within ~6 hours of boundaries
No libration modeling The synodic model ignores lunar libration and perturbations Negligible for phase naming

For precision-critical applications (astronomical scheduling, scientific observations, tidal calculations), always use the :api source phases. The interpolated and calculated paths are designed for display and UX — calendars, widgets, badges, content scheduling.

Calculator (Synodic Model) — What It Can and Can't Do

The LunarCalculator uses a fixed synodic month of 29.530588853 days anchored to a known New Moon epoch (January 6, 2000, 18:14 UTC). It gives you:

  • Correct named phase ~99% of the time — the 8 phase boundaries divide the cycle into equal 1/8th arcs
  • Illumination via cosine curve(1 - cos(2π × fraction)) / 2 approximates the visual illumination percentage
  • Instant results for any date from 1800 to 2100 — no network, no rate limits

What it doesn't model: orbital eccentricity, solar perturbations, atmospheric refraction, or the equation of time. These factors accumulate over centuries, so dates far from the epoch will drift more.

Validating Against USNO Data

You can cross-check any calculated phase against the authoritative API data:

require 'moon_phase_tracker'

date = Date.new(2025, 8, 19)

# Calculator path (instant, approximate)
calc = MoonPhaseTracker.phase_at(date)

# API path (network call, authoritative)
api_phases = MoonPhaseTracker.phases_for_month(2025, 8)
api = api_phases.find { |p| p.date == date }

if api
  puts "Calculator: #{calc.name} (#{calc.illumination.round(1)}%)"
  puts "USNO API:   #{api.name}"
  puts "Phase match: #{calc.name == api.name}"
  puts "Date delta:  #{(calc.date - api.date).to_i} days"
end

For systematic validation, compare an entire year's calculated phases against API data:

tracker = MoonPhaseTracker::Tracker.new
api_phases = tracker.phases_for_year(2025)
calculator = MoonPhaseTracker::LunarCalculator.new

mismatches = api_phases.count do |api_phase|
  calc_phase = calculator.phase_at(api_phase.date)
  calc_phase.name != api_phase.name
end

accuracy = ((api_phases.size - mismatches).to_f / api_phases.size * 100).round(1)
puts "#{accuracy}% phase name accuracy (#{mismatches}/#{api_phases.size} mismatches)"

📦 Multi-Year Queries, Caching, and Rate Limits

What the Gem Caches (and What It Doesn't)

The Client has one internal cache: @uri_cache, keyed by endpoint + params. It caches parsed URI objects, not API responses. Calling phases_for_year(2025) twice on the same Tracker instance reuses the parsed URI but makes two network requests.

The gem has no built-in response cache. This is intentional — caching strategy depends on your application (Redis, Memcached, database, in-memory), so it belongs in your layer.

Avoiding Redundant API Calls for the Same Month

phases_for_month(year, month) internally calls phases_for_year(year) and filters by month. This means querying January, February, and March of 2025 makes three identical API calls to the same year endpoint:

tracker = MoonPhaseTracker::Tracker.new

# BAD: 3 API calls for the same year
jan = tracker.phases_for_month(2025, 1)
feb = tracker.phases_for_month(2025, 2)
mar = tracker.phases_for_month(2025, 3)

Fetch the year once and filter locally:

tracker = MoonPhaseTracker::Tracker.new

# GOOD: 1 API call, filter in Ruby
all_2025 = tracker.phases_for_year(2025)
jan = all_2025.select { |p| p.in_month?(2025, 1) }
feb = all_2025.select { |p| p.in_month?(2025, 2) }
mar = all_2025.select { |p| p.in_month?(2025, 3) }

The same applies to all_phases_for_month — use all_phases_for_year once and filter.

Rate Limiter Scope

The rate limiter is per-Tracker instance. Each Tracker.new creates its own Client with its own token bucket. Multiple trackers (e.g. one per background worker) each enforce their own rate limit independently.

Multi-Year Strategy

The USNO API returns all phases for a given year in a single response (~50 phases per year). Querying 10 years means 10 API calls, not 120 month-by-month calls. Use phases_for_year as the batch unit:

# Efficient: 1 API call per year, rate limiter spaces them automatically
tracker = MoonPhaseTracker::Tracker.new

phases_by_year = (2020..2030).each_with_object({}) do |year, acc|
  acc[year] = tracker.all_phases_for_year(year)
end

# 11 API calls total, ~11 seconds at default rate (1 req/sec)
# Each year returns ~50 phases (major) or ~100 phases (all 8)

For faster bulk loading, increase the rate limit:

fast_limiter = MoonPhaseTracker::RateLimiter.new(
  requests_per_second: 3.0,
  burst_size: 3
)
tracker = MoonPhaseTracker::Tracker.new(rate_limiter: fast_limiter)

# Same 11 calls, now ~4 seconds
(2020..2030).each { |year| tracker.phases_for_year(year) }

Adding a Response Cache

Lunar data is static per year, so a long TTL works well. A pattern with Rails:

class LunarDataService
  def phases_for_year(year)
    Rails.cache.fetch("lunar_phases/#{year}", expires_in: 30.days) do
      MoonPhaseTracker.all_phases_for_year(year).map(&:to_h)
    end
  end

  def phases_for_range(start_year, end_year)
    (start_year..end_year).flat_map { |year| phases_for_year(year) }
  end
end

This gives you: one API call per year, cached for 30 days, rate-limited automatically. The to_h serialization ensures the cache stores plain hashes (safe for any cache backend).

Combining API and Calculator for Multi-Year Systems

For applications that span multiple years, the recommended architecture:

Time Range Source Why
Historical data (past years) API, cached in database Exact dates, fetch once, query forever
Current year API, cached with shorter TTL Re-fetch quarterly to catch USNO updates
Real-time display LunarCalculator No network, no cache, instant
Far future (> 10 years out) LunarCalculator USNO API range is limited

The database-backed approach from the Hybrid Architecture example works across years — run the sync job once per year for each new year, and the lunar_phases table grows by ~100 rows annually.

⚓ Our Trusted Cosmic Oracle

We get our lunar wisdom from the finest source - the folks who navigate the seven seas by the stars.

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:

All times are provided in Universal Time (UTC) - because the moon doesn't care about your timezone preferences, and neither should precise astronomical data.

🚦 Playing Nice with the Navy - Rate Limiting with Style

Because even cosmic APIs need coffee breaks, and we're not monsters.

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.

🧘 The Zen Approach - Default Rate Limiting

Slow and steady wins the lunar race.

# Default: 1 request per second - the patient approach
tracker = MoonPhaseTracker::Tracker.new

# Check current rate limit configuration - know thy limits
puts tracker.rate_limit_info
# => {:requests_per_second=>1.0, :burst_size=>1, :available_tokens=>1}

# Multiple requests will be automatically rate limited - watch the magic
start = Time.now
tracker.phases_for_year(2025)  # Immediate (first one's free!)
tracker.phases_for_year(2024)  # Waits ~1 second (patience, young padawan)
puts "Total time: #{Time.now - start}s"  # ~1.0 seconds

⚡ Need for Speed? - Custom Rate Limiting

For when you want to live life in the fast lane (but still be respectful).

# Create custom rate limiter: 3 requests per second, burst of 2 - living a little
rate_limiter = MoonPhaseTracker::RateLimiter.new(
  requests_per_second: 3.0,
  burst_size: 2
)

tracker = MoonPhaseTracker::Tracker.new(rate_limiter: rate_limiter)

# Burst requests are immediate, then rate limited - controlled excitement
tracker.phases_for_year(2025)  # Immediate (wheee!)
tracker.phases_for_year(2024)  # Immediate (double wheee!)
tracker.phases_for_year(2023)  # Waits ~0.33 seconds (and... breathe)

🌍 Set It and Forget It - Environment Variables

Configure once, smile forever.

# Set via environment variables - the lazy developer's paradise
ENV["MOON_PHASE_RATE_LIMIT"] = "2.5"
ENV["MOON_PHASE_BURST_SIZE"] = "3"

tracker = MoonPhaseTracker::Tracker.new
puts tracker.rate_limit_info
# => {:requests_per_second=>2.5, :burst_size=>3, :available_tokens=>3}

🏁 Living Dangerously - Disabling Rate Limiting

For the rebels and the reckless (use responsibly, dear lunar adventurer).

# Disable rate limiting completely - for the speed demons
ENV["MOON_PHASE_RATE_LIMIT"] = "0"

tracker = MoonPhaseTracker::Tracker.new
puts tracker.rate_limit_info  # => nil (no limits, no safety net!)

# Or pass nil explicitly - the explicit rebel
tracker = MoonPhaseTracker::Tracker.new(rate_limiter: nil)

🔬 Under the Hood - Rate Limit Monitoring

For the control freaks and performance enthusiasts among us.

rate_limiter = MoonPhaseTracker::RateLimiter.new(
  requests_per_second: 1.0,
  burst_size: 2
)

# Check if request can proceed without waiting - the crystal ball
puts rate_limiter.can_proceed?  # => true (or false if you've been naughty)

# Check current token status - your digital wallet
puts rate_limiter.configuration
# => {:requests_per_second=>1.0, :burst_size=>2, :available_tokens=>2}

# Manual rate limiting control - take the wheel
rate_limiter.throttle  # Waits if necessary and consumes token (om nom nom)

🪣 The Magic Behind the Curtain - Token Bucket Algorithm

The elegant dance of digital tokens that keeps everything flowing smoothly.

The rate limiter uses a token bucket algorithm:

  • Tokens: Represent available requests
  • Bucket Size: Maximum burst requests allowed
  • Refill Rate: How quickly tokens are replenished
  • Thread Safe: Handles concurrent requests safely

🛠️ Join the Lunar Development Cult

Want to contribute to the cosmic cause? We welcome fellow moon enthusiasts!

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).

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 (sharing lunar magic with the world!).

🤝 Become a Lunar Contributor

Every great gem needs a community of stargazers and code wizards.

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.

📜 Legal Lunar Stuff

Even moon phases need proper paperwork.

The gem is available as open source under the terms of the MIT License.

🌟 Lunar Community Guidelines

We believe in creating a space as welcoming as moonlight on a summer evening.

Everyone interacting in the MoonPhaseTracker project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct. Together, we create a community as harmonious as the lunar cycles themselves.