X-Git-Url: https://git.madduck.net/etc/awesome.git/blobdiff_plain/8422230dae4e01daed5915c125b0c42a41b4243c..6058aa9e63278e4ae7400c6f8b91540718821d17:/luatz/timetable.lua?ds=sidebyside

diff --git a/luatz/timetable.lua b/luatz/timetable.lua
index f096372..fc89fca 100644
--- a/luatz/timetable.lua
+++ b/luatz/timetable.lua
@@ -16,7 +16,13 @@ end
 local sakamoto = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
 
 local function is_leap ( y )
-	return (y % 4) == 0 and (y % 100) ~= 0 or (y % 400) == 0
+	if (y % 4) ~= 0 then
+		return false
+	elseif (y % 100) ~= 0 then
+		return true
+	else
+		return (y % 400) == 0
+	end
 end
 
 local function year_length ( y )
@@ -50,7 +56,14 @@ local function day_of_week ( day , month , year )
 	return ( year + leap_years_since ( year ) + sakamoto[month] + day ) % 7 + 1
 end
 
-local function increment ( tens , units , base )
+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 )
 		units = units % base
@@ -63,32 +76,50 @@ end
 
 -- Modify parameters so they all fit within the "normal" range
 local function normalise ( year , month , day , hour , min , sec )
-	min  , sec  = increment ( min  , sec  , 60 ) -- TODO: consider leap seconds?
-	hour , min  = increment ( hour , min  , 60 )
-	day  , hour = increment ( day  , hour , 24 )
-
-	while day <= 0 do
+	-- `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 )
+	-- 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
+	year , month = carry ( year , month , 12 )
 
-	-- Lua months start from 1, need -1 and +1 around this increment
-	month = month - 1
-	year , month = increment ( 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
 
@@ -196,10 +227,11 @@ local function new_from_timestamp ( ts )
 	if type ( ts ) ~= "number" then
 		error ( "bad argument #1 to 'new_from_timestamp' (number expected, got " .. type ( ts ) .. ")" , 2 )
 	end
-	return new_timetable ( 1970 , 1 , 1 , 0 , 0 , ts )
+	return new_timetable ( 1970 , 1 , 1 , 0 , 0 , ts ):normalise ( )
 end
 
 return {
+	is_leap = is_leap ;
 	day_of_year = day_of_year ;
 	day_of_week = day_of_week ;
 	normalise = normalise ;