# HG changeset patch # User jbe # Date 1352001322 -3600 # Node ID bbfbbddf13ad3bd82162a8bfed3b8d7d97eb22a6 # Parent fd31e0aa629a9f35c43ce7e210cedb5dea9fafca Support for intervals diff -r fd31e0aa629a -r bbfbbddf13ad framework/env/format/interval.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/framework/env/format/interval.lua Sun Nov 04 04:55:22 2012 +0100 @@ -0,0 +1,65 @@ +--[[-- +text = -- text with the value formatted as a time, according to the locale settings +format.interval( + value, -- a time, a timestamp or nil + { + nil_as = nil_text -- text to be returned for a nil value + } +) + +Formats an interval, according to the locale settings. + +--]]-- + +function format.interval(value, options) + local options = options or {} + if value == nil then + return options.nil_as or "" + end + if not atom.has_type(value, atom.interval) then + error("Value passed to format.interval(...) is neither an interval, nor nil.") + end + if value.invalid then + return "invalid" + end + local parts = {} + if value.years ~= 0 then + parts[#parts+1] = tostring(value.years) + if value.years == 1 or value.years == -1 then + parts[#parts+1] = "year" -- TODO: localization + else + parts[#parts+1] = "years" -- TODO: localization + end + end + if value.months ~= 0 then + parts[#parts+1] = tostring(value.months) + if value.months == 1 or value.months == -1 then + parts[#parts+1] = "month" -- TODO: localization + else + parts[#parts+1] = "months" -- TODO: localization + end + end + if value.days ~= 0 then + parts[#parts+1] = tostring(value.days) + if value.days == 1 or value.days == -1 then + parts[#parts+1] = "day" -- TODO: localization + else + parts[#parts+1] = "days" -- TODO: localization + end + end + if options.hide_seconds then + parts[#parts+1] = string.format( + "%02i:%02i", + value.hours, + math.abs(value.minutes) -- NOTE: hours and minutes always have equal sign + ) + else + parts[#parts+1] = string.format( + "%02i:%02i:%02i", + value.hours, + math.abs(value.minutes), -- NOTE: hours and minutes always have equal sign + math.abs(value.seconds) -- NOTE: hours and seconds always have equal sign + ) + end + return table.concat(parts, " ") +end diff -r fd31e0aa629a -r bbfbbddf13ad libraries/atom/atom.lua --- a/libraries/atom/atom.lua Sun Nov 04 04:54:54 2012 +0100 +++ b/libraries/atom/atom.lua Sun Nov 04 04:55:22 2012 +0100 @@ -1015,7 +1015,7 @@ error("Left operand of '-' operator has wrong type.") end if getmetatable(value2) == date then - return value1.jd - value2.jd -- TODO: transform to interval + return value1.jd - value2.jd elseif type(value2) == "number" then return date(value1.jd - value2) else @@ -1299,7 +1299,7 @@ error("Left operand of '-' operator has wrong type.") end if getmetatable(value2) == timestamp then - return value1.tsec - value2.tsec -- TODO: transform to interval + return value1.tsec - value2.tsec elseif type(value2) == "number" then return timestamp(value1.tsec - value2) else @@ -1508,7 +1508,7 @@ error("Left operand of '-' operator has wrong type.") end if getmetatable(value2) == time then - return value1.dsec - value2.dsec -- TODO: transform to interval + return value1.dsec - value2.dsec elseif type(value2) == "number" then return time((value1.dsec - value2) % 86400) else @@ -1539,4 +1539,154 @@ +-------------- +-- interval -- +-------------- + +interval = create_new_type("interval") + +--[[-- +atom.interval.invalid + +Value representing an invalid interval. + +--]]-- +interval.invalid = interval:_create{ + years = not_a_number, months = not_a_number, days = not_a_number, + hours = not_a_number, minutes = not_a_number, seconds = not_a_number, + hms_seconds = not_a_number, + invalid = true +} +--//-- + +--[[-- +t = -- time based on given data +atom.interval:new{ + years = years, + months = months, + days = days, + hours = hours, + minutes = minutes, + seconds = seconds +} + +This method returns a new time value, based on given data. + +--]]-- +function interval:new(args) + local args = args + if type(args) == "number" then args = { seconds = args } end + if type(args) == "table" then + if + (type(args.years) == "number" or not args.years ) and + (type(args.months) == "number" or not args.months ) and + (type(args.days) == "number" or not args.days ) and + (type(args.hours) == "number" or not args.hours ) and + (type(args.minutes) == "number" or not args.minutes) and + (type(args.seconds) == "number" or not args.seconds) + then + local years = args.years and tonumber(args.years) or 0 + local months = args.months and tonumber(args.months) or 0 + local days = args.days and tonumber(args.days) or 0 + local hours = args.hours and tonumber(args.hours) or 0 + local minutes = args.minutes and tonumber(args.minutes) or 0 + local seconds = args.seconds and tonumber(args.seconds) or 0 + if not ( + is_integer(years) and is_integer(months) and is_integer(days) and + is_integer(hours) and is_integer(minutes) and is_integer(seconds) + ) then + return interval.invalid + end + local hms_seconds = 3600 * hours + 60 * minutes + seconds + if not is_integer(hms_seconds) then return interval.invalid end + local hms_negative = false + if hms_seconds < 0 then + hms_negative = true + hms_seconds = -hms_seconds + if not is_integer(hms_seconds) then return interval.invalid end + end + hours = math.floor(hms_seconds / 3600) + minutes = math.floor(hms_seconds / 60) % 60 + seconds = hms_seconds % 60 + if hms_negative then + hours = -hours + minutes = -minutes + seconds = -seconds + hms_seconds = -hms_seconds + end + return interval:_create{ + years = years, months = months, days = days, + hours = hours, minutes = minutes, seconds = seconds, + hms_seconds = hms_seconds, + } + end + end + error("Invalid arguments passed to interval constructor.") +end +--//-- + +--[[-- +t = -- interval represented by the string +atom.interval:load( + string -- string representing an interval +) + +This method returns an interval represented by the given string. + +--]]-- +function interval:load(str) + if str == nil or str == "" then + return nil + elseif type(str) ~= "string" then + error("String expected") + else + local years, months, days, hours, minutes, seconds = string.match( + str, + "^(%-?[0-9]+) years (%-?[0-9]+) months (%-?[0-9]+) days (%-?[0-9]+) hours (%-?[0-9]+) minutes (%-?[0-9]+) seconds$" + ) + return interval:new{ + years = tonumber(years), + months = tonumber(months), + days = tonumber(days), + hours = tonumber(hours), + minutes = tonumber(minutes), + seconds = tonumber(seconds) + } + end +end +--//-- + +function interval:__tostring() + if self.invalid then + return "invalid_interval" + else + return string.format( + "%i years %i months %i days %i hours %i minutes %i seconds", + self.years, self.months, self.days, self.hours, self.minutes, self.seconds + ) + end +end + +function interval.__eq(value1, value2) + if value1.invalid or value2.invalid then + return false + else + return ( + value1.years == value2.years and + value1.months == value2.months and + value1.days == value2.days and + value1.hms_seconds == value2.hms_seconds + ) + end +end + +function interval:__unm() + return self.new{ + years = -self.years, months = -self.months, days = -self.days, + hours = -self.hours, minutes = -self.minutes, seconds = -self.seconds + } +end + + + return _M diff -r fd31e0aa629a -r bbfbbddf13ad libraries/mondelefant/mondelefant_atom_connector.lua --- a/libraries/mondelefant/mondelefant_atom_connector.lua Sun Nov 04 04:54:54 2012 +0100 +++ b/libraries/mondelefant/mondelefant_atom_connector.lua Sun Nov 04 04:55:22 2012 +0100 @@ -75,6 +75,24 @@ return conn:quote_string(tostring(value)) .. "::time" end +input_converters[atom.interval] = function(conn, value) + return ( + conn:quote_string( + table.concat( + { + tostring(value.years), "years", + tostring(value.months), "months", + tostring(value.days), "days", + tostring(value.hours), "hours", + tostring(value.minutes), "minutes", + tostring(value.seconds), "seconds" + }, + " " + ) + ) .. "::interval" + ) +end + output_converters = setmetatable({}, { __mode = "k" }) @@ -129,6 +147,59 @@ output_converters.time = time_loader_func output_converters.timetz = time_loader_func +output_converters.interval = function(str) + local years, months, days, hours, minutes, seconds = 0, 0, 0, 0, 0, 0 + local any_match = false + for amount, unit in string.gmatch(str, "(%-?[0-9]+)%s*([A-Za-z]+)") do + local unit = string.lower(unit) + if string.match(unit, "^y") then + years = years + tonumber(amount) + any_match = true + elseif string.match(unit, "^mo") then + months = months + tonumber(amount) + any_match = true + elseif string.match(unit, "^d") then + days = days + tonumber(amount) + any_match = true + elseif string.match(unit, "^h") then + hours = hours + tonumber(amount) + any_match = true + elseif string.match(unit, "^mi") then + minutes = minutes + tonumber(amount) + any_match = true + elseif string.match(unit, "^s") then + seconds = seconds + tonumber(amount) + any_match = true + else + return atom.interval.invalid + end + end + local sign, h, m, s = string.match(str, "(%-?)([0-9]+):([0-9]+):([0-9]+)") + if h then + if sign == "-" then + hours = hours - tonumber(h) + minutes = minutes - tonumber(m) + seconds = seconds - tonumber(s) + else + hours = hours + tonumber(h) + minutes = minutes + tonumber(m) + seconds = seconds + tonumber(s) + end + any_match = true + end + if not any_match then + return atom.interval.invalid + end + if string.match(str, "%sago%s*$") then + years, months, days = -years, -months, -days + hours, minutes, seconds = -hours, -minutes, -seconds + end + return atom.interval:new{ + years = years, months = months, days = days, + hours = hours, minutes = minutes, seconds = seconds + } +end + mondelefant.postgresql_connection_prototype.type_mappings = { int8 = atom.integer, int4 = atom.integer,