Skip to content

Instantly share code, notes, and snippets.

@straight-shoota
Last active June 20, 2017 23:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save straight-shoota/0152c2193b267539b1a9543185944ba6 to your computer and use it in GitHub Desktop.
Save straight-shoota/0152c2193b267539b1a9543185944ba6 to your computer and use it in GitHub Desktop.
Example Date Class for Crystal
struct Date
include Comparable(self)
getter year : Int32
getter month : Int16
getter day : Int16
def initialize(year, month, day)
Calendar.validate!(year, month, day)
@year = year.to_i32
@month = month.to_i16
@day = day.to_i16
end
def succ
self + 1.days
end
def prev
self - 1.days
end
def +(period)
day = @day + period.days
months_to_add = period.months
year = @year
days_in_month = Calendar.days_in_month(year, @month + months_to_add)
while day < 1 || day > days_in_month
if @month + months_to_add == Calendar::MONTH_MAX
year += day.sign
months_to_add = -@month
end
months_to_add += day.sign
day = day.sign * (day.abs - days_in_month)
days_in_month = Calendar.days_in_month(year, @month + months_to_add)
end
calc_months = year * 12i64 + (@month - 1) + months_to_add
year, month = calc_months.divmod(12)
year += period.years
Date.new(year, month + 1, day)
end
def to_s(io)
io.printf("%04d-%02d-%02d", year, month, day)
end
def <=>(other : Date)
{year, month, day} <=> {other.year, other.month, other.day}
end
end
module Calendar
YEAR_MIN = -999_999_999
YEAR_MAX = 999_999_999
YEAR_RANGE = YEAR_MIN..YEAR_MAX
MONTH_MIN = 1
MONTH_MAX = 12
MONTH_RANGE = MONTH_MIN..MONTH_MAX
DAY_MIN = 1
DAY_MAX = 31
DAY_RANGE = DAY_MIN..DAY_MAX
def self.validate_year!(year)
unless YEAR_RANGE.includes?(year)
raise ArgumentError.new("year not in #{YEAR_RANGE}: #{year}")
end
end
def self.validate_month!(year, month)
unless MONTH_RANGE.includes?(month)
raise ArgumentError.new("month not in #{MONTH_RANGE}: #{month}")
end
end
def self.validate!(year, month = nil, day = nil)
validate_year!(year)
validate_month!(year, month) unless month.nil?
validate_day!(year, month, day) unless day.nil?
end
def self.validate_day!(year, month, day)
day_range = (DAY_MIN..days_in_month(year, month))
unless day.nil? || day_range.includes?(day)
raise ArgumentError.new("day not in #{day_range}: #{day}")
end
end
def self.days_in_month(year, month)
case month
when 2
leap_year?(year) ? 29 : 28
when 4, 6, 9, 11
30
else
31
end
end
def self.days_in_year(year)
leap_year?(year) ? 366 : 365
end
def self.leap_year?(year)
(year & 3 == 0) && ((year % 100 != 0) || (year % 400 == 0))
end
end
struct Period
getter years : Int32
getter months : Int32
getter days : Int32
def initialize(@years, @months, @days)
end
end
struct Int
def years
Period.new(self, 0, 0)
end
def months
Period.new(0, self, 0)
end
def days
Period.new(0, 0, self)
end
def weeks
Period.new(0, 0, self * 7)
end
end
require "spec"
it { (Date.new(2017, 6, 18) + 1.day).should eq Date.new(2017, 6, 19) }
it { (Date.new(2017, 6, 18) + 1.week).should eq Date.new(2017, 6, 25) }
it { (Date.new(2017, 6, 18) + 1.month).should eq Date.new(2017, 7, 18) }
it { (Date.new(2017, 6, 18) + 1.year).should eq Date.new(2018, 6, 18) }
it { (Date.new(2017, 6, 18) + 365.days).should eq Date.new(2018, 6, 18) }
@straight-shoota
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment