Last active
June 20, 2017 23:04
-
-
Save straight-shoota/0152c2193b267539b1a9543185944ba6 to your computer and use it in GitHub Desktop.
Example Date Class for Crystal
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://carc.in/#/r/285b