From f09916d72034242a9bc4966ecf6f5590ed40d2cc Mon Sep 17 00:00:00 2001 From: coffeepots Date: Tue, 14 Jul 2015 11:58:26 +0100 Subject: [PATCH 1/4] Procs to decode Time to TimeInfo & TimeInterval Fills in the missing functionality of decoding Time to TimeInfo and TimeInterval, whilst also adding some procs to work with leap years and to get the day of the week based on a date. --- lib/pure/times.nim | 130 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index e4d3f7494e..efed1bbc05 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -1039,6 +1039,126 @@ proc parse*(value, layout: string): TimeInfo = info.weekday = getLocalTime(timeInfoToTime(info)).weekday return info +# Leap year calculations are adapted from: +# from http://www.codeproject.com/Articles/7358/Ultra-fast-Algorithms-for-Working-with-Leap-Years +# The dayOfTheWeek procs are adapated from: +# from http://stason.org/TULARC/society/calendars/2-5-What-day-of-the-week-was-2-August-1953.html + +# Note: for leap years, start date is assumed to be 1 AD. +# counts the number of leap years up to January 1st of a given year. +# Keep in mind that if specified year is a leap year, the leap day +# has not happened before January 1st of that year. +proc countLeapYears(yearSpan: int): int = + (((yearSpan - 1) / 4) - ((yearSpan - 1) / 100) + ((yearSpan - 1)/400)).int + +proc countDays(yearSpan: int): int = + (yearSpan - 1) * 365 + countLeapYears(yearSpan) + +# counts the number of years spanned by a given number of days. +proc countYears(daySpan: int): int = + ((daySpan - countLeapYears(daySpan div 365)) div 365) + +proc countYearsAndDays(daySpan: int): tuple[years: int, days: int] = + let days = daySpan - countLeapYears(daySpan div 365) + result.years = days div 365 + result.days = days mod 365 + +type + SecondScale = enum ssMinute, ssHour, ssDay, ssMonth, ssYear + +# these are very approximate beyond day +const secondsIn*: array[SecondScale.low..SecondScale.high, int] =[ + 60, # minute + 60*60, # hour + 60*60*24, # day + 60*60*24*30, # month (estimate) + 60*60*24*30*12] # year (estimate) + +const + epochStartYear = 1970 + leapYearsSinceEpoch* = countLeapYears(epochStartYear) + +proc dayOfWeek*(day, month, year: int): WeekDay = + # This is for the Gregorian calendar + # Day & month start from one. + let + a = (14 - month) div 12 + y = year - a + m = month + (12*a) - 2 + d = (day + y + (y div 4) - (y div 100) + (y div 400) + (31*m) div 12) mod 7 + # The value of d is 0 for a Sunday, 1 for a Monday, 2 for a Tuesday, etc. so we must correct + # for the WeekDay type. + if d == 0: return dSun + result = (d-1).WeekDay + +proc dayOfWeekJulian*(day, month, year: int): WeekDay = + # This is for the Julian calendar + # Day & month start from one. + let + a = (14 - month) div 12 + y = year - a + m = month + (12*a) - 2 + d = (5 + day + y + (y div 4) + (31*m) div 12) mod 7 + # The value of d is 0 for a Sunday, 1 for a Monday, 2 for a Tuesday, etc. so we must correct + # for the WeekDay type. + if d == 0: return dSun + result = (d-1).WeekDay + +proc decodeTime*(t: Time): TimeInfo = + let + daysSinceEpoch = t.int div secondsIn[ssDay] + (yearsSinceEpoch, daysRemaining) = countYearsAndDays(daysSinceEpoch) + daySeconds = t.int mod secondsIn[ssDay] + + y = yearsSinceEpoch + epochStartYear + + var + mon = mJan + days = daysRemaining + daysInMonth = getDaysInMonth(mon, y) + + # calculate month and day remainder + while days > daysInMonth and mon <= mDec: + days -= daysInMonth + mon.inc + daysInMonth = getDaysInMonth(mon, y) + + let + yd = daysRemaining + m = mon # month is zero indexed enum + md = days + # NB: month is zero indexed but dayOfWeek expects 1 indexed. + wd = dayOfWeek(days, mon.int + 1, y).Weekday + h = daySeconds div secondsIn[ssHour] + 1 + mi = (daySeconds mod secondsIn[ssHour]) div secondsIn[ssMinute] + s = daySeconds mod secondsIn[ssMinute] + result = TimeInfo(year: y, yearday: yd, month: m, monthday: md, weekday: wd, hour: h, minute: mi, second: s) + +proc decodeTimeInterval*(t: Time): TimeInterval = + var + daysSinceEpoch = t.int div secondsIn[ssDay] + (yearsSinceEpoch, daysRemaining) = countYearsAndDays(daysSinceEpoch) + daySeconds = t.int mod secondsIn[ssDay] + + result.years = yearsSinceEpoch + epochStartYear + + var + mon = mJan + days = daysRemaining + daysInMonth = getDaysInMonth(mon, result.years) + + # calculate month and day remainder + while days > daysInMonth and mon <= mDec: + days -= daysInMonth + mon.inc + daysInMonth = getDaysInMonth(mon, result.years) + + result.months = mon.int + 1 # month is 1 indexed int + result.days = days + result.hours = daySeconds div secondsIn[ssHour] + 1 + result.minutes = (daySeconds mod secondsIn[ssHour]) div secondsIn[ssMinute] + result.seconds = daySeconds mod secondsIn[ssMinute] + # Milliseconds not available from Time when isMainModule: # $ date --date='@2147483647' @@ -1122,3 +1242,13 @@ when isMainModule: assert "15:04:00" in $s.parse(f) when not defined(testing): echo "Kitchen: " & $s.parse(f) + var ti = decodeTime(getTime()) + echo "Todays date after decoding: ", ti + var tint = decodeTimeInterval(getTime()) + echo "Todays date after decoding to interval: ", tint + # checking dayOfWeek matches known days + assert dayOfWeek(21, 9, 1900) == dFri + assert dayOfWeek(1, 1, 1970) == dThu + assert dayOfWeek(21, 9, 1970) == dMon + assert dayOfWeek(1, 1, 2000) == dSat + assert dayOfWeek(1, 1, 2021) == dFri From a2f0fe03b634753fb26c6da899fcdefbb8e46035 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Thu, 16 Jul 2015 10:20:34 +0100 Subject: [PATCH 2/4] Fixed dayOfWeekJulian, exported SecondScale --- lib/pure/times.nim | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index efed1bbc05..a85998cb5c 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -1064,7 +1064,7 @@ proc countYearsAndDays(daySpan: int): tuple[years: int, days: int] = result.days = days mod 365 type - SecondScale = enum ssMinute, ssHour, ssDay, ssMonth, ssYear + SecondScale* = enum ssMinute, ssHour, ssDay, ssMonth, ssYear # these are very approximate beyond day const secondsIn*: array[SecondScale.low..SecondScale.high, int] =[ @@ -1099,10 +1099,7 @@ proc dayOfWeekJulian*(day, month, year: int): WeekDay = y = year - a m = month + (12*a) - 2 d = (5 + day + y + (y div 4) + (31*m) div 12) mod 7 - # The value of d is 0 for a Sunday, 1 for a Monday, 2 for a Tuesday, etc. so we must correct - # for the WeekDay type. - if d == 0: return dSun - result = (d-1).WeekDay + result = d.WeekDay proc decodeTime*(t: Time): TimeInfo = let @@ -1252,3 +1249,8 @@ when isMainModule: assert dayOfWeek(21, 9, 1970) == dMon assert dayOfWeek(1, 1, 2000) == dSat assert dayOfWeek(1, 1, 2021) == dFri + # Julian tests + assert dayOfWeekJulian(21, 9, 1900) == dFri + assert dayOfWeekJulian(21, 9, 1970) == dMon + assert dayOfWeekJulian(1, 1, 2000) == dSat + assert dayOfWeekJulian(1, 1, 2021) == dFri From 5e0b8d5ef6ce616361cedf963afde667edfadb89 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Fri, 31 Jul 2015 09:44:37 +0100 Subject: [PATCH 3/4] Implemented changes suggested by dom96 * Removed extraneous exports (and converted const array to separate consts) * Renamed dayOfWeek, dayOfWeekJulian to getDayOfWeek and getDayOfWeekJulian * Renamed decodeTime procs to timeToTimeInfo and timeToTimeInterval * Added some basic descriptions to docs --- lib/pure/times.nim | 79 ++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 41 deletions(-) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index a85998cb5c..e69357597d 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -1054,32 +1054,27 @@ proc countLeapYears(yearSpan: int): int = proc countDays(yearSpan: int): int = (yearSpan - 1) * 365 + countLeapYears(yearSpan) -# counts the number of years spanned by a given number of days. proc countYears(daySpan: int): int = + # counts the number of years spanned by a given number of days. ((daySpan - countLeapYears(daySpan div 365)) div 365) proc countYearsAndDays(daySpan: int): tuple[years: int, days: int] = + # counts the number of years spanned by a given number of days and the remainder as days. let days = daySpan - countLeapYears(daySpan div 365) result.years = days div 365 result.days = days mod 365 -type - SecondScale* = enum ssMinute, ssHour, ssDay, ssMonth, ssYear - -# these are very approximate beyond day -const secondsIn*: array[SecondScale.low..SecondScale.high, int] =[ - 60, # minute - 60*60, # hour - 60*60*24, # day - 60*60*24*30, # month (estimate) - 60*60*24*30*12] # year (estimate) +const + secondsInMin = 60 + secondsInHour = 60*60 + secondsInDay = 60*60*24 const epochStartYear = 1970 - leapYearsSinceEpoch* = countLeapYears(epochStartYear) + leapYearsSinceEpoch = countLeapYears(epochStartYear) -proc dayOfWeek*(day, month, year: int): WeekDay = - # This is for the Gregorian calendar +proc getDayOfWeek*(day, month, year: int): WeekDay = + ## Returns the day of the week enum from day, month and year. # Day & month start from one. let a = (14 - month) div 12 @@ -1091,8 +1086,8 @@ proc dayOfWeek*(day, month, year: int): WeekDay = if d == 0: return dSun result = (d-1).WeekDay -proc dayOfWeekJulian*(day, month, year: int): WeekDay = - # This is for the Julian calendar +proc getDayOfWeekJulian*(day, month, year: int): WeekDay = + ## Returns the day of the week enum from day, month and year, according to the Julian calender. # Day & month start from one. let a = (14 - month) div 12 @@ -1101,11 +1096,12 @@ proc dayOfWeekJulian*(day, month, year: int): WeekDay = d = (5 + day + y + (y div 4) + (31*m) div 12) mod 7 result = d.WeekDay -proc decodeTime*(t: Time): TimeInfo = +proc timeToTimeInfo*(t: Time): TimeInfo = + ## Converts a Time to TimeInfo. let - daysSinceEpoch = t.int div secondsIn[ssDay] + daysSinceEpoch = t.int div secondsInDay (yearsSinceEpoch, daysRemaining) = countYearsAndDays(daysSinceEpoch) - daySeconds = t.int mod secondsIn[ssDay] + daySeconds = t.int mod secondsInDay y = yearsSinceEpoch + epochStartYear @@ -1125,17 +1121,18 @@ proc decodeTime*(t: Time): TimeInfo = m = mon # month is zero indexed enum md = days # NB: month is zero indexed but dayOfWeek expects 1 indexed. - wd = dayOfWeek(days, mon.int + 1, y).Weekday - h = daySeconds div secondsIn[ssHour] + 1 - mi = (daySeconds mod secondsIn[ssHour]) div secondsIn[ssMinute] - s = daySeconds mod secondsIn[ssMinute] - result = TimeInfo(year: y, yearday: yd, month: m, monthday: md, weekday: wd, hour: h, minute: mi, second: s) + wd = getDayOfWeek(days, mon.int + 1, y).Weekday + h = daySeconds div secondsInHour + 1 + mi = (daySeconds mod secondsInHour) div secondsInMin + s = daySeconds mod secondsInMin + result = TimeInfo(year: y, yearday: yd, month: m, monthday: md, weekday: wd, hour: h, minute: mi, second: s) -proc decodeTimeInterval*(t: Time): TimeInterval = +proc timetoTimeInterval*(t: Time): TimeInterval = + ## Converts a Time to a TimeInterval. var - daysSinceEpoch = t.int div secondsIn[ssDay] + daysSinceEpoch = t.int div secondsInDay (yearsSinceEpoch, daysRemaining) = countYearsAndDays(daysSinceEpoch) - daySeconds = t.int mod secondsIn[ssDay] + daySeconds = t.int mod secondsInDay result.years = yearsSinceEpoch + epochStartYear @@ -1152,9 +1149,9 @@ proc decodeTimeInterval*(t: Time): TimeInterval = result.months = mon.int + 1 # month is 1 indexed int result.days = days - result.hours = daySeconds div secondsIn[ssHour] + 1 - result.minutes = (daySeconds mod secondsIn[ssHour]) div secondsIn[ssMinute] - result.seconds = daySeconds mod secondsIn[ssMinute] + result.hours = daySeconds div secondsInHour + 1 + result.minutes = (daySeconds mod secondsInHour) div secondsInMin + result.seconds = daySeconds mod secondsInMin # Milliseconds not available from Time when isMainModule: @@ -1239,18 +1236,18 @@ when isMainModule: assert "15:04:00" in $s.parse(f) when not defined(testing): echo "Kitchen: " & $s.parse(f) - var ti = decodeTime(getTime()) + var ti = timeToTimeInfo(getTime()) echo "Todays date after decoding: ", ti - var tint = decodeTimeInterval(getTime()) + var tint = timeToTimeInterval(getTime()) echo "Todays date after decoding to interval: ", tint # checking dayOfWeek matches known days - assert dayOfWeek(21, 9, 1900) == dFri - assert dayOfWeek(1, 1, 1970) == dThu - assert dayOfWeek(21, 9, 1970) == dMon - assert dayOfWeek(1, 1, 2000) == dSat - assert dayOfWeek(1, 1, 2021) == dFri + assert getDayOfWeek(21, 9, 1900) == dFri + assert getDayOfWeek(1, 1, 1970) == dThu + assert getDayOfWeek(21, 9, 1970) == dMon + assert getDayOfWeek(1, 1, 2000) == dSat + assert getDayOfWeek(1, 1, 2021) == dFri # Julian tests - assert dayOfWeekJulian(21, 9, 1900) == dFri - assert dayOfWeekJulian(21, 9, 1970) == dMon - assert dayOfWeekJulian(1, 1, 2000) == dSat - assert dayOfWeekJulian(1, 1, 2021) == dFri + assert getDayOfWeekJulian(21, 9, 1900) == dFri + assert getDayOfWeekJulian(21, 9, 1970) == dMon + assert getDayOfWeekJulian(1, 1, 2000) == dSat + assert getDayOfWeekJulian(1, 1, 2021) == dFri From f9d909bb947749666c0241f64bd8adef8c071a9b Mon Sep 17 00:00:00 2001 From: coffeepots Date: Fri, 31 Jul 2015 10:20:21 +0100 Subject: [PATCH 4/4] Removed unused leapYearsSinceEpoch --- lib/pure/times.nim | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index e69357597d..0b77f02c57 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -1068,10 +1068,7 @@ const secondsInMin = 60 secondsInHour = 60*60 secondsInDay = 60*60*24 - -const epochStartYear = 1970 - leapYearsSinceEpoch = countLeapYears(epochStartYear) proc getDayOfWeek*(day, month, year: int): WeekDay = ## Returns the day of the week enum from day, month and year.