import "luxe: io" for IO //modulo utility function in here to make this pretty self contained for now var mod = Fn.new{|dividend, divisor| return (dividend%divisor + divisor) % divisor } //time based on epoch time - assumes time is after 1.1.1970 and doesnt do leap seconds class DateTime{ static seconds_per_minute{60} static seconds_per_hour{3600} static seconds_per_day{86400} static minutes_per_hour{60} static hours_per_day{24} static epoch{1970} toString{ var month_names = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"] var week_names = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"] var ends = ["th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"] return "%(week_names[day_of_week]), %(days+1)%(ends[(days+1)%10]) of %(month_names[months]) %(years) "+ "%(hours):%(minutes):%(seconds)" } total_seconds{_time} total_minutes{(_time / DateTime.seconds_per_minute).truncate} total_hours{(_time / DateTime.seconds_per_hour).truncate} total_days{(_time / DateTime.seconds_per_day).truncate} total_years{years} seconds{total_seconds % DateTime.seconds_per_minute} minutes{(total_minutes % DateTime.minutes_per_hour).truncate} hours{(total_hours % DateTime.hours_per_day).truncate} days{ var months = [31, DateTime.is_leap_year(years)?29:28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] var month = 0 var days = day_in_year while(days >= months[month]){ days = days - months[month] month = month + 1 } return days } day_in_year{ var days = total_days var year = DateTime.epoch //while the day is in a later year while(days >= 365){ if(DateTime.is_leap_year(year)){ if(days == 365) return days days = days - 366 } else { days = days - 365 } year = year + 1 } return days } //day of the week from monday=0 to sunday=6 day_of_week{ return (total_days + 3) % 7 //+3 is because 1.1.1970 was a thursday } week_in_year{ //what return ((day_in_year-day_of_week-1)/7).truncate+1 } months{ var months = [31, DateTime.is_leap_year(years)?29:28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] var month = 0 var days = day_in_year while(days >= months[month]){ days = days - months[month] month = month + 1 } return month } years{ return DateTime.epoch + ((total_days - leap_days_since_epoch_start) / 365).truncate } leap_days_since_epoch_start{ var days = total_days var year = DateTime.epoch var leaps = 0 //while the day is in a later year or later than january 29/march 1 while(days > 58){ if(DateTime.is_leap_year(year)){ leaps = leaps + 1 days = days - 366 } else { days = days - 365 } year = year + 1 } return leaps } is_leap_year{is_leap_year(years)} static is_leap_year(year){ return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) } construct new(epoch){ _time = epoch } construct now(){ _time = IO.time_since_epoch_utc() } construct clone(instance){ _time = instance.total_seconds } //overloads static from_time(years) {from_time(years, 0, 0, 0, 0, 0)} static from_time(years, months) {from_time(years, months, 0, 0, 0, 0)} static from_time(years, months, days) {from_time(years, months, days, 0, 0, 0)} static from_time(years, months, days, hours) {from_time(years, months, days, hours, 0, 0)} static from_time(years, months, days, hours, minutes) {from_time(years, months, days, hours, minutes, 0)} construct from_time(years, months, days, hours, minutes, seconds){ if(years < DateTime.epoch) { System.print("[warn] dates before %(DateTime.epoch) aren't possible yet :(") return } for(i in DateTime.epoch...years){ days = days + (DateTime.is_leap_year(i)?366:365) } var month_days = [31, DateTime.is_leap_year(years)?29:28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] for(i in 0...months){ days = days + month_days[i] } _time = days * DateTime.seconds_per_day + hours * DateTime.seconds_per_hour + minutes * DateTime.seconds_per_minute + seconds } add_seconds(second_diff){ _time = _time + second_diff } add_minutes(minute_diff){ _time = _time + minute_diff * DateTime.seconds_per_minute } add_hours(hour_diff){ _time = _time + hour_diff * DateTime.seconds_per_hour } add_days(day_diff){ _time = _time + day_diff * DateTime.seconds_per_day } add_weeks(week_diff){ _time = _time + week_diff * DateTime.seconds_per_day * 7 } add_months(month_diff){ var month_days = [31, null, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] var day_difference = 0 var month = months var year = years var sign = month_diff.sign var original_days = days //execute once if counting down to use the next months days, not this months days if(sign < 0){ month = month + sign if(month < 0){ month = month + 12 year = year - 1 } if(month >= 12){ month = month - 12 year = year + 1 } } //collect days we have to move the time for(i in 0...month_diff){ var days = month_days[month] if(days){ day_difference = day_difference + days * sign } else { day_difference = day_difference + (DateTime.is_leap_year(year)?29:28) * sign } month = month + sign if(month < 0){ month = month + 12 year = year - 1 } if(month >= 12){ month = month - 12 year = year + 1 } } add_days(day_difference) //if the days shifted we can assume we jumped, for example from a 31st of jan to a 2nd of march since the feb is short //so we just go back until we arrive in the previous month var new_days = days if(new_days != original_days){ add_days(-new_days-1) } } add_years(year_diff){ var before_leap = day_in_year <= 59 var day_difference = 0 var year = years var sign = year_diff.sign if(sign > 0 != before_leap){ year = year + sign } for(i in 0...year_diff){ day_difference = day_difference + sign * (DateTime.is_leap_year(year)?366:365) year = year + sign } add_days(day_difference) } add(span){ _time = _time + span.total_seconds } set(time){ _time = time } update(){ _time = IO.time_since_epoch_utc() } static difference(time_1, time_2){ return TimeSpan.new((time_1.total_seconds - time_2.total_seconds).abs) } +(val){ if(val is TimeSpan){ return DateTime.new(_time + val.total_seconds) } Fiber.abort("Can't add DateTime and %(val.type.name)") } -(val){ if(val is DateTime){ return DateTime.difference(this, val) } if(val is TimeSpan){ return DateTime.new(_time - val.total_seconds) } Fiber.abort("Can't subtract %(val.type.name) from TimeSpan") } } class TimeSpan{ static zero{TimeSpan.new(0)} toString{"%(days) days, %(hours) hours, %(minutes) minutes, and %(seconds) seconds"} total_seconds{_time} total_minutes{(_time / DateTime.seconds_per_minute).truncate} total_hours{(_time / DateTime.seconds_per_hour).truncate} total_days{(_time / DateTime.seconds_per_day).truncate} seconds{total_seconds % DateTime.seconds_per_minute} minutes{total_minutes % DateTime.minutes_per_hour} hours{total_hours % DateTime.hours_per_day} days{total_days} //months and years are not possible because we don't know which months/years we're talking about construct new(time){ _time = time } construct from_seconds(seconds){ _time = seconds } construct from_minutes(minutes){ _time = minutes * DateTime.seconds_per_minute } construct from_hours(hours){ _time = hours * DateTime.seconds_per_hour } construct from_days(days){ _time = days * DateTime.seconds_per_day } add_seconds(seconds){ _time = _time + seconds } add_minutes(minutes){ _time = _time + minutes * DateTime.seconds_per_minute } add_hours(hours){ _time = _time + hours * DateTime.seconds_per_hour } add_days(days){ _time = _time + days * DateTime.seconds_per_day } add(span){ _time = _time + span.total_seconds } set(time){ _time = time } +(val){ if(val is TimeSpan){ return TimeSpan.new(_time + val.total_seconds) } Fiber.abort("Can't add TimeSpan and %(val.type.name)") } -(val){ if(val is TimeSpan){ return TimeSpan.new(_time - val.total_seconds) } Fiber.abort("Can't add TimeSpan and %(val.type.name)") } }