Class: Fasti::CalendarTransition

Inherits:
Object
  • Object
show all
Defined in:
lib/fasti/calendar_transition.rb

Overview

Manages Julian to Gregorian calendar transitions for a specific country.

This class provides country-specific calendar transition data and validation methods to handle historical calendar reforms. It uses Julian Day Numbers (JDN) for precise date calculations and gap management during transition periods.

Examples:

Basic usage

transition = CalendarTransition.new(:gb)
transition.gregorian_start_jdn  #=> 2361222
transition.valid_date?(Date.new(1752, 9, 10))  #=> false (in gap)

Date creation with country-specific transitions

gb = CalendarTransition.new(:gb)
gb.create_date(1752, 9, 2)   #=> Date object with British transition
it = CalendarTransition.new(:it)
it.create_date(1582, 10, 4)  #=> Date object with Italian transition

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(country) ⇒ CalendarTransition

Creates a new CalendarTransition instance for the specified country.

Parameters:

  • country (Symbol)

    Country code (e.g., :gb, :us, :it)



109
110
111
112
# File 'lib/fasti/calendar_transition.rb', line 109

def initialize(country)
  @country = country
  @transition_jdn = TRANSITIONS[@country] || DEFAULT_TRANSITION
end

Instance Attribute Details

#countrySymbol (readonly)

Returns the country code for this transition.

Returns:

  • (Symbol)

    Country code



117
118
119
# File 'lib/fasti/calendar_transition.rb', line 117

def country
  @country
end

Class Method Details

.supported_countriesArray<Symbol>

Returns list of supported countries with transition dates.

Returns:

  • (Array<Symbol>)

    List of supported country codes



102
103
104
# File 'lib/fasti/calendar_transition.rb', line 102

def self.supported_countries
  TRANSITIONS.keys.sort
end

Instance Method Details

#create_date(year, month, day) ⇒ Date

Creates a Date object using the appropriate calendar system for this country.

This method automatically selects Julian or Gregorian calendar based on the date and country’s transition point, ensuring appropriate calendar date representation.

Examples:

gb = CalendarTransition.new(:gb)
# Before British transition - uses Julian
gb.create_date(1752, 9, 2)

# After British transition - uses Gregorian
gb.create_date(1752, 9, 14)

# In gap - raises ArgumentError
gb.create_date(1752, 9, 10)  # => ArgumentError

Parameters:

  • year (Integer)

    Year

  • month (Integer)

    Month (1-12)

  • day (Integer)

    Day of month

Returns:

  • (Date)

    Date object with appropriate calendar system

Raises:

  • (ArgumentError)

    If the date falls in the transition gap (non-existent)



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/fasti/calendar_transition.rb', line 158

def create_date(year, month, day)
  # Try creating the date with Gregorian first (most common case)
  gregorian_date = Date.new(year, month, day, Date::GREGORIAN)

  if gregorian_date.jd >= @transition_jdn
    # Date is on or after transition - use Gregorian
    gregorian_date
  else
    # Date is before transition - use Julian
    julian_date = Date.new(year, month, day, Date::JULIAN)

    # Check if this date would fall in the gap
    if julian_date.jd >= @transition_jdn
      raise ArgumentError,
        "Date #{year}-#{month.to_s.rjust(2, "0")}-#{day.to_s.rjust(2, "0")} " \
        "does not exist in #{@country.upcase} due to calendar transition"
    end

    julian_date
  end
end

#gregorian_start_jdnInteger

Returns the Julian Day Number when this country adopted the Gregorian calendar.

Examples:

gb = CalendarTransition.new(:gb)
gb.gregorian_start_jdn  #=> 2361222

it = CalendarTransition.new(:it)
it.gregorian_start_jdn  #=> 2299161

unknown = CalendarTransition.new(:unknown)
unknown.gregorian_start_jdn  #=> 2299161 (default)

Returns:

  • (Integer)

    Julian Day Number of Gregorian calendar adoption



132
133
134
# File 'lib/fasti/calendar_transition.rb', line 132

def gregorian_start_jdn
  @transition_jdn
end

#transition_infoHash

Returns information about this country’s calendar transition.

Examples:

gb = CalendarTransition.new(:gb)
gb.transition_info
#=> {
#     country: :gb,
#     gregorian_start_jdn: 2361222,
#     gregorian_start_date: #<Date: 1752-09-14>,
#     julian_end_date: #<Date: 1752-09-02>,
#     gap_days: 11
#   }

Returns:

  • (Hash)

    Transition information including JDN and gap details



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/fasti/calendar_transition.rb', line 233

def transition_info
  # Use explicit calendar system to avoid implicit Italian transition
  gregorian_start = Date.jd(@transition_jdn, Date::GREGORIAN)
  julian_end = Date.jd(@transition_jdn - 1, Date::JULIAN)

  # Calculate actual gap in calendar dates, not just JDN difference
  # For example: Oct 4 (Julian) -> Oct 15 (Gregorian) has 10 gap days (5-14)
  if @transition_jdn == DEFAULT_TRANSITION
    # For Italy, Ruby's Date class already handles the gap
    gap_days = 10 # Known historical gap for Italy
  else
    # For other countries, calculate based on calendar date differences
    # The gap is the difference between the date numbers minus 1
    gap_days = gregorian_start.day - julian_end.day - 1

    # Handle cross-month transitions (like Denmark Dec 21 -> Jan 1)
    if gap_days < 0
      # When crossing months, need to account for days in the previous month
      # This is a simplified calculation - may need refinement for complex cases
      prev_month_days = Date.new(julian_end.year, julian_end.month, -1, Date::JULIAN).day
      gap_days = (prev_month_days - julian_end.day) + gregorian_start.day - 1
    end
  end

  {
    country: @country,
    gregorian_start_jdn: @transition_jdn,
    gregorian_start_date: gregorian_start,
    julian_end_date: julian_end,
    gap_days:
  }
end

#valid_date?(date) ⇒ Boolean

Checks if a date exists in this country’s calendar system.

During calendar transitions, certain dates were skipped and never existed. This method validates whether a specific date is valid for this country.

Examples:

gb = CalendarTransition.new(:gb)
date1 = Date.new(1752, 9, 10)  # In British gap
gb.valid_date?(date1)  #=> false

date2 = Date.new(1752, 9, 2)   # Before British gap
gb.valid_date?(date2)  #=> true

Parameters:

  • date (Date)

    Date to validate

Returns:

  • (Boolean)

    true if date exists, false if it falls in transition gap



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/fasti/calendar_transition.rb', line 195

def valid_date?(date)
  # If we're dealing with the default transition (Italy), Ruby handles it correctly
  return true if @transition_jdn == DEFAULT_TRANSITION && date.jd != @transition_jdn - 1

  # For other countries, check if the date falls in a gap
  # We need to check both Julian and Gregorian representations
  begin
    julian_version = Date.new(date.year, date.month, date.day, Date::JULIAN)
    gregorian_version = Date.new(date.year, date.month, date.day, Date::GREGORIAN)

    # If both versions have the same JDN, there's no ambiguity
    return true if julian_version.jd == gregorian_version.jd

    # Check if either version is valid for this country
    julian_valid = julian_version.jd < @transition_jdn
    gregorian_valid = gregorian_version.jd >= @transition_jdn

    julian_valid || gregorian_valid
  rescue ArgumentError
    # Invalid date in both calendar systems
    false
  end
end