From 6c4658848c01ae0862a2fcad7f6565efd9aa948d Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 29 Aug 2014 16:03:47 -0400 Subject: [PATCH] luatz/timetable: Add support to `normalise` for fractional columns --- luatz/timetable.lua | 45 +++++++++++++++++++++++++++++++---------- spec/timetable_spec.lua | 20 ++++++++++++++++++ 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/luatz/timetable.lua b/luatz/timetable.lua index 879f0a2..3e80242 100644 --- a/luatz/timetable.lua +++ b/luatz/timetable.lua @@ -56,6 +56,13 @@ local function day_of_week ( day , month , year ) return ( year + leap_years_since ( year ) + sakamoto[month] + day ) % 7 + 1 end +local function borrow ( tens , units , base ) + local frac = tens % 1 + units = units + frac * base + tens = tens - frac + return tens , units +end + local function carry ( tens , units , base ) if units >= base then tens = tens + idiv ( units , base ) @@ -69,34 +76,50 @@ end -- Modify parameters so they all fit within the "normal" range local function normalise ( year , month , day , hour , min , sec ) + -- `month` and `day` start from 1, need -1 and +1 so it works modulo + month , day = month - 1 , day - 1 + + -- Convert everything (except seconds) to an integer + -- by propagating fractional components down. + year , month = borrow ( year , month , 12 ) + -- Carry from month to year first, so we get month length correct in next line around leap years + year , month = carry ( year , month , 12 ) + month , day = borrow ( month , day , month_length ( floor ( month + 1 ) , year ) ) + day , hour = borrow ( day , hour , 24 ) + hour , min = borrow ( hour , min , 60 ) + min , sec = borrow ( min , sec , 60 ) + -- Propagate out of range values up -- e.g. if `min` is 70, `hour` increments by 1 and `min` becomes 10 + -- This has to happen for all columns after borrowing, as lower radixes may be pushed out of range min , sec = carry ( min , sec , 60 ) -- TODO: consider leap seconds? hour , min = carry ( hour , min , 60 ) day , hour = carry ( day , hour , 24 ) - - while day <= 0 do + -- Ensure `day` is not underflowed + -- Add a whole year of days at a time, this is later resolved by adding months + -- TODO[OPTIMIZE]: This could be slow if `day` is far out of range + while day < 0 do year = year - 1 day = day + year_length ( year ) end - - -- Lua months start from 1, need -1 and +1 around this increment - month = month - 1 year , month = carry ( year , month , 12 ) - month = month + 1 - -- This could potentially be slow if `day` is very large + -- TODO[OPTIMIZE]: This could potentially be slow if `day` is very large while true do - local i = month_length ( month , year ) - if day <= i then break end + local i = month_length ( month + 1 , year ) + if day < i then break end day = day - i month = month + 1 - if month > 12 then - month = 1 + if month >= 12 then + month = 0 year = year + 1 end end + -- Now we can place `day` and `month` back in their normal ranges + -- e.g. month as 1-12 instead of 0-11 + month , day = month + 1 , day + 1 + return year , month , day , hour , min , sec end diff --git a/spec/timetable_spec.lua b/spec/timetable_spec.lua index 34c0529..569864f 100644 --- a/spec/timetable_spec.lua +++ b/spec/timetable_spec.lua @@ -84,4 +84,24 @@ describe ( "Timetable library" , function ( ) assert.same ( { timetable.normalise(2013,42,52,0,0,0) } , { 2016,7,22,0,0,0 } ) assert.same ( { timetable.normalise(2013,42,52,50,0,0) } , { 2016,7,24,2,0,0 } ) end ) + + it ( "#normalise handles fractional #month" , function ( ) + assert.same ( { timetable.normalise(2014,14.5,1,0,0,0) } , { 2015,2,15,0,0,0 } ) + assert.same ( { timetable.normalise(2015,14.5,1,0,0,0) } , { 2016,2,15,12,0,0 } ) -- leap year, so hours is 12 + assert.same ( { timetable.normalise(2016,14.5,1,0,0,0) } , { 2017,2,15,0,0,0 } ) + end ) + + local function round_trip_add(t, field, x) + local before = t:clone() + t[field]=t[field]+x; + t:normalise(); + t[field]=t[field]-x; + t:normalise(); + assert.same(0, t-before) + end + it ( "#normalise round trips" , function ( ) + round_trip_add(timetable.new(2000,2,28,0,0,0), "month", 0.5) + round_trip_add(timetable.new(2014,8,28,19,23,0), "month", 0.4) + round_trip_add(timetable.new(2014,14.5,28,0,0,0), "month", 0.4) + end ) end ) -- 2.39.2