jbe/bsw@0: #!/usr/bin/env lua jbe/bsw@0: jbe/bsw@0: local _G = _G jbe/bsw@0: local _VERSION = _VERSION jbe/bsw@0: local assert = assert jbe/bsw@0: local error = error jbe/bsw@0: local getmetatable = getmetatable jbe/bsw@0: local ipairs = ipairs jbe/bsw@0: local next = next jbe/bsw@0: local pairs = pairs jbe/bsw@0: local print = print jbe/bsw@0: local rawequal = rawequal jbe/bsw@0: local rawget = rawget jbe@64: local rawlen = rawlen jbe/bsw@0: local rawset = rawset jbe/bsw@0: local select = select jbe/bsw@0: local setmetatable = setmetatable jbe/bsw@0: local tonumber = tonumber jbe/bsw@0: local tostring = tostring jbe/bsw@0: local type = type jbe/bsw@0: jbe/bsw@0: local math = math jbe@89: local os = os jbe/bsw@0: local string = string jbe/bsw@0: local table = table jbe/bsw@0: jbe@64: local _M = {} jbe@64: if _ENV then jbe@64: _ENV = _M jbe@64: else jbe@64: _G[...] = _M jbe@64: setfenv(1, _M) jbe@64: end jbe/bsw@0: jbe/bsw@0: jbe/bsw@0: jbe/bsw@0: --------------------------------------- jbe/bsw@0: -- general functions and definitions -- jbe/bsw@0: --------------------------------------- jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: bool = -- true, if value is an integer within resolution jbe/bsw@0: atom.is_integer( jbe/bsw@0: value -- value to be tested jbe/bsw@0: ) jbe/bsw@0: jbe/bsw@0: This function returns true if the given object is an integer within resolution. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function is_integer(i) jbe/bsw@0: return jbe/bsw@0: type(i) == "number" and i % 1 == 0 and jbe/bsw@0: (i + 1) - i == 1 and i - (i - 1) == 1 jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: atom.not_a_number jbe/bsw@0: jbe/bsw@0: Value representing an invalid numeric result. Used for atom.integer.invalid and atom.number.invalid. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: not_a_number = 0 / 0 jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: do jbe/bsw@0: jbe/bsw@0: local shadow = setmetatable({}, { __mode = "k" }) jbe/bsw@0: jbe/bsw@0: local type_mt = { __index = {} } jbe/bsw@0: jbe/bsw@0: function type_mt:__call(...) jbe/bsw@0: return self:new(...) jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function type_mt.__index:_create(data) jbe/bsw@0: local value = setmetatable({}, self) jbe/bsw@0: shadow[value] = data jbe/bsw@0: return value jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: local function write_prohibited() jbe/bsw@0: error("Modification of an atom is prohibited.") jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: -- returns a new type as a table, which serves also as metatable jbe/bsw@0: function create_new_type(name) jbe/bsw@0: local t = setmetatable( jbe/bsw@0: { methods = {}, getters = {}, name = name }, jbe/bsw@0: type_mt jbe/bsw@0: ) jbe/bsw@0: function t.__index(self, key) jbe/bsw@0: local data = shadow[self] jbe/bsw@0: local value = data[key] jbe/bsw@0: if value ~= nil then return value end jbe/bsw@0: local method = t.methods[key] jbe/bsw@0: if method then return method end jbe/bsw@0: local getter = t.getters[key] jbe/bsw@0: if getter then return getter(self) end jbe/bsw@0: end jbe/bsw@0: t.__newindex = write_prohibited jbe/bsw@0: return t jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: bool = -- true, if 'value' is of type 't' jbe/bsw@0: atom.has_type( jbe/bsw@0: value, -- any value jbe/bsw@0: t -- atom time, e.g. atom.date, or lua type, e.g. "string" jbe/bsw@0: ) jbe/bsw@0: jbe/bsw@0: This function checks, if a value is of a given type. The value may be an invalid value though, e.g. atom.date.invalid. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function has_type(value, t) jbe/bsw@0: if t == nil then error("No type passed to has_type(...) function.") end jbe/bsw@0: local lua_type = type(value) jbe/bsw@0: return jbe/bsw@0: lua_type == t or jbe/bsw@0: getmetatable(value) == t or jbe/bsw@0: (lua_type == "boolean" and t == _M.boolean) or jbe/bsw@0: (lua_type == "string" and t == _M.string) or ( jbe/bsw@0: lua_type == "number" and jbe/bsw@0: (t == _M.number or ( jbe/bsw@0: t == _M.integer and ( jbe/bsw@0: not (value <= 0 or value >= 0) or ( jbe/bsw@0: value % 1 == 0 and jbe/bsw@0: (value + 1) - value == 1 and jbe/bsw@0: value - (value - 1) == 1 jbe/bsw@0: ) jbe/bsw@0: ) jbe/bsw@0: )) jbe/bsw@0: ) jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: bool = -- true, if 'value' is of type 't' jbe/bsw@0: atom.is_valid( jbe/bsw@0: value, -- any value jbe/bsw@0: t -- atom time, e.g. atom.date, or lua type, e.g. "string" jbe/bsw@0: ) jbe/bsw@0: jbe/bsw@0: This function checks, if a value is valid. It optionally checks, if the value is of a given type. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function is_valid(value, t) jbe/bsw@0: local lua_type = type(value) jbe/bsw@0: if lua_type == "table" then jbe/bsw@0: local mt = getmetatable(value) jbe/bsw@0: if t then jbe/bsw@0: return t == mt and not value.invalid jbe/bsw@0: else jbe/bsw@0: return (getmetatable(mt) == type_mt) and not value.invalid jbe/bsw@0: end jbe/bsw@0: elseif lua_type == "boolean" then jbe/bsw@0: return not t or t == "boolean" or t == _M.boolean jbe/bsw@0: elseif lua_type == "string" then jbe/bsw@0: return not t or t == "string" or t == _M.string jbe/bsw@0: elseif lua_type == "number" then jbe/bsw@0: if t == _M.integer then jbe/bsw@0: return jbe/bsw@0: value % 1 == 0 and jbe/bsw@0: (value + 1) - value == 1 and jbe/bsw@0: value - (value - 1) == 1 jbe/bsw@0: else jbe/bsw@0: return jbe/bsw@0: (not t or t == "number" or t == _M.number) and jbe/bsw@0: (value <= 0 or value >= 0) jbe/bsw@0: end jbe/bsw@0: else jbe/bsw@0: return false jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: string = -- string representation to be passed to a load function jbe/bsw@0: atom.dump( jbe/bsw@0: value -- value to be dumped jbe/bsw@0: ) jbe/bsw@0: jbe/bsw@0: This function returns a string representation of the given value. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function dump(obj) jbe/bsw@0: if obj == nil then jbe/bsw@0: return "" jbe/bsw@0: else jbe/bsw@0: return tostring(obj) jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: jbe/bsw@0: jbe/bsw@0: ------------- jbe/bsw@0: -- boolean -- jbe/bsw@0: ------------- jbe/bsw@0: jbe/bsw@0: boolean = { name = "boolean" } jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: bool = -- true, false, or nil jbe/bsw@0: atom.boolean:load( jbe/bsw@0: string -- string to be interpreted as boolean jbe/bsw@0: ) jbe/bsw@0: jbe/bsw@0: This method returns true or false or nil, depending on the input string. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function boolean:load(str) jbe@1: if str == nil or str == "" then jbe@1: return nil jbe@1: elseif type(str) ~= "string" then jbe/bsw@0: error("String expected") jbe/bsw@0: elseif string.find(str, "^[TtYy1]") then jbe/bsw@0: return true jbe/bsw@0: elseif string.find(str, "^[FfNn0]") then jbe/bsw@0: return false jbe/bsw@0: else jbe/bsw@0: return nil -- we don't have an undefined bool jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: jbe/bsw@0: jbe/bsw@0: ------------ jbe/bsw@0: -- string -- jbe/bsw@0: ------------ jbe/bsw@0: jbe/bsw@0: _M.string = { name = "string" } jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: string = -- the same string jbe/bsw@0: atom.string:load( jbe/bsw@0: string -- a string jbe/bsw@0: ) jbe/bsw@0: jbe/bsw@0: This method returns the passed string, or throws an error, if the passed argument is not a string. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function _M.string:load(str) jbe@1: if str == nil then jbe@1: return nil jbe@1: elseif type(str) ~= "string" then jbe/bsw@0: error("String expected") jbe/bsw@0: else jbe/bsw@0: return str jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: jbe/bsw@0: jbe/bsw@0: ------------- jbe/bsw@0: -- integer -- jbe/bsw@0: ------------- jbe/bsw@0: jbe/bsw@0: integer = { name = "integer" } jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: int = -- an integer or atom.integer.invalid (atom.not_a_number) jbe/bsw@0: atom.integer:load( jbe/bsw@0: string -- a string representing an integer jbe/bsw@0: ) jbe/bsw@0: jbe/bsw@0: This method returns an integer represented by the given string. If the string doesn't represent a valid integer, then not-a-number is returned. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function integer:load(str) jbe@1: if str == nil or str == "" then jbe@1: return nil jbe@1: elseif type(str) ~= "string" then jbe/bsw@0: error("String expected") jbe/bsw@0: else jbe/bsw@0: local num = tonumber(str) jbe/bsw@0: if is_integer(num) then return num else return not_a_number end jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: atom.integer.invalid jbe/bsw@0: jbe/bsw@0: This represents an invalid integer. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: integer.invalid = not_a_number jbe@196: --//-- jbe/bsw@0: jbe/bsw@0: jbe/bsw@0: jbe/bsw@0: ------------ jbe/bsw@0: -- number -- jbe/bsw@0: ------------ jbe/bsw@0: jbe/bsw@0: number = create_new_type("number") jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: int = -- a number or atom.number.invalid (atom.not_a_number) jbe/bsw@0: atom.number:load( jbe/bsw@0: string -- a string representing a number jbe/bsw@0: ) jbe/bsw@0: jbe/bsw@0: This method returns a number represented by the given string. If the string doesn't represent a valid number, then not-a-number is returned. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function number:load(str) jbe@1: if str == nil or str == "" then jbe@1: return nil jbe@1: elseif type(str) ~= "string" then jbe/bsw@0: error("String expected") jbe/bsw@0: else jbe/bsw@0: return tonumber(str) or not_a_number jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: atom.number.invalid jbe/bsw@0: jbe/bsw@0: This represents an invalid number. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: number.invalid = not_a_number jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: jbe/bsw@0: jbe/bsw@0: -------------- jbe/bsw@0: -- fraction -- jbe/bsw@0: -------------- jbe/bsw@0: jbe/bsw@0: fraction = create_new_type("fraction") jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: i = -- the greatest common divisor (GCD) of all given natural numbers jbe/bsw@0: atom.gcd( jbe/bsw@0: a, -- a natural number jbe/bsw@0: b, -- another natural number jbe/bsw@0: ... -- optionally more natural numbers jbe/bsw@0: ) jbe/bsw@0: jbe/bsw@0: This function returns the greatest common divisor (GCD) of two or more natural numbers. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function gcd(a, b, ...) jbe/bsw@0: if a % 1 ~= 0 or a <= 0 then return 0 / 0 end jbe/bsw@0: if b == nil then jbe/bsw@0: return a jbe/bsw@0: else jbe/bsw@0: if b % 1 ~= 0 or b <= 0 then return 0 / 0 end jbe/bsw@0: if ... == nil then jbe/bsw@0: local k = 0 jbe/bsw@0: local t jbe/bsw@0: while a % 2 == 0 and b % 2 == 0 do jbe/bsw@0: a = a / 2; b = b / 2; k = k + 1 jbe/bsw@0: end jbe/bsw@0: if a % 2 == 0 then t = a else t = -b end jbe/bsw@0: while t ~= 0 do jbe/bsw@0: while t % 2 == 0 do t = t / 2 end jbe/bsw@0: if t > 0 then a = t else b = -t end jbe/bsw@0: t = a - b jbe/bsw@0: end jbe/bsw@0: return a * 2 ^ k jbe/bsw@0: else jbe/bsw@0: return gcd(gcd(a, b), ...) jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: i = --the least common multiple (LCD) of all given natural numbers jbe/bsw@0: atom.lcm( jbe/bsw@0: a, -- a natural number jbe/bsw@0: b, -- another natural number jbe/bsw@0: ... -- optionally more natural numbers jbe/bsw@0: ) jbe/bsw@0: jbe/bsw@0: This function returns the least common multiple (LCD) of two or more natural numbers. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function lcm(a, b, ...) jbe/bsw@0: if a % 1 ~= 0 or a <= 0 then return 0 / 0 end jbe/bsw@0: if b == nil then jbe/bsw@0: return a jbe/bsw@0: else jbe/bsw@0: if b % 1 ~= 0 or b <= 0 then return 0 / 0 end jbe/bsw@0: if ... == nil then jbe/bsw@0: return a * b / gcd(a, b) jbe/bsw@0: else jbe/bsw@0: return lcm(lcm(a, b), ...) jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: atom.fraction.invalid jbe/bsw@0: jbe/bsw@0: Value representing an invalid fraction. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: fraction.invalid = fraction:_create{ jbe/bsw@0: numerator = not_a_number, denominator = not_a_number, invalid = true jbe/bsw@0: } jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: frac = -- fraction jbe/bsw@0: atom.fraction:new( jbe/bsw@0: numerator, -- numerator jbe/bsw@0: denominator -- denominator jbe/bsw@0: ) jbe/bsw@0: jbe/bsw@0: This method creates a new fraction. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function fraction:new(numerator, denominator) jbe/bsw@0: if not ( jbe/bsw@0: (numerator == nil or type(numerator) == "number") and jbe/bsw@0: (denominator == nil or type(denominator) == "number") jbe/bsw@0: ) then jbe/bsw@0: error("Invalid arguments passed to fraction constructor.") jbe/bsw@0: elseif jbe/bsw@0: (not is_integer(numerator)) or jbe/bsw@0: (denominator and (not is_integer(denominator))) jbe/bsw@0: then jbe/bsw@0: return fraction.invalid jbe/bsw@0: elseif denominator then jbe/bsw@0: if denominator == 0 then jbe/bsw@0: return fraction.invalid jbe/bsw@0: elseif numerator == 0 then jbe/bsw@0: return fraction:_create{ numerator = 0, denominator = 1, float = 0 } jbe/bsw@0: else jbe/bsw@0: local d = gcd(math.abs(numerator), math.abs(denominator)) jbe/bsw@0: if denominator < 0 then d = -d end jbe/bsw@0: local numerator2, denominator2 = numerator / d, denominator / d jbe/bsw@0: return fraction:_create{ jbe/bsw@0: numerator = numerator2, jbe/bsw@0: denominator = denominator2, jbe/bsw@0: float = numerator2 / denominator2 jbe/bsw@0: } jbe/bsw@0: end jbe/bsw@0: else jbe/bsw@0: return fraction:_create{ jbe/bsw@0: numerator = numerator, denominator = 1, float = numerator jbe/bsw@0: } jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: frac = -- fraction represented by the given string jbe/bsw@0: atom.fraction:load( jbe/bsw@0: string -- string representation of a fraction jbe/bsw@0: ) jbe/bsw@0: jbe/bsw@0: This method returns a fraction represented by the given string. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function fraction:load(str) jbe@1: if str == nil or str == "" then jbe/bsw@0: return nil jbe@1: elseif type(str) ~= "string" then jbe@1: error("String expected") jbe/bsw@0: else jbe/bsw@0: local sign, int = string.match(str, "^(%-?)([0-9]+)$") jbe/bsw@0: if sign == "" then return fraction:new(tonumber(int)) jbe/bsw@0: elseif sign == "-" then return fraction:new(- tonumber(int)) jbe/bsw@0: end jbe/bsw@0: local sign, n, d = string.match(str, "^(%-?)([0-9]+)/([0-9]+)$") jbe/bsw@0: if sign == "" then return fraction:new(tonumber(n), tonumber(d)) jbe/bsw@0: elseif sign == "-" then return fraction:new(- tonumber(n), tonumber(d)) jbe/bsw@0: end jbe/bsw@0: return fraction.invalid jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: function fraction:__tostring() jbe/bsw@0: if self.invalid then jbe/bsw@0: return "not_a_fraction" jbe/bsw@0: else jbe/bsw@0: return self.numerator .. "/" .. self.denominator jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function fraction.__eq(value1, value2) jbe/bsw@0: if value1.invalid or value2.invalid then jbe/bsw@0: return false jbe/bsw@0: else jbe/bsw@0: return jbe/bsw@0: value1.numerator == value2.numerator and jbe/bsw@0: value1.denominator == value2.denominator jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function fraction.__lt(value1, value2) jbe/bsw@0: if value1.invalid or value2.invalid then jbe/bsw@0: return false jbe/bsw@0: else jbe/bsw@0: return value1.float < value2.float jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function fraction.__le(value1, value2) jbe/bsw@0: if value1.invalid or value2.invalid then jbe/bsw@0: return false jbe/bsw@0: else jbe/bsw@0: return value1.float <= value2.float jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function fraction:__unm() jbe/bsw@0: return fraction(-self.numerator, self.denominator) jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: do jbe/bsw@0: jbe/bsw@0: local function extract(value1, value2) jbe/bsw@0: local n1, d1, n2, d2 jbe/bsw@0: if getmetatable(value1) == fraction then jbe/bsw@0: n1 = value1.numerator jbe/bsw@0: d1 = value1.denominator jbe/bsw@0: elseif type(value1) == "number" then jbe/bsw@0: n1 = value1 jbe/bsw@0: d1 = 1 jbe/bsw@0: else jbe/bsw@0: error("Left operand of operator has wrong type.") jbe/bsw@0: end jbe/bsw@0: if getmetatable(value2) == fraction then jbe/bsw@0: n2 = value2.numerator jbe/bsw@0: d2 = value2.denominator jbe/bsw@0: elseif type(value2) == "number" then jbe/bsw@0: n2 = value2 jbe/bsw@0: d2 = 1 jbe/bsw@0: else jbe/bsw@0: error("Right operand of operator has wrong type.") jbe/bsw@0: end jbe/bsw@0: return n1, d1, n2, d2 jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function fraction.__add(value1, value2) jbe/bsw@0: local n1, d1, n2, d2 = extract(value1, value2) jbe/bsw@0: return fraction(n1 * d2 + n2 * d1, d1 * d2) jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function fraction.__sub(value1, value2) jbe/bsw@0: local n1, d1, n2, d2 = extract(value1, value2) jbe/bsw@0: return fraction(n1 * d2 - n2 * d1, d1 * d2) jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function fraction.__mul(value1, value2) jbe/bsw@0: local n1, d1, n2, d2 = extract(value1, value2) jbe/bsw@0: return fraction(n1 * n2, d1 * d2) jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function fraction.__div(value1, value2) jbe/bsw@0: local n1, d1, n2, d2 = extract(value1, value2) jbe/bsw@0: return fraction(n1 * d2, d1 * n2) jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function fraction.__pow(value1, value2) jbe/bsw@0: local n1, d1, n2, d2 = extract(value1, value2) jbe/bsw@0: local n1_abs = math.abs(n1) jbe/bsw@0: local d1_abs = math.abs(d1) jbe/bsw@0: local n2_abs = math.abs(n2) jbe/bsw@0: local d2_abs = math.abs(d2) jbe/bsw@0: local numerator, denominator jbe/bsw@0: if d2_abs == 1 then jbe/bsw@0: numerator = n1_abs jbe/bsw@0: denominator = d1_abs jbe/bsw@0: else jbe/bsw@0: numerator = 0 jbe/bsw@0: while true do jbe/bsw@0: local t = numerator ^ d2_abs jbe/bsw@0: if t == n1_abs then break end jbe/bsw@0: if not (t < n1_abs) then return value1.float / value2.float end jbe/bsw@0: numerator = numerator + 1 jbe/bsw@0: end jbe/bsw@0: denominator = 1 jbe/bsw@0: while true do jbe/bsw@0: local t = denominator ^ d2_abs jbe/bsw@0: if t == d1_abs then break end jbe/bsw@0: if not (t < d1_abs) then return value1.float / value2.float end jbe/bsw@0: denominator = denominator + 1 jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: if n1 < 0 then jbe/bsw@0: if d2_abs % 2 == 1 then jbe/bsw@0: numerator = -numerator jbe/bsw@0: else jbe/bsw@0: return fraction.invalid jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: if n2 < 0 then jbe/bsw@0: numerator, denominator = denominator, numerator jbe/bsw@0: end jbe/bsw@0: return fraction(numerator ^ n2_abs, denominator ^ n2_abs) jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: jbe/bsw@0: jbe/bsw@0: ---------- jbe/bsw@0: -- date -- jbe/bsw@0: ---------- jbe/bsw@0: jbe/bsw@0: date = create_new_type("date") jbe/bsw@0: jbe/bsw@0: do jbe/bsw@0: local c1 = 365 -- days of a non-leap year jbe/bsw@0: local c4 = 4 * c1 + 1 -- days of a full 4 year cycle jbe/bsw@0: local c100 = 25 * c4 - 1 -- days of a full 100 year cycle jbe/bsw@0: local c400 = 4 * c100 + 1 -- days of a full 400 year cycle jbe/bsw@0: local get_month_offset -- function returning days elapsed within jbe/bsw@0: -- the given year until the given month jbe/bsw@0: -- (exclusive the given month) jbe/bsw@0: do jbe/bsw@0: local normal_month_offsets = {} jbe/bsw@0: local normal_month_lengths = { jbe/bsw@0: 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 jbe/bsw@0: } jbe/bsw@0: local sum = 0 jbe/bsw@0: for i = 1, 12 do jbe/bsw@0: normal_month_offsets[i] = sum jbe/bsw@0: sum = sum + normal_month_lengths[i] jbe/bsw@0: end jbe/bsw@0: function get_month_offset(year, month) jbe/bsw@0: if jbe/bsw@0: (((year % 4 == 0) and not (year % 100 == 0)) or (year % 400 == 0)) jbe/bsw@0: and month > 2 jbe/bsw@0: then jbe/bsw@0: return normal_month_offsets[month] + 1 jbe/bsw@0: else jbe/bsw@0: return normal_month_offsets[month] jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: jd = -- days from January 1st 1970 jbe/bsw@0: atom.date.ymd_to_jd( jbe/bsw@0: year, -- year jbe/bsw@0: month, -- month from 1 to 12 jbe/bsw@0: day -- day from 1 to 31 jbe/bsw@0: ) jbe/bsw@0: jbe/bsw@0: This function calculates the days from January 1st 1970 for a given year, month and day. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: local offset = 0 jbe/bsw@0: function date.ymd_to_jd(year, month, day) jbe/bsw@0: assert(is_integer(year), "Invalid year specified.") jbe/bsw@0: assert(is_integer(month), "Invalid month specified.") jbe/bsw@0: assert(is_integer(day), "Invalid day specified.") jbe/bsw@0: local calc_year = year - 1 jbe/bsw@0: local n400 = math.floor(calc_year / 400) jbe/bsw@0: local r400 = calc_year % 400 jbe/bsw@0: local n100 = math.floor(r400 / 100) jbe/bsw@0: local r100 = r400 % 100 jbe/bsw@0: local n4 = math.floor(r100 / 4) jbe/bsw@0: local n1 = r100 % 4 jbe/bsw@0: local jd = ( jbe/bsw@0: c400 * n400 + c100 * n100 + c4 * n4 + c1 * n1 + jbe/bsw@0: get_month_offset(year, month) + (day - 1) jbe/bsw@0: ) jbe/bsw@0: return jd - offset jbe/bsw@0: end jbe/bsw@0: offset = date.ymd_to_jd(1970, 1, 1) jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: year, -- year jbe/bsw@0: month, -- month from 1 to 12 jbe/bsw@0: day = -- day from 1 to 31 jbe/bsw@0: atom.date.jd_to_ymd( jbe/bsw@0: jd, -- days from January 1st 1970 jbe/bsw@0: ) jbe/bsw@0: jbe/bsw@0: Given the days from January 1st 1970 this function returns year, month and day. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function date.jd_to_ymd(jd) jbe/bsw@0: assert(is_integer(jd), "Invalid julian date specified.") jbe/bsw@0: local calc_jd = jd + offset jbe/bsw@0: assert(is_integer(calc_jd), "Julian date is out of range.") jbe/bsw@0: local n400 = math.floor(calc_jd / c400) jbe/bsw@0: local r400 = calc_jd % c400 jbe/bsw@0: local n100 = math.floor(r400 / c100) jbe/bsw@0: local r100 = r400 % c100 jbe/bsw@0: if n100 == 4 then n100, r100 = 3, c100 end jbe/bsw@0: local n4 = math.floor(r100 / c4) jbe/bsw@0: local r4 = r100 % c4 jbe/bsw@0: local n1 = math.floor(r4 / c1) jbe/bsw@0: local r1 = r4 % c1 jbe/bsw@0: if n1 == 4 then n1, r1 = 3, c1 end jbe/bsw@0: local year = 1 + 400 * n400 + 100 * n100 + 4 * n4 + n1 jbe/bsw@0: local month = 1 + math.floor(r1 / 31) jbe/bsw@0: local month_offset = get_month_offset(year, month) jbe/bsw@0: if month < 12 then jbe/bsw@0: local next_month_offset = get_month_offset(year, month + 1) jbe/bsw@0: if r1 >= next_month_offset then jbe/bsw@0: month = month + 1 jbe/bsw@0: month_offset = next_month_offset jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: local day = 1 + r1 - month_offset jbe/bsw@0: return year, month, day jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: atom.date.invalid jbe/bsw@0: jbe/bsw@0: Value representing an invalid date. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: date.invalid = date:_create{ jbe/bsw@0: jd = not_a_number, jbe/bsw@0: year = not_a_number, month = not_a_number, day = not_a_number, jbe/bsw@0: invalid = true jbe/bsw@0: } jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: d = -- date based on the given data jbe/bsw@0: atom.date:new{ jbe/bsw@0: jd = jd, -- days since January 1st 1970 jbe/bsw@0: year = year, -- year jbe/bsw@0: month = month, -- month from 1 to 12 jbe/bsw@0: day = day, -- day from 1 to 31 jbe/bsw@0: iso_weekyear = iso_weekyear, -- year according to ISO 8601 jbe/bsw@0: iso_week = iso_week, -- week number according to ISO 8601 jbe/bsw@0: iso_weekday = iso_weekday, -- day of week from 1 for monday to 7 for sunday jbe/bsw@0: us_weekyear = us_weekyear, -- year jbe/bsw@0: us_week = us_week, -- week number according to US style counting jbe/bsw@0: us_weekday = us_weekday -- day of week from 1 for sunday to 7 for saturday jbe/bsw@0: } jbe/bsw@0: jbe/bsw@0: This method returns a new date value, based on given data. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function date:new(args) jbe/bsw@0: local args = args jbe/bsw@0: if type(args) == "number" then args = { jd = args } end jbe/bsw@0: if type(args) == "table" then jbe/bsw@0: local year, month, day = args.year, args.month, args.day jbe/bsw@0: local jd = args.jd jbe/bsw@0: local iso_weekyear = args.iso_weekyear jbe/bsw@0: local iso_week = args.iso_week jbe/bsw@0: local iso_weekday = args.iso_weekday jbe/bsw@0: local us_week = args.us_week jbe/bsw@0: local us_weekday = args.us_weekday jbe/bsw@0: if jbe/bsw@0: type(year) == "number" and jbe/bsw@0: type(month) == "number" and jbe/bsw@0: type(day) == "number" jbe/bsw@0: then jbe/bsw@0: if jbe/bsw@0: is_integer(year) and year >= 1 and year <= 9999 and jbe/bsw@0: is_integer(month) and month >= 1 and month <= 12 and jbe/bsw@0: is_integer(day) and day >= 1 and day <= 31 jbe/bsw@0: then jbe@531: local jd = date.ymd_to_jd(year, month, day) jbe@531: local year2, month2, day2 = date.jd_to_ymd(jd) jbe@531: if year == year2 and month == month2 and day == day2 then jbe@531: return date:_create{ jbe@531: jd = date.ymd_to_jd(year, month, day), jbe@531: year = year2, month = month2, day = day2 jbe@531: } jbe@531: else jbe@531: return date.invalid jbe@531: end jbe/bsw@0: else jbe/bsw@0: return date.invalid jbe/bsw@0: end jbe/bsw@0: elseif type(jd) == "number" then jbe/bsw@0: if is_integer(jd) and jd >= -719162 and jd <= 2932896 then jbe/bsw@0: local year, month, day = date.jd_to_ymd(jd) jbe/bsw@0: return date:_create{ jbe/bsw@0: jd = jd, year = year, month = month, day = day jbe/bsw@0: } jbe/bsw@0: else jbe/bsw@0: return date.invalid jbe/bsw@0: end jbe/bsw@0: elseif jbe/bsw@0: type(year) == "number" and not iso_weekyear and jbe/bsw@0: type(iso_week) == "number" and jbe/bsw@0: type(iso_weekday) == "number" jbe/bsw@0: then jbe/bsw@0: if jbe/bsw@0: is_integer(year) and jbe/bsw@0: is_integer(iso_week) and iso_week >= 0 and iso_week <= 53 and jbe/bsw@0: is_integer(iso_weekday) and iso_weekday >= 1 and iso_weekday <= 7 jbe/bsw@0: then jbe/bsw@0: local jan4 = date{ year = year, month = 1, day = 4 } jbe/bsw@0: local reference = jan4 - jan4.iso_weekday - 7 -- Sun. of week -1 jbe/bsw@0: return date(reference + 7 * iso_week + iso_weekday) jbe/bsw@0: else jbe/bsw@0: return date.invalid jbe/bsw@0: end jbe/bsw@0: elseif jbe/bsw@0: type(iso_weekyear) == "number" and not year and jbe/bsw@0: type(iso_week) == "number" and jbe/bsw@0: type(iso_weekday) == "number" jbe/bsw@0: then jbe/bsw@0: if jbe/bsw@0: is_integer(iso_weekyear) and jbe/bsw@0: is_integer(iso_week) and iso_week >= 0 and iso_week <= 53 and jbe/bsw@0: is_integer(iso_weekday) and iso_weekday >= 1 and iso_weekday <= 7 jbe/bsw@0: then jbe/bsw@0: local guessed = date{ jbe/bsw@0: year = iso_weekyear, jbe/bsw@0: iso_week = iso_week, jbe/bsw@0: iso_weekday = iso_weekday jbe/bsw@0: } jbe/bsw@0: if guessed.invalid or guessed.iso_weekyear == iso_weekyear then jbe/bsw@0: return guessed jbe/bsw@0: else jbe/bsw@0: local year jbe/bsw@0: if iso_week <= 1 then jbe/bsw@0: year = iso_weekyear - 1 jbe/bsw@0: elseif iso_week >= 52 then jbe/bsw@0: year = iso_weekyear + 1 jbe/bsw@0: else jbe/bsw@0: error("Internal error in ISO week computation occured.") jbe/bsw@0: end jbe/bsw@0: return date{ jbe/bsw@0: year = year, iso_week = iso_week, iso_weekday = iso_weekday jbe/bsw@0: } jbe/bsw@0: end jbe/bsw@0: else jbe/bsw@0: return date.invalid jbe/bsw@0: end jbe/bsw@0: elseif jbe/bsw@0: type(year) == "number" and jbe/bsw@0: type(us_week) == "number" and jbe/bsw@0: type(us_weekday) == "number" jbe/bsw@0: then jbe/bsw@0: if jbe/bsw@0: is_integer(year) and jbe/bsw@0: is_integer(us_week) and us_week >= 0 and us_week <= 54 and jbe/bsw@0: is_integer(us_weekday) and us_weekday >= 1 and us_weekday <= 7 jbe/bsw@0: then jbe/bsw@0: local jan1 = date{ year = year, month = 1, day = 1 } jbe/bsw@0: local reference = jan1 - jan1.us_weekday - 7 -- Sat. of week -1 jbe/bsw@0: return date(reference + 7 * us_week + us_weekday) jbe/bsw@0: else jbe/bsw@0: return date.invalid jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: error("Illegal arguments passed to date constructor.") jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: atom.date:get_current() jbe/bsw@0: jbe/bsw@0: This function returns today's date. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function date:get_current() jbe/bsw@0: local now = os.date("*t") jbe/bsw@0: return date{ jbe/bsw@0: year = now.year, month = now.month, day = now.day jbe/bsw@0: } jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: date = -- date represented by the string jbe/bsw@0: atom.date:load( jbe/bsw@0: string -- string representing a date jbe/bsw@0: ) jbe/bsw@0: jbe/bsw@0: This method returns a date represented by the given string. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function date:load(str) jbe@1: if str == nil or str == "" then jbe/bsw@0: return nil jbe@1: elseif type(str) ~= "string" then jbe@1: error("String expected") jbe/bsw@0: else jbe/bsw@0: local year, month, day = string.match( jbe/bsw@0: str, "^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$" jbe/bsw@0: ) jbe/bsw@0: if year then jbe/bsw@0: return date{ jbe/bsw@0: year = tonumber(year), jbe/bsw@0: month = tonumber(month), jbe/bsw@0: day = tonumber(day) jbe/bsw@0: } jbe/bsw@0: else jbe/bsw@0: return date.invalid jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: function date:__tostring() jbe/bsw@0: if self.invalid then jbe/bsw@0: return "invalid_date" jbe/bsw@0: else jbe/bsw@0: return string.format( jbe/bsw@0: "%04i-%02i-%02i", self.year, self.month, self.day jbe/bsw@0: ) jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function date.getters:midnight() jbe@90: return timestamp{ year = self.year, month = self.month, day = self.day } jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function date.getters:midday() jbe@90: return timestamp{ jbe/bsw@0: year = self.year, month = self.month, day = self.day, jbe/bsw@0: hour = 12 jbe/bsw@0: } jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function date.getters:iso_weekday() -- 1 = Monday jbe/bsw@0: return (self.jd + 3) % 7 + 1 jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function date.getters:us_weekday() -- 1 = Sunday jbe/bsw@0: return (self.jd + 4) % 7 + 1 jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function date.getters:iso_weekyear() -- ISO week-numbering year jbe/bsw@0: local year, month, day = self.year, self.month, self.day jbe/bsw@0: local iso_weekday = self.iso_weekday jbe/bsw@0: if month == 1 then jbe/bsw@0: if jbe/bsw@0: (day == 3 and iso_weekday == 7) or jbe/bsw@0: (day == 2 and iso_weekday >= 6) or jbe/bsw@0: (day == 1 and iso_weekday >= 5) jbe/bsw@0: then jbe/bsw@0: return year - 1 jbe/bsw@0: end jbe/bsw@0: elseif month == 12 then jbe/bsw@0: if jbe/bsw@0: (day == 29 and iso_weekday == 1) or jbe/bsw@0: (day == 30 and iso_weekday <= 2) or jbe/bsw@0: (day == 31 and iso_weekday <= 3) jbe/bsw@0: then jbe/bsw@0: return year + 1 jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: return year jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function date.getters:iso_week() jbe/bsw@0: local jan4 = date{ year = self.iso_weekyear, month = 1, day = 4 } jbe/bsw@0: local reference = jan4.jd - jan4.iso_weekday - 6 -- monday of week 0 jbe/bsw@0: return math.floor((self.jd - reference) / 7) jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function date.getters:us_week() jbe/bsw@0: local jan1 = date{ year = self.year, month = 1, day = 1 } jbe/bsw@0: local reference = jan1.jd - jan1.us_weekday - 6 -- sunday of week 0 jbe/bsw@0: return math.floor((self.jd - reference) / 7) jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function date.__eq(value1, value2) jbe/bsw@0: if value1.invalid or value2.invalid then jbe/bsw@0: return false jbe/bsw@0: else jbe/bsw@0: return value1.jd == value2.jd jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function date.__lt(value1, value2) jbe/bsw@0: if value1.invalid or value2.invalid then jbe/bsw@0: return false jbe/bsw@0: else jbe/bsw@0: return value1.jd < value2.jd jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function date.__le(value1, value2) jbe/bsw@0: if value1.invalid or value2.invalid then jbe/bsw@0: return false jbe/bsw@0: else jbe/bsw@0: return value1.jd <= value2.jd jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function date.__add(value1, value2) jbe/bsw@0: if getmetatable(value1) == date then jbe/bsw@0: if getmetatable(value2) == date then jbe/bsw@0: error("Can not add two dates.") jbe/bsw@0: elseif type(value2) == "number" then jbe/bsw@0: return date(value1.jd + value2) jbe/bsw@0: else jbe/bsw@0: error("Right operand of '+' operator has wrong type.") jbe/bsw@0: end jbe/bsw@0: elseif type(value1) == "number" then jbe/bsw@0: if getmetatable(value2) == date then jbe/bsw@0: return date(value1 + value2.jd) jbe/bsw@0: else jbe/bsw@0: error("Assertion failed") jbe/bsw@0: end jbe/bsw@0: else jbe/bsw@0: error("Left operand of '+' operator has wrong type.") jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function date.__sub(value1, value2) jbe/bsw@0: if not getmetatable(value1) == date then jbe/bsw@0: error("Left operand of '-' operator has wrong type.") jbe/bsw@0: end jbe/bsw@0: if getmetatable(value2) == date then jbe@107: return value1.jd - value2.jd -- TODO: transform to interval jbe/bsw@0: elseif type(value2) == "number" then jbe/bsw@0: return date(value1.jd - value2) jbe/bsw@0: else jbe/bsw@0: error("Right operand of '-' operator has wrong type.") jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: jbe/bsw@0: jbe/bsw@0: --------------- jbe/bsw@0: -- timestamp -- jbe/bsw@0: --------------- jbe/bsw@0: jbe/bsw@0: timestamp = create_new_type("timestamp") jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: tsec = -- seconds since January 1st 1970 00:00 jbe/bsw@0: atom.timestamp.ymdhms_to_tsec( jbe/bsw@0: year, -- year jbe/bsw@0: month, -- month from 1 to 12 jbe/bsw@0: day, -- day from 1 to 31 jbe/bsw@0: hour, -- hour from 0 to 23 jbe/bsw@0: minute, -- minute from 0 to 59 jbe/bsw@0: second -- second from 0 to 59 jbe/bsw@0: ) jbe/bsw@0: jbe/bsw@0: Given the year, month, day, hour, minute and second, this function returns the number of seconds since January 1st 1970 00:00. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function timestamp.ymdhms_to_tsec(year, month, day, hour, minute, second) jbe/bsw@0: return jbe/bsw@0: 86400 * date.ymd_to_jd(year, month, day) + jbe/bsw@0: 3600 * hour + 60 * minute + second jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: year, -- year jbe/bsw@0: month, -- month from 1 to 12 jbe/bsw@0: day, -- day from 1 to 31 jbe/bsw@0: hour, -- hour from 0 to 23 jbe/bsw@0: minute, -- minute from 0 to 59 jbe/bsw@0: second = -- second from 0 to 59 jbe/bsw@0: atom.timestamp.tsec_to_ymdhms( jbe/bsw@0: tsec -- seconds since January 1st 1970 00:00 jbe/bsw@0: ) jbe/bsw@0: jbe/bsw@0: Given the seconds since January 1st 1970 00:00, this function returns the year, month, day, hour, minute and second. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function timestamp.tsec_to_ymdhms(tsec) jbe/bsw@0: local jd = math.floor(tsec / 86400) jbe/bsw@0: local dsec = tsec % 86400 jbe/bsw@0: local year, month, day = date.jd_to_ymd(jd) jbe/bsw@0: local hour = math.floor(dsec / 3600) jbe/bsw@0: local minute = math.floor((dsec % 3600) / 60) jbe/bsw@0: local second = dsec % 60 jbe/bsw@0: return year, month, day, hour, minute, second jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: --[[-- jbe@320: atom.timestamp.invalid jbe/bsw@0: jbe/bsw@0: Value representing an invalid timestamp. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: timestamp.invalid = timestamp:_create{ jbe/bsw@0: tsec = not_a_number, jbe/bsw@0: year = not_a_number, month = not_a_number, day = not_a_number, jbe/bsw@0: hour = not_a_number, minute = not_a_number, second = not_a_number, jbe/bsw@0: invalid = true jbe/bsw@0: } jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: ts = -- timestamp based on given data jbe/bsw@0: atom.timestamp:new{ jbe/bsw@0: tsec = tsec, -- seconds since January 1st 1970 00:00 jbe/bsw@0: year = year, -- year jbe/bsw@0: month = month, -- month from 1 to 12 jbe/bsw@0: day = day, -- day from 1 to 31 jbe/bsw@0: hour = hour, -- hour from 0 to 23 jbe/bsw@0: minute = minute, -- minute from 0 to 59 jbe/bsw@0: second = second -- second from 0 to 59 jbe/bsw@0: } jbe/bsw@0: jbe/bsw@0: This method returns a new timestamp value, based on given data. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function timestamp:new(args) jbe/bsw@0: local args = args jbe/bsw@0: if type(args) == "number" then args = { tsec = args } end jbe/bsw@0: if type(args) == "table" then jbe/bsw@0: if not args.second then jbe/bsw@0: args.second = 0 jbe/bsw@0: if not args.minute then jbe/bsw@0: args.minute = 0 jbe/bsw@0: if not args.hour then jbe/bsw@0: args.hour = 0 jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: if jbe/bsw@0: type(args.year) == "number" and jbe/bsw@0: type(args.month) == "number" and jbe/bsw@0: type(args.day) == "number" and jbe/bsw@0: type(args.hour) == "number" and jbe/bsw@0: type(args.minute) == "number" and jbe/bsw@0: type(args.second) == "number" jbe/bsw@0: then jbe/bsw@0: if jbe/bsw@0: is_integer(args.year) and jbe/bsw@0: args.year >= 1 and args.year <= 9999 and jbe/bsw@0: is_integer(args.month) and jbe/bsw@0: args.month >= 1 and args.month <= 12 and jbe/bsw@0: is_integer(args.day) and jbe/bsw@0: args.day >= 1 and args.day <= 31 and jbe/bsw@0: is_integer(args.hour) and jbe/bsw@0: args.hour >= 0 and args.hour <= 23 and jbe/bsw@0: is_integer(args.minute) and jbe/bsw@0: args.minute >= 0 and args.minute <= 59 and jbe/bsw@0: is_integer(args.second) and jbe/bsw@0: args.second >= 0 and args.second <= 59 jbe/bsw@0: then jbe/bsw@0: return timestamp:_create{ jbe/bsw@0: tsec = timestamp.ymdhms_to_tsec( jbe/bsw@0: args.year, args.month, args.day, jbe/bsw@0: args.hour, args.minute, args.second jbe/bsw@0: ), jbe/bsw@0: year = args.year, jbe/bsw@0: month = args.month, jbe/bsw@0: day = args.day, jbe/bsw@0: hour = args.hour, jbe/bsw@0: minute = args.minute, jbe/bsw@0: second = args.second jbe/bsw@0: } jbe/bsw@0: else jbe/bsw@0: return timestamp.invalid jbe/bsw@0: end jbe/bsw@0: elseif type(args.tsec) == "number" then jbe/bsw@0: if jbe/bsw@0: is_integer(args.tsec) and jbe/bsw@0: args.tsec >= -62135596800 and args.tsec <= 253402300799 jbe/bsw@0: then jbe/bsw@0: local year, month, day, hour, minute, second = jbe/bsw@0: timestamp.tsec_to_ymdhms(args.tsec) jbe/bsw@0: return timestamp:_create{ jbe/bsw@0: tsec = args.tsec, jbe/bsw@0: year = year, month = month, day = day, jbe/bsw@0: hour = hour, minute = minute, second = second jbe/bsw@0: } jbe/bsw@0: else jbe/bsw@0: return timestamp.invalid jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: error("Invalid arguments passed to timestamp constructor.") jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: ts = -- current date/time as timestamp jbe/bsw@0: atom.timestamp:get_current() jbe/bsw@0: jbe/bsw@0: This function returns the current date and time as timestamp. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function timestamp:get_current() jbe/bsw@0: local now = os.date("*t") jbe/bsw@0: return timestamp{ jbe/bsw@0: year = now.year, month = now.month, day = now.day, jbe/bsw@0: hour = now.hour, minute = now.min, second = now.sec jbe/bsw@0: } jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: ts = -- timestamp represented by the string jbe/bsw@0: atom.timestamp:load( jbe/bsw@0: string -- string representing a timestamp jbe/bsw@0: ) jbe/bsw@0: jbe/bsw@0: This method returns a timestamp represented by the given string. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function timestamp:load(str) jbe@1: if str == nil or str == "" then jbe/bsw@0: return nil jbe@1: elseif type(str) ~= "string" then jbe@1: error("String expected") jbe/bsw@0: else jbe/bsw@0: local year, month, day, hour, minute, second = string.match( jbe/bsw@0: str, jbe/bsw@0: "^([0-9][0-9][0-9][0-9])%-([0-9][0-9])%-([0-9][0-9]) ([0-9][0-9]):([0-9][0-9]):([0-9][0-9])$" jbe/bsw@0: ) jbe/bsw@0: if year then jbe/bsw@0: return timestamp{ jbe/bsw@0: year = tonumber(year), jbe/bsw@0: month = tonumber(month), jbe/bsw@0: day = tonumber(day), jbe/bsw@0: hour = tonumber(hour), jbe/bsw@0: minute = tonumber(minute), jbe/bsw@0: second = tonumber(second) jbe/bsw@0: } jbe/bsw@0: else jbe/bsw@0: return timestamp.invalid jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function timestamp:__tostring() jbe/bsw@0: if self.invalid then jbe/bsw@0: return "invalid_timestamp" jbe/bsw@0: else jbe/bsw@0: return string.format( jbe/bsw@0: "%04i-%02i-%02i %02i:%02i:%02i", jbe/bsw@0: self.year, self.month, self.day, self.hour, self.minute, self.second jbe/bsw@0: ) jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function timestamp.getters:date() jbe/bsw@0: return date{ year = self.year, month = self.month, day = self.day } jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function timestamp.getters:time() jbe/bsw@0: return time{ jbe/bsw@0: hour = self.hour, jbe/bsw@0: minute = self.minute, jbe/bsw@0: second = self.second jbe/bsw@0: } jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function timestamp.__eq(value1, value2) jbe/bsw@0: if value1.invalid or value2.invalid then jbe/bsw@0: return false jbe/bsw@0: else jbe/bsw@0: return value1.tsec == value2.tsec jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function timestamp.__lt(value1, value2) jbe/bsw@0: if value1.invalid or value2.invalid then jbe/bsw@0: return false jbe/bsw@0: else jbe/bsw@0: return value1.tsec < value2.tsec jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function timestamp.__le(value1, value2) jbe/bsw@0: if value1.invalid or value2.invalid then jbe/bsw@0: return false jbe/bsw@0: else jbe/bsw@0: return value1.tsec <= value2.tsec jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function timestamp.__add(value1, value2) jbe/bsw@0: if getmetatable(value1) == timestamp then jbe/bsw@0: if getmetatable(value2) == timestamp then jbe/bsw@0: error("Can not add two timestamps.") jbe/bsw@0: elseif type(value2) == "number" then jbe/bsw@0: return timestamp(value1.tsec + value2) jbe/bsw@0: else jbe/bsw@0: error("Right operand of '+' operator has wrong type.") jbe/bsw@0: end jbe/bsw@0: elseif type(value1) == "number" then jbe/bsw@0: if getmetatable(value2) == timestamp then jbe/bsw@0: return timestamp(value1 + value2.tsec) jbe/bsw@0: else jbe/bsw@0: error("Assertion failed") jbe/bsw@0: end jbe/bsw@0: else jbe/bsw@0: error("Left operand of '+' operator has wrong type.") jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function timestamp.__sub(value1, value2) jbe/bsw@0: if not getmetatable(value1) == timestamp then jbe/bsw@0: error("Left operand of '-' operator has wrong type.") jbe/bsw@0: end jbe/bsw@0: if getmetatable(value2) == timestamp then jbe@107: return value1.tsec - value2.tsec -- TODO: transform to interval jbe/bsw@0: elseif type(value2) == "number" then jbe/bsw@0: return timestamp(value1.tsec - value2) jbe/bsw@0: else jbe/bsw@0: error("Right operand of '-' operator has wrong type.") jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: jbe/bsw@0: jbe/bsw@0: ---------- jbe/bsw@0: -- time -- jbe/bsw@0: ---------- jbe/bsw@0: jbe/bsw@0: time = create_new_type("time") jbe/bsw@0: jbe/bsw@0: function time.hms_to_dsec(hour, minute, second) jbe/bsw@0: return 3600 * hour + 60 * minute + second jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function time.dsec_to_hms(dsec) jbe/bsw@0: local hour = math.floor(dsec / 3600) jbe/bsw@0: local minute = math.floor((dsec % 3600) / 60) jbe/bsw@0: local second = dsec % 60 jbe/bsw@0: return hour, minute, second jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: atom.time.invalid jbe/bsw@0: jbe/bsw@0: Value representing an invalid time of day. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: time.invalid = time:_create{ jbe/bsw@0: dsec = not_a_number, jbe/bsw@0: hour = not_a_number, minute = not_a_number, second = not_a_number, jbe/bsw@0: invalid = true jbe/bsw@0: } jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: t = -- time based on given data jbe/bsw@0: atom.time:new{ jbe/bsw@0: dsec = dsec, -- seconds since 00:00:00 jbe/bsw@0: hour = hour, -- hour from 0 to 23 jbe/bsw@0: minute = minute, -- minute from 0 to 59 jbe/bsw@0: second = second -- second from 0 to 59 jbe/bsw@0: } jbe/bsw@0: jbe/bsw@0: This method returns a new time value, based on given data. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function time:new(args) jbe/bsw@0: local args = args jbe/bsw@0: if type(args) == "number" then args = { dsec = args } end jbe/bsw@0: if type(args) == "table" then jbe/bsw@0: if not args.second then jbe/bsw@0: args.second = 0 jbe/bsw@0: if not args.minute then jbe/bsw@0: args.minute = 0 jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: if jbe/bsw@0: type(args.hour) == "number" and jbe/bsw@0: type(args.minute) == "number" and jbe/bsw@0: type(args.second) == "number" jbe/bsw@0: then jbe/bsw@0: if jbe/bsw@0: is_integer(args.hour) and jbe/bsw@0: args.hour >= 0 and args.hour <= 23 and jbe/bsw@0: is_integer(args.minute) and jbe/bsw@0: args.minute >= 0 and args.minute <= 59 and jbe/bsw@0: is_integer(args.second) and jbe/bsw@0: args.second >= 0 and args.second <= 59 jbe/bsw@0: then jbe/bsw@0: return time:_create{ jbe/bsw@0: dsec = time.hms_to_dsec(args.hour, args.minute, args.second), jbe/bsw@0: hour = args.hour, jbe/bsw@0: minute = args.minute, jbe/bsw@0: second = args.second jbe/bsw@0: } jbe/bsw@0: else jbe/bsw@0: return time.invalid jbe/bsw@0: end jbe/bsw@0: elseif type(args.dsec) == "number" then jbe/bsw@0: if jbe/bsw@0: is_integer(args.dsec) and jbe/bsw@0: args.dsec >= 0 and args.dsec <= 86399 jbe/bsw@0: then jbe/bsw@0: local hour, minute, second = jbe/bsw@0: time.dsec_to_hms(args.dsec) jbe/bsw@0: return time:_create{ jbe/bsw@0: dsec = args.dsec, jbe/bsw@0: hour = hour, minute = minute, second = second jbe/bsw@0: } jbe/bsw@0: else jbe/bsw@0: return time.invalid jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: error("Invalid arguments passed to time constructor.") jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: t = -- current time of day jbe/bsw@0: atom.time:get_current() jbe/bsw@0: jbe/bsw@0: This method returns the current time of the day. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function time:get_current() jbe/bsw@0: local now = os.date("*t") jbe/bsw@0: return time{ hour = now.hour, minute = now.min, second = now.sec } jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: --[[-- jbe/bsw@0: t = -- time represented by the string jbe/bsw@0: atom.time:load( jbe/bsw@0: string -- string representing a time of day jbe/bsw@0: ) jbe/bsw@0: jbe/bsw@0: This method returns a time represented by the given string. jbe/bsw@0: jbe/bsw@0: --]]-- jbe/bsw@0: function time:load(str) jbe@1: if str == nil or str == "" then jbe/bsw@0: return nil jbe@1: elseif type(str) ~= "string" then jbe@1: error("String expected") jbe/bsw@0: else jbe/bsw@0: local hour, minute, second = string.match( jbe/bsw@0: str, jbe/bsw@0: "^([0-9][0-9]):([0-9][0-9]):([0-9][0-9])$" jbe/bsw@0: ) jbe/bsw@0: if hour then jbe/bsw@0: return time{ jbe/bsw@0: hour = tonumber(hour), jbe/bsw@0: minute = tonumber(minute), jbe/bsw@0: second = tonumber(second) jbe/bsw@0: } jbe/bsw@0: else jbe/bsw@0: return time.invalid jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: --//-- jbe/bsw@0: jbe/bsw@0: function time:__tostring() jbe/bsw@0: if self.invalid then jbe/bsw@0: return "invalid_time" jbe/bsw@0: else jbe/bsw@0: return string.format( jbe/bsw@0: "%02i:%02i:%02i", jbe/bsw@0: self.hour, self.minute, self.second jbe/bsw@0: ) jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function time.__eq(value1, value2) jbe/bsw@0: if value1.invalid or value2.invalid then jbe/bsw@0: return false jbe/bsw@0: else jbe/bsw@0: return value1.dsec == value2.dsec jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function time.__lt(value1, value2) jbe/bsw@0: if value1.invalid or value2.invalid then jbe/bsw@0: return false jbe/bsw@0: else jbe/bsw@0: return value1.dsec < value2.dsec jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function time.__le(value1, value2) jbe/bsw@0: if value1.invalid or value2.invalid then jbe/bsw@0: return false jbe/bsw@0: else jbe/bsw@0: return value1.dsec <= value2.dsec jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function time.__add(value1, value2) jbe/bsw@0: if getmetatable(value1) == time then jbe/bsw@0: if getmetatable(value2) == time then jbe/bsw@0: error("Can not add two times.") jbe/bsw@0: elseif type(value2) == "number" then jbe/bsw@0: return time((value1.dsec + value2) % 86400) jbe/bsw@0: else jbe/bsw@0: error("Right operand of '+' operator has wrong type.") jbe/bsw@0: end jbe/bsw@0: elseif type(value1) == "number" then jbe/bsw@0: if getmetatable(value2) == time then jbe/bsw@0: return time((value1 + value2.dsec) % 86400) jbe/bsw@0: else jbe/bsw@0: error("Assertion failed") jbe/bsw@0: end jbe/bsw@0: else jbe/bsw@0: error("Left operand of '+' operator has wrong type.") jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function time.__sub(value1, value2) jbe/bsw@0: if not getmetatable(value1) == time then jbe/bsw@0: error("Left operand of '-' operator has wrong type.") jbe/bsw@0: end jbe/bsw@0: if getmetatable(value2) == time then jbe@107: return value1.dsec - value2.dsec -- TODO: transform to interval jbe/bsw@0: elseif type(value2) == "number" then jbe/bsw@0: return time((value1.dsec - value2) % 86400) jbe/bsw@0: else jbe/bsw@0: error("Right operand of '-' operator has wrong type.") jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe/bsw@0: function time.__concat(value1, value2) jbe/bsw@0: local mt1, mt2 = getmetatable(value1), getmetatable(value2) jbe/bsw@0: if mt1 == date and mt2 == time then jbe/bsw@0: return timestamp{ jbe/bsw@0: year = value1.year, month = value1.month, day = value1.day, jbe/bsw@0: hour = value2.hour, minute = value2.minute, second = value2.second jbe/bsw@0: } jbe/bsw@0: elseif mt1 == time and mt2 == date then jbe/bsw@0: return timestamp{ jbe/bsw@0: year = value2.year, month = value2.month, day = value2.day, jbe/bsw@0: hour = value1.hour, minute = value1.minute, second = value1.second jbe/bsw@0: } jbe/bsw@0: elseif mt1 == time then jbe/bsw@0: error("Right operand of '..' operator has wrong type.") jbe/bsw@0: elseif mt2 == time then jbe/bsw@0: error("Left operand of '..' operator has wrong type.") jbe/bsw@0: else jbe/bsw@0: error("Assertion failed") jbe/bsw@0: end jbe/bsw@0: end jbe/bsw@0: jbe@64: jbe@64: jbe@64: return _M