I stumbled upon this question on SO, and I found it very intriguing.
https://stackoverflow.com/questions/45703404/custom-leap-year-logic-in-datetime
After spending some time to find a solution I had to give up, maybe someone
can shed some light on this.
The problem is this:
I'm working on a fantasy text-based adventure, using the Curse library, for
fun and decided that I wanted to customize Dates, from the standard
Gregorian calendar. I've designed a new Date system that will stay
relatively similar:- 12 months with different names
- The days in each month match up with the Gregorian calendar
- 7 days (with different names) a week
- 24 hours per day
- 60 minutes per hour
- 60 seconds per minute.
- leap years every 15 yearsThe different names I'm able to handle through I18n. The big difference
ended up being that instead of a leap year being every 4 years, except
centuries, I wanted leap years to occur every 15 years. So, seeing that
Date <https://ruby-doc.org/stdlib-2.4.1/libdoc/date/rdoc/Date.html> has
the *_leap? methods, I tried creating a subclass overriding those in
hopes DateTime would check these methods when instantiating/adding to
dates:require 'date'
class CustomDate < DateTime
class << self
def leap?(year)
year % 15 == 0
enddef gregorian_leap?(year)
leap?(year)
enddef julian_leap?(year)
leap?(year)
end
enddef leap?
self.class.leap?(year)
endend
CustomDate.leap?(2010) # => trueDate.leap?(2010) # => falseCustomDate.leap?(2000) # => falseDate.leap?(2000) # => trueSo far looking good, but then when actually trying to use these CustomDate
nothing changed:CustomDate.new(2000, 2, 29) # => hoped this would error because CustomDate.leap?(2000) is false...it didn'tCustomDate.new(2010, 2, 29) # => errors, but was hoping this would work
Adding days:
# Was hoping this would be 2010-02-29CustomDate.new(2010, 2, 28) + 1# => #<CustomDate: 2010-03-01T00:00:00+00:00 ...>
# Was hoping this one would be 2000-03-01CustomDate.new(2000, 2, 28) + 1# => #<CustomDate: 2000-02-29T00:00:00+00:00 ...>So, unless I'm missing something with this approach, this isn't going to
work. Looking further, since Date is written in C, that kind of makes
sense.Looking at the source code for Date, there's a function c_gregorian_leap_p
<https://github.com/ruby/ruby/blob/trunk/ext/date/date_core.c#L684> that
seems to be the work horse for all these methods I overrode on the Ruby
side. I'm thinking I'm going to need to write a c extension of some kind to
override *that* function instead of the ruby one. I've never written C
before, so before I get started down this path, I want to make sure I
haven't missed anything in actual Ruby code.*TL;DR:* Is there anyway to customize Date/DateTime in ruby to have
custom leap year logic without writing some sort of C extension? With leap
years being the only change to a Gregorian calendar, seems silly to rewrite
Date/DateTime from scratch to change that one thing.
My initial thought was to use monkey patching, but of course as he already
mentioned when it come to actual usage of the said class things don't
really work out.
What I tried:
- Monkey patch the Date.leap? but the new method does not use that.
- Monkey patch the DateTime new method, like
def self.new(*args)
raise 'invalid date' if CustomDate.leap?(args[0])
end
But this only work for the new leap year not the old.
Does anyone have any idea of how to accomplish this only with Ruby, even
if it might not be the best practice, or the only solution is to work
directly with C.
Cheers, Bud.