webmcp
diff libraries/atom/atom.lua @ 0:9fdfb27f8e67
Version 1.0.0
author | jbe/bsw |
---|---|
date | Sun Oct 25 12:00:00 2009 +0100 (2009-10-25) |
parents | |
children | 985024b16520 |
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/libraries/atom/atom.lua Sun Oct 25 12:00:00 2009 +0100 1.3 @@ -0,0 +1,1529 @@ 1.4 +#!/usr/bin/env lua 1.5 + 1.6 +local _G = _G 1.7 +local _VERSION = _VERSION 1.8 +local assert = assert 1.9 +local error = error 1.10 +local getfenv = getfenv 1.11 +local getmetatable = getmetatable 1.12 +local ipairs = ipairs 1.13 +local module = module 1.14 +local next = next 1.15 +local pairs = pairs 1.16 +local print = print 1.17 +local rawequal = rawequal 1.18 +local rawget = rawget 1.19 +local rawset = rawset 1.20 +local require = require 1.21 +local select = select 1.22 +local setfenv = setfenv 1.23 +local setmetatable = setmetatable 1.24 +local tonumber = tonumber 1.25 +local tostring = tostring 1.26 +local type = type 1.27 +local unpack = unpack 1.28 + 1.29 +local coroutine = coroutine 1.30 +local io = io 1.31 +local math = math 1.32 +local os = os 1.33 +local string = string 1.34 +local table = table 1.35 + 1.36 +module(...) 1.37 + 1.38 + 1.39 + 1.40 +--------------------------------------- 1.41 +-- general functions and definitions -- 1.42 +--------------------------------------- 1.43 + 1.44 +--[[-- 1.45 +bool = -- true, if value is an integer within resolution 1.46 +atom.is_integer( 1.47 + value -- value to be tested 1.48 +) 1.49 + 1.50 +This function returns true if the given object is an integer within resolution. 1.51 + 1.52 +--]]-- 1.53 +function is_integer(i) 1.54 + return 1.55 + type(i) == "number" and i % 1 == 0 and 1.56 + (i + 1) - i == 1 and i - (i - 1) == 1 1.57 +end 1.58 +--//-- 1.59 + 1.60 +--[[-- 1.61 +atom.not_a_number 1.62 + 1.63 +Value representing an invalid numeric result. Used for atom.integer.invalid and atom.number.invalid. 1.64 + 1.65 +--]]-- 1.66 +not_a_number = 0 / 0 1.67 +--//-- 1.68 + 1.69 +do 1.70 + 1.71 + local shadow = setmetatable({}, { __mode = "k" }) 1.72 + 1.73 + local type_mt = { __index = {} } 1.74 + 1.75 + function type_mt:__call(...) 1.76 + return self:new(...) 1.77 + end 1.78 + 1.79 + function type_mt.__index:_create(data) 1.80 + local value = setmetatable({}, self) 1.81 + shadow[value] = data 1.82 + return value 1.83 + end 1.84 + 1.85 + local function write_prohibited() 1.86 + error("Modification of an atom is prohibited.") 1.87 + end 1.88 + 1.89 + -- returns a new type as a table, which serves also as metatable 1.90 + function create_new_type(name) 1.91 + local t = setmetatable( 1.92 + { methods = {}, getters = {}, name = name }, 1.93 + type_mt 1.94 + ) 1.95 + function t.__index(self, key) 1.96 + local data = shadow[self] 1.97 + local value = data[key] 1.98 + if value ~= nil then return value end 1.99 + local method = t.methods[key] 1.100 + if method then return method end 1.101 + local getter = t.getters[key] 1.102 + if getter then return getter(self) end 1.103 + end 1.104 + t.__newindex = write_prohibited 1.105 + return t 1.106 + end 1.107 + 1.108 + --[[-- 1.109 + bool = -- true, if 'value' is of type 't' 1.110 + atom.has_type( 1.111 + value, -- any value 1.112 + t -- atom time, e.g. atom.date, or lua type, e.g. "string" 1.113 + ) 1.114 + 1.115 + This function checks, if a value is of a given type. The value may be an invalid value though, e.g. atom.date.invalid. 1.116 + 1.117 + --]]-- 1.118 + function has_type(value, t) 1.119 + if t == nil then error("No type passed to has_type(...) function.") end 1.120 + local lua_type = type(value) 1.121 + return 1.122 + lua_type == t or 1.123 + getmetatable(value) == t or 1.124 + (lua_type == "boolean" and t == _M.boolean) or 1.125 + (lua_type == "string" and t == _M.string) or ( 1.126 + lua_type == "number" and 1.127 + (t == _M.number or ( 1.128 + t == _M.integer and ( 1.129 + not (value <= 0 or value >= 0) or ( 1.130 + value % 1 == 0 and 1.131 + (value + 1) - value == 1 and 1.132 + value - (value - 1) == 1 1.133 + ) 1.134 + ) 1.135 + )) 1.136 + ) 1.137 + end 1.138 + --//-- 1.139 + 1.140 + --[[-- 1.141 + bool = -- true, if 'value' is of type 't' 1.142 + atom.is_valid( 1.143 + value, -- any value 1.144 + t -- atom time, e.g. atom.date, or lua type, e.g. "string" 1.145 + ) 1.146 + 1.147 + This function checks, if a value is valid. It optionally checks, if the value is of a given type. 1.148 + 1.149 + --]]-- 1.150 + function is_valid(value, t) 1.151 + local lua_type = type(value) 1.152 + if lua_type == "table" then 1.153 + local mt = getmetatable(value) 1.154 + if t then 1.155 + return t == mt and not value.invalid 1.156 + else 1.157 + return (getmetatable(mt) == type_mt) and not value.invalid 1.158 + end 1.159 + elseif lua_type == "boolean" then 1.160 + return not t or t == "boolean" or t == _M.boolean 1.161 + elseif lua_type == "string" then 1.162 + return not t or t == "string" or t == _M.string 1.163 + elseif lua_type == "number" then 1.164 + if t == _M.integer then 1.165 + return 1.166 + value % 1 == 0 and 1.167 + (value + 1) - value == 1 and 1.168 + value - (value - 1) == 1 1.169 + else 1.170 + return 1.171 + (not t or t == "number" or t == _M.number) and 1.172 + (value <= 0 or value >= 0) 1.173 + end 1.174 + else 1.175 + return false 1.176 + end 1.177 + end 1.178 + --//-- 1.179 + 1.180 +end 1.181 + 1.182 +--[[-- 1.183 +string = -- string representation to be passed to a load function 1.184 +atom.dump( 1.185 + value -- value to be dumped 1.186 +) 1.187 + 1.188 +This function returns a string representation of the given value. 1.189 + 1.190 +--]]-- 1.191 +function dump(obj) 1.192 + if obj == nil then 1.193 + return "" 1.194 + else 1.195 + return tostring(obj) 1.196 + end 1.197 +end 1.198 +--//-- 1.199 + 1.200 + 1.201 + 1.202 +------------- 1.203 +-- boolean -- 1.204 +------------- 1.205 + 1.206 +boolean = { name = "boolean" } 1.207 + 1.208 +--[[-- 1.209 +bool = -- true, false, or nil 1.210 +atom.boolean:load( 1.211 + string -- string to be interpreted as boolean 1.212 +) 1.213 + 1.214 +This method returns true or false or nil, depending on the input string. 1.215 + 1.216 +--]]-- 1.217 +function boolean:load(str) 1.218 + if type(str) ~= "string" then 1.219 + error("String expected") 1.220 + elseif str == "" then 1.221 + return nil 1.222 + elseif string.find(str, "^[TtYy1]") then 1.223 + return true 1.224 + elseif string.find(str, "^[FfNn0]") then 1.225 + return false 1.226 + else 1.227 + return nil -- we don't have an undefined bool 1.228 + end 1.229 +end 1.230 +--//-- 1.231 + 1.232 + 1.233 + 1.234 +------------ 1.235 +-- string -- 1.236 +------------ 1.237 + 1.238 +_M.string = { name = "string" } 1.239 + 1.240 +--[[-- 1.241 +string = -- the same string 1.242 +atom.string:load( 1.243 + string -- a string 1.244 +) 1.245 + 1.246 +This method returns the passed string, or throws an error, if the passed argument is not a string. 1.247 + 1.248 +--]]-- 1.249 +function _M.string:load(str) 1.250 + if type(str) ~= "string" then 1.251 + error("String expected") 1.252 + else 1.253 + return str 1.254 + end 1.255 +end 1.256 +--//-- 1.257 + 1.258 + 1.259 + 1.260 +------------- 1.261 +-- integer -- 1.262 +------------- 1.263 + 1.264 +integer = { name = "integer" } 1.265 + 1.266 +--[[-- 1.267 +int = -- an integer or atom.integer.invalid (atom.not_a_number) 1.268 +atom.integer:load( 1.269 + string -- a string representing an integer 1.270 +) 1.271 + 1.272 +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. 1.273 + 1.274 +--]]-- 1.275 +function integer:load(str) 1.276 + if type(str) ~= "string" then 1.277 + error("String expected") 1.278 + elseif str == "" then 1.279 + return nil 1.280 + else 1.281 + local num = tonumber(str) 1.282 + if is_integer(num) then return num else return not_a_number end 1.283 + end 1.284 +end 1.285 +--//-- 1.286 + 1.287 +--[[-- 1.288 +atom.integer.invalid 1.289 + 1.290 +This represents an invalid integer. 1.291 + 1.292 +--]]-- 1.293 +integer.invalid = not_a_number 1.294 +--// 1.295 + 1.296 + 1.297 + 1.298 +------------ 1.299 +-- number -- 1.300 +------------ 1.301 + 1.302 +number = create_new_type("number") 1.303 + 1.304 +--[[-- 1.305 +int = -- a number or atom.number.invalid (atom.not_a_number) 1.306 +atom.number:load( 1.307 + string -- a string representing a number 1.308 +) 1.309 + 1.310 +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. 1.311 + 1.312 +--]]-- 1.313 +function number:load(str) 1.314 + if type(str) ~= "string" then 1.315 + error("String expected") 1.316 + elseif str == "" then 1.317 + return nil 1.318 + else 1.319 + return tonumber(str) or not_a_number 1.320 + end 1.321 +end 1.322 +--//-- 1.323 + 1.324 +--[[-- 1.325 +atom.number.invalid 1.326 + 1.327 +This represents an invalid number. 1.328 + 1.329 +--]]-- 1.330 +number.invalid = not_a_number 1.331 +--//-- 1.332 + 1.333 + 1.334 + 1.335 +-------------- 1.336 +-- fraction -- 1.337 +-------------- 1.338 + 1.339 +fraction = create_new_type("fraction") 1.340 + 1.341 +--[[-- 1.342 +i = -- the greatest common divisor (GCD) of all given natural numbers 1.343 +atom.gcd( 1.344 + a, -- a natural number 1.345 + b, -- another natural number 1.346 + ... -- optionally more natural numbers 1.347 +) 1.348 + 1.349 +This function returns the greatest common divisor (GCD) of two or more natural numbers. 1.350 + 1.351 +--]]-- 1.352 +function gcd(a, b, ...) 1.353 + if a % 1 ~= 0 or a <= 0 then return 0 / 0 end 1.354 + if b == nil then 1.355 + return a 1.356 + else 1.357 + if b % 1 ~= 0 or b <= 0 then return 0 / 0 end 1.358 + if ... == nil then 1.359 + local k = 0 1.360 + local t 1.361 + while a % 2 == 0 and b % 2 == 0 do 1.362 + a = a / 2; b = b / 2; k = k + 1 1.363 + end 1.364 + if a % 2 == 0 then t = a else t = -b end 1.365 + while t ~= 0 do 1.366 + while t % 2 == 0 do t = t / 2 end 1.367 + if t > 0 then a = t else b = -t end 1.368 + t = a - b 1.369 + end 1.370 + return a * 2 ^ k 1.371 + else 1.372 + return gcd(gcd(a, b), ...) 1.373 + end 1.374 + end 1.375 +end 1.376 +--//-- 1.377 + 1.378 +--[[-- 1.379 +i = --the least common multiple (LCD) of all given natural numbers 1.380 +atom.lcm( 1.381 + a, -- a natural number 1.382 + b, -- another natural number 1.383 + ... -- optionally more natural numbers 1.384 +) 1.385 + 1.386 +This function returns the least common multiple (LCD) of two or more natural numbers. 1.387 + 1.388 +--]]-- 1.389 +function lcm(a, b, ...) 1.390 + if a % 1 ~= 0 or a <= 0 then return 0 / 0 end 1.391 + if b == nil then 1.392 + return a 1.393 + else 1.394 + if b % 1 ~= 0 or b <= 0 then return 0 / 0 end 1.395 + if ... == nil then 1.396 + return a * b / gcd(a, b) 1.397 + else 1.398 + return lcm(lcm(a, b), ...) 1.399 + end 1.400 + end 1.401 +end 1.402 +--//-- 1.403 + 1.404 +--[[-- 1.405 +atom.fraction.invalid 1.406 + 1.407 +Value representing an invalid fraction. 1.408 + 1.409 +--]]-- 1.410 +fraction.invalid = fraction:_create{ 1.411 + numerator = not_a_number, denominator = not_a_number, invalid = true 1.412 +} 1.413 +--//-- 1.414 + 1.415 +--[[-- 1.416 +frac = -- fraction 1.417 +atom.fraction:new( 1.418 + numerator, -- numerator 1.419 + denominator -- denominator 1.420 +) 1.421 + 1.422 +This method creates a new fraction. 1.423 + 1.424 +--]]-- 1.425 +function fraction:new(numerator, denominator) 1.426 + if not ( 1.427 + (numerator == nil or type(numerator) == "number") and 1.428 + (denominator == nil or type(denominator) == "number") 1.429 + ) then 1.430 + error("Invalid arguments passed to fraction constructor.") 1.431 + elseif 1.432 + (not is_integer(numerator)) or 1.433 + (denominator and (not is_integer(denominator))) 1.434 + then 1.435 + return fraction.invalid 1.436 + elseif denominator then 1.437 + if denominator == 0 then 1.438 + return fraction.invalid 1.439 + elseif numerator == 0 then 1.440 + return fraction:_create{ numerator = 0, denominator = 1, float = 0 } 1.441 + else 1.442 + local d = gcd(math.abs(numerator), math.abs(denominator)) 1.443 + if denominator < 0 then d = -d end 1.444 + local numerator2, denominator2 = numerator / d, denominator / d 1.445 + return fraction:_create{ 1.446 + numerator = numerator2, 1.447 + denominator = denominator2, 1.448 + float = numerator2 / denominator2 1.449 + } 1.450 + end 1.451 + else 1.452 + return fraction:_create{ 1.453 + numerator = numerator, denominator = 1, float = numerator 1.454 + } 1.455 + end 1.456 +end 1.457 +--//-- 1.458 + 1.459 +--[[-- 1.460 +frac = -- fraction represented by the given string 1.461 +atom.fraction:load( 1.462 + string -- string representation of a fraction 1.463 +) 1.464 + 1.465 +This method returns a fraction represented by the given string. 1.466 + 1.467 +--]]-- 1.468 +function fraction:load(str) 1.469 + if str == "" then 1.470 + return nil 1.471 + else 1.472 + local sign, int = string.match(str, "^(%-?)([0-9]+)$") 1.473 + if sign == "" then return fraction:new(tonumber(int)) 1.474 + elseif sign == "-" then return fraction:new(- tonumber(int)) 1.475 + end 1.476 + local sign, n, d = string.match(str, "^(%-?)([0-9]+)/([0-9]+)$") 1.477 + if sign == "" then return fraction:new(tonumber(n), tonumber(d)) 1.478 + elseif sign == "-" then return fraction:new(- tonumber(n), tonumber(d)) 1.479 + end 1.480 + return fraction.invalid 1.481 + end 1.482 +end 1.483 +--//-- 1.484 + 1.485 +function fraction:__tostring() 1.486 + if self.invalid then 1.487 + return "not_a_fraction" 1.488 + else 1.489 + return self.numerator .. "/" .. self.denominator 1.490 + end 1.491 +end 1.492 + 1.493 +function fraction.__eq(value1, value2) 1.494 + if value1.invalid or value2.invalid then 1.495 + return false 1.496 + else 1.497 + return 1.498 + value1.numerator == value2.numerator and 1.499 + value1.denominator == value2.denominator 1.500 + end 1.501 +end 1.502 + 1.503 +function fraction.__lt(value1, value2) 1.504 + if value1.invalid or value2.invalid then 1.505 + return false 1.506 + else 1.507 + return value1.float < value2.float 1.508 + end 1.509 +end 1.510 + 1.511 +function fraction.__le(value1, value2) 1.512 + if value1.invalid or value2.invalid then 1.513 + return false 1.514 + else 1.515 + return value1.float <= value2.float 1.516 + end 1.517 +end 1.518 + 1.519 +function fraction:__unm() 1.520 + return fraction(-self.numerator, self.denominator) 1.521 +end 1.522 + 1.523 +do 1.524 + 1.525 + local function extract(value1, value2) 1.526 + local n1, d1, n2, d2 1.527 + if getmetatable(value1) == fraction then 1.528 + n1 = value1.numerator 1.529 + d1 = value1.denominator 1.530 + elseif type(value1) == "number" then 1.531 + n1 = value1 1.532 + d1 = 1 1.533 + else 1.534 + error("Left operand of operator has wrong type.") 1.535 + end 1.536 + if getmetatable(value2) == fraction then 1.537 + n2 = value2.numerator 1.538 + d2 = value2.denominator 1.539 + elseif type(value2) == "number" then 1.540 + n2 = value2 1.541 + d2 = 1 1.542 + else 1.543 + error("Right operand of operator has wrong type.") 1.544 + end 1.545 + return n1, d1, n2, d2 1.546 + end 1.547 + 1.548 + function fraction.__add(value1, value2) 1.549 + local n1, d1, n2, d2 = extract(value1, value2) 1.550 + return fraction(n1 * d2 + n2 * d1, d1 * d2) 1.551 + end 1.552 + 1.553 + function fraction.__sub(value1, value2) 1.554 + local n1, d1, n2, d2 = extract(value1, value2) 1.555 + return fraction(n1 * d2 - n2 * d1, d1 * d2) 1.556 + end 1.557 + 1.558 + function fraction.__mul(value1, value2) 1.559 + local n1, d1, n2, d2 = extract(value1, value2) 1.560 + return fraction(n1 * n2, d1 * d2) 1.561 + end 1.562 + 1.563 + function fraction.__div(value1, value2) 1.564 + local n1, d1, n2, d2 = extract(value1, value2) 1.565 + return fraction(n1 * d2, d1 * n2) 1.566 + end 1.567 + 1.568 + function fraction.__pow(value1, value2) 1.569 + local n1, d1, n2, d2 = extract(value1, value2) 1.570 + local n1_abs = math.abs(n1) 1.571 + local d1_abs = math.abs(d1) 1.572 + local n2_abs = math.abs(n2) 1.573 + local d2_abs = math.abs(d2) 1.574 + local numerator, denominator 1.575 + if d2_abs == 1 then 1.576 + numerator = n1_abs 1.577 + denominator = d1_abs 1.578 + else 1.579 + numerator = 0 1.580 + while true do 1.581 + local t = numerator ^ d2_abs 1.582 + if t == n1_abs then break end 1.583 + if not (t < n1_abs) then return value1.float / value2.float end 1.584 + numerator = numerator + 1 1.585 + end 1.586 + denominator = 1 1.587 + while true do 1.588 + local t = denominator ^ d2_abs 1.589 + if t == d1_abs then break end 1.590 + if not (t < d1_abs) then return value1.float / value2.float end 1.591 + denominator = denominator + 1 1.592 + end 1.593 + end 1.594 + if n1 < 0 then 1.595 + if d2_abs % 2 == 1 then 1.596 + numerator = -numerator 1.597 + else 1.598 + return fraction.invalid 1.599 + end 1.600 + end 1.601 + if n2 < 0 then 1.602 + numerator, denominator = denominator, numerator 1.603 + end 1.604 + return fraction(numerator ^ n2_abs, denominator ^ n2_abs) 1.605 + end 1.606 + 1.607 +end 1.608 + 1.609 + 1.610 + 1.611 +---------- 1.612 +-- date -- 1.613 +---------- 1.614 + 1.615 +date = create_new_type("date") 1.616 + 1.617 +do 1.618 + local c1 = 365 -- days of a non-leap year 1.619 + local c4 = 4 * c1 + 1 -- days of a full 4 year cycle 1.620 + local c100 = 25 * c4 - 1 -- days of a full 100 year cycle 1.621 + local c400 = 4 * c100 + 1 -- days of a full 400 year cycle 1.622 + local get_month_offset -- function returning days elapsed within 1.623 + -- the given year until the given month 1.624 + -- (exclusive the given month) 1.625 + do 1.626 + local normal_month_offsets = {} 1.627 + local normal_month_lengths = { 1.628 + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 1.629 + } 1.630 + local sum = 0 1.631 + for i = 1, 12 do 1.632 + normal_month_offsets[i] = sum 1.633 + sum = sum + normal_month_lengths[i] 1.634 + end 1.635 + function get_month_offset(year, month) 1.636 + if 1.637 + (((year % 4 == 0) and not (year % 100 == 0)) or (year % 400 == 0)) 1.638 + and month > 2 1.639 + then 1.640 + return normal_month_offsets[month] + 1 1.641 + else 1.642 + return normal_month_offsets[month] 1.643 + end 1.644 + end 1.645 + end 1.646 + 1.647 + --[[-- 1.648 + jd = -- days from January 1st 1970 1.649 + atom.date.ymd_to_jd( 1.650 + year, -- year 1.651 + month, -- month from 1 to 12 1.652 + day -- day from 1 to 31 1.653 + ) 1.654 + 1.655 + This function calculates the days from January 1st 1970 for a given year, month and day. 1.656 + 1.657 + --]]-- 1.658 + local offset = 0 1.659 + function date.ymd_to_jd(year, month, day) 1.660 + assert(is_integer(year), "Invalid year specified.") 1.661 + assert(is_integer(month), "Invalid month specified.") 1.662 + assert(is_integer(day), "Invalid day specified.") 1.663 + local calc_year = year - 1 1.664 + local n400 = math.floor(calc_year / 400) 1.665 + local r400 = calc_year % 400 1.666 + local n100 = math.floor(r400 / 100) 1.667 + local r100 = r400 % 100 1.668 + local n4 = math.floor(r100 / 4) 1.669 + local n1 = r100 % 4 1.670 + local jd = ( 1.671 + c400 * n400 + c100 * n100 + c4 * n4 + c1 * n1 + 1.672 + get_month_offset(year, month) + (day - 1) 1.673 + ) 1.674 + return jd - offset 1.675 + end 1.676 + offset = date.ymd_to_jd(1970, 1, 1) 1.677 + --//-- 1.678 + 1.679 + --[[-- 1.680 + year, -- year 1.681 + month, -- month from 1 to 12 1.682 + day = -- day from 1 to 31 1.683 + atom.date.jd_to_ymd( 1.684 + jd, -- days from January 1st 1970 1.685 + ) 1.686 + 1.687 + Given the days from January 1st 1970 this function returns year, month and day. 1.688 + 1.689 + --]]-- 1.690 + function date.jd_to_ymd(jd) 1.691 + assert(is_integer(jd), "Invalid julian date specified.") 1.692 + local calc_jd = jd + offset 1.693 + assert(is_integer(calc_jd), "Julian date is out of range.") 1.694 + local n400 = math.floor(calc_jd / c400) 1.695 + local r400 = calc_jd % c400 1.696 + local n100 = math.floor(r400 / c100) 1.697 + local r100 = r400 % c100 1.698 + if n100 == 4 then n100, r100 = 3, c100 end 1.699 + local n4 = math.floor(r100 / c4) 1.700 + local r4 = r100 % c4 1.701 + local n1 = math.floor(r4 / c1) 1.702 + local r1 = r4 % c1 1.703 + if n1 == 4 then n1, r1 = 3, c1 end 1.704 + local year = 1 + 400 * n400 + 100 * n100 + 4 * n4 + n1 1.705 + local month = 1 + math.floor(r1 / 31) 1.706 + local month_offset = get_month_offset(year, month) 1.707 + if month < 12 then 1.708 + local next_month_offset = get_month_offset(year, month + 1) 1.709 + if r1 >= next_month_offset then 1.710 + month = month + 1 1.711 + month_offset = next_month_offset 1.712 + end 1.713 + end 1.714 + local day = 1 + r1 - month_offset 1.715 + return year, month, day 1.716 + end 1.717 + --//-- 1.718 +end 1.719 + 1.720 +--[[-- 1.721 +atom.date.invalid 1.722 + 1.723 +Value representing an invalid date. 1.724 + 1.725 +--]]-- 1.726 +date.invalid = date:_create{ 1.727 + jd = not_a_number, 1.728 + year = not_a_number, month = not_a_number, day = not_a_number, 1.729 + invalid = true 1.730 +} 1.731 +--//-- 1.732 + 1.733 +--[[-- 1.734 +d = -- date based on the given data 1.735 +atom.date:new{ 1.736 + jd = jd, -- days since January 1st 1970 1.737 + year = year, -- year 1.738 + month = month, -- month from 1 to 12 1.739 + day = day, -- day from 1 to 31 1.740 + iso_weekyear = iso_weekyear, -- year according to ISO 8601 1.741 + iso_week = iso_week, -- week number according to ISO 8601 1.742 + iso_weekday = iso_weekday, -- day of week from 1 for monday to 7 for sunday 1.743 + us_weekyear = us_weekyear, -- year 1.744 + us_week = us_week, -- week number according to US style counting 1.745 + us_weekday = us_weekday -- day of week from 1 for sunday to 7 for saturday 1.746 +} 1.747 + 1.748 +This method returns a new date value, based on given data. 1.749 + 1.750 +--]]-- 1.751 +function date:new(args) 1.752 + local args = args 1.753 + if type(args) == "number" then args = { jd = args } end 1.754 + if type(args) == "table" then 1.755 + local year, month, day = args.year, args.month, args.day 1.756 + local jd = args.jd 1.757 + local iso_weekyear = args.iso_weekyear 1.758 + local iso_week = args.iso_week 1.759 + local iso_weekday = args.iso_weekday 1.760 + local us_week = args.us_week 1.761 + local us_weekday = args.us_weekday 1.762 + if 1.763 + type(year) == "number" and 1.764 + type(month) == "number" and 1.765 + type(day) == "number" 1.766 + then 1.767 + if 1.768 + is_integer(year) and year >= 1 and year <= 9999 and 1.769 + is_integer(month) and month >= 1 and month <= 12 and 1.770 + is_integer(day) and day >= 1 and day <= 31 1.771 + then 1.772 + return date:_create{ 1.773 + jd = date.ymd_to_jd(year, month, day), 1.774 + year = year, month = month, day = day 1.775 + } 1.776 + else 1.777 + return date.invalid 1.778 + end 1.779 + elseif type(jd) == "number" then 1.780 + if is_integer(jd) and jd >= -719162 and jd <= 2932896 then 1.781 + local year, month, day = date.jd_to_ymd(jd) 1.782 + return date:_create{ 1.783 + jd = jd, year = year, month = month, day = day 1.784 + } 1.785 + else 1.786 + return date.invalid 1.787 + end 1.788 + elseif 1.789 + type(year) == "number" and not iso_weekyear and 1.790 + type(iso_week) == "number" and 1.791 + type(iso_weekday) == "number" 1.792 + then 1.793 + if 1.794 + is_integer(year) and 1.795 + is_integer(iso_week) and iso_week >= 0 and iso_week <= 53 and 1.796 + is_integer(iso_weekday) and iso_weekday >= 1 and iso_weekday <= 7 1.797 + then 1.798 + local jan4 = date{ year = year, month = 1, day = 4 } 1.799 + local reference = jan4 - jan4.iso_weekday - 7 -- Sun. of week -1 1.800 + return date(reference + 7 * iso_week + iso_weekday) 1.801 + else 1.802 + return date.invalid 1.803 + end 1.804 + elseif 1.805 + type(iso_weekyear) == "number" and not year and 1.806 + type(iso_week) == "number" and 1.807 + type(iso_weekday) == "number" 1.808 + then 1.809 + if 1.810 + is_integer(iso_weekyear) and 1.811 + is_integer(iso_week) and iso_week >= 0 and iso_week <= 53 and 1.812 + is_integer(iso_weekday) and iso_weekday >= 1 and iso_weekday <= 7 1.813 + then 1.814 + local guessed = date{ 1.815 + year = iso_weekyear, 1.816 + iso_week = iso_week, 1.817 + iso_weekday = iso_weekday 1.818 + } 1.819 + if guessed.invalid or guessed.iso_weekyear == iso_weekyear then 1.820 + return guessed 1.821 + else 1.822 + local year 1.823 + if iso_week <= 1 then 1.824 + year = iso_weekyear - 1 1.825 + elseif iso_week >= 52 then 1.826 + year = iso_weekyear + 1 1.827 + else 1.828 + error("Internal error in ISO week computation occured.") 1.829 + end 1.830 + return date{ 1.831 + year = year, iso_week = iso_week, iso_weekday = iso_weekday 1.832 + } 1.833 + end 1.834 + else 1.835 + return date.invalid 1.836 + end 1.837 + elseif 1.838 + type(year) == "number" and 1.839 + type(us_week) == "number" and 1.840 + type(us_weekday) == "number" 1.841 + then 1.842 + if 1.843 + is_integer(year) and 1.844 + is_integer(us_week) and us_week >= 0 and us_week <= 54 and 1.845 + is_integer(us_weekday) and us_weekday >= 1 and us_weekday <= 7 1.846 + then 1.847 + local jan1 = date{ year = year, month = 1, day = 1 } 1.848 + local reference = jan1 - jan1.us_weekday - 7 -- Sat. of week -1 1.849 + return date(reference + 7 * us_week + us_weekday) 1.850 + else 1.851 + return date.invalid 1.852 + end 1.853 + end 1.854 + end 1.855 + error("Illegal arguments passed to date constructor.") 1.856 +end 1.857 +--//-- 1.858 + 1.859 +--[[-- 1.860 +atom.date:get_current() 1.861 + 1.862 +This function returns today's date. 1.863 + 1.864 +--]]-- 1.865 +function date:get_current() 1.866 + local now = os.date("*t") 1.867 + return date{ 1.868 + year = now.year, month = now.month, day = now.day 1.869 + } 1.870 +end 1.871 +--//-- 1.872 + 1.873 +--[[-- 1.874 +date = -- date represented by the string 1.875 +atom.date:load( 1.876 + string -- string representing a date 1.877 +) 1.878 + 1.879 +This method returns a date represented by the given string. 1.880 + 1.881 +--]]-- 1.882 +function date:load(str) 1.883 + if str == "" then 1.884 + return nil 1.885 + else 1.886 + local year, month, day = string.match( 1.887 + str, "^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$" 1.888 + ) 1.889 + if year then 1.890 + return date{ 1.891 + year = tonumber(year), 1.892 + month = tonumber(month), 1.893 + day = tonumber(day) 1.894 + } 1.895 + else 1.896 + return date.invalid 1.897 + end 1.898 + end 1.899 +end 1.900 +--//-- 1.901 + 1.902 +function date:__tostring() 1.903 + if self.invalid then 1.904 + return "invalid_date" 1.905 + else 1.906 + return string.format( 1.907 + "%04i-%02i-%02i", self.year, self.month, self.day 1.908 + ) 1.909 + end 1.910 +end 1.911 + 1.912 +function date.getters:midnight() 1.913 + return time{ year = self.year, month = self.month, day = self.day } 1.914 +end 1.915 + 1.916 +function date.getters:midday() 1.917 + return time{ 1.918 + year = self.year, month = self.month, day = self.day, 1.919 + hour = 12 1.920 + } 1.921 +end 1.922 + 1.923 +function date.getters:iso_weekday() -- 1 = Monday 1.924 + return (self.jd + 3) % 7 + 1 1.925 +end 1.926 + 1.927 +function date.getters:us_weekday() -- 1 = Sunday 1.928 + return (self.jd + 4) % 7 + 1 1.929 +end 1.930 + 1.931 +function date.getters:iso_weekyear() -- ISO week-numbering year 1.932 + local year, month, day = self.year, self.month, self.day 1.933 + local iso_weekday = self.iso_weekday 1.934 + if month == 1 then 1.935 + if 1.936 + (day == 3 and iso_weekday == 7) or 1.937 + (day == 2 and iso_weekday >= 6) or 1.938 + (day == 1 and iso_weekday >= 5) 1.939 + then 1.940 + return year - 1 1.941 + end 1.942 + elseif month == 12 then 1.943 + if 1.944 + (day == 29 and iso_weekday == 1) or 1.945 + (day == 30 and iso_weekday <= 2) or 1.946 + (day == 31 and iso_weekday <= 3) 1.947 + then 1.948 + return year + 1 1.949 + end 1.950 + end 1.951 + return year 1.952 +end 1.953 + 1.954 +function date.getters:iso_week() 1.955 + local jan4 = date{ year = self.iso_weekyear, month = 1, day = 4 } 1.956 + local reference = jan4.jd - jan4.iso_weekday - 6 -- monday of week 0 1.957 + return math.floor((self.jd - reference) / 7) 1.958 +end 1.959 + 1.960 +function date.getters:us_week() 1.961 + local jan1 = date{ year = self.year, month = 1, day = 1 } 1.962 + local reference = jan1.jd - jan1.us_weekday - 6 -- sunday of week 0 1.963 + return math.floor((self.jd - reference) / 7) 1.964 +end 1.965 + 1.966 +function date.__eq(value1, value2) 1.967 + if value1.invalid or value2.invalid then 1.968 + return false 1.969 + else 1.970 + return value1.jd == value2.jd 1.971 + end 1.972 +end 1.973 + 1.974 +function date.__lt(value1, value2) 1.975 + if value1.invalid or value2.invalid then 1.976 + return false 1.977 + else 1.978 + return value1.jd < value2.jd 1.979 + end 1.980 +end 1.981 + 1.982 +function date.__le(value1, value2) 1.983 + if value1.invalid or value2.invalid then 1.984 + return false 1.985 + else 1.986 + return value1.jd <= value2.jd 1.987 + end 1.988 +end 1.989 + 1.990 +function date.__add(value1, value2) 1.991 + if getmetatable(value1) == date then 1.992 + if getmetatable(value2) == date then 1.993 + error("Can not add two dates.") 1.994 + elseif type(value2) == "number" then 1.995 + return date(value1.jd + value2) 1.996 + else 1.997 + error("Right operand of '+' operator has wrong type.") 1.998 + end 1.999 + elseif type(value1) == "number" then 1.1000 + if getmetatable(value2) == date then 1.1001 + return date(value1 + value2.jd) 1.1002 + else 1.1003 + error("Assertion failed") 1.1004 + end 1.1005 + else 1.1006 + error("Left operand of '+' operator has wrong type.") 1.1007 + end 1.1008 +end 1.1009 + 1.1010 +function date.__sub(value1, value2) 1.1011 + if not getmetatable(value1) == date then 1.1012 + error("Left operand of '-' operator has wrong type.") 1.1013 + end 1.1014 + if getmetatable(value2) == date then 1.1015 + return value1.jd - value2.jd -- TODO: transform to interval 1.1016 + elseif type(value2) == "number" then 1.1017 + return date(value1.jd - value2) 1.1018 + else 1.1019 + error("Right operand of '-' operator has wrong type.") 1.1020 + end 1.1021 +end 1.1022 + 1.1023 + 1.1024 + 1.1025 +--------------- 1.1026 +-- timestamp -- 1.1027 +--------------- 1.1028 + 1.1029 +timestamp = create_new_type("timestamp") 1.1030 + 1.1031 +--[[-- 1.1032 +tsec = -- seconds since January 1st 1970 00:00 1.1033 +atom.timestamp.ymdhms_to_tsec( 1.1034 + year, -- year 1.1035 + month, -- month from 1 to 12 1.1036 + day, -- day from 1 to 31 1.1037 + hour, -- hour from 0 to 23 1.1038 + minute, -- minute from 0 to 59 1.1039 + second -- second from 0 to 59 1.1040 +) 1.1041 + 1.1042 +Given the year, month, day, hour, minute and second, this function returns the number of seconds since January 1st 1970 00:00. 1.1043 + 1.1044 +--]]-- 1.1045 +function timestamp.ymdhms_to_tsec(year, month, day, hour, minute, second) 1.1046 + return 1.1047 + 86400 * date.ymd_to_jd(year, month, day) + 1.1048 + 3600 * hour + 60 * minute + second 1.1049 +end 1.1050 +--//-- 1.1051 + 1.1052 +--[[-- 1.1053 +year, -- year 1.1054 +month, -- month from 1 to 12 1.1055 +day, -- day from 1 to 31 1.1056 +hour, -- hour from 0 to 23 1.1057 +minute, -- minute from 0 to 59 1.1058 +second = -- second from 0 to 59 1.1059 +atom.timestamp.tsec_to_ymdhms( 1.1060 + tsec -- seconds since January 1st 1970 00:00 1.1061 +) 1.1062 + 1.1063 +Given the seconds since January 1st 1970 00:00, this function returns the year, month, day, hour, minute and second. 1.1064 + 1.1065 +--]]-- 1.1066 +function timestamp.tsec_to_ymdhms(tsec) 1.1067 + local jd = math.floor(tsec / 86400) 1.1068 + local dsec = tsec % 86400 1.1069 + local year, month, day = date.jd_to_ymd(jd) 1.1070 + local hour = math.floor(dsec / 3600) 1.1071 + local minute = math.floor((dsec % 3600) / 60) 1.1072 + local second = dsec % 60 1.1073 + return year, month, day, hour, minute, second 1.1074 +end 1.1075 +--//-- 1.1076 + 1.1077 +--[[-- 1.1078 +timestamp.invalid 1.1079 + 1.1080 +Value representing an invalid timestamp. 1.1081 + 1.1082 +--]]-- 1.1083 +timestamp.invalid = timestamp:_create{ 1.1084 + tsec = not_a_number, 1.1085 + year = not_a_number, month = not_a_number, day = not_a_number, 1.1086 + hour = not_a_number, minute = not_a_number, second = not_a_number, 1.1087 + invalid = true 1.1088 +} 1.1089 +--//-- 1.1090 + 1.1091 +--[[-- 1.1092 +ts = -- timestamp based on given data 1.1093 +atom.timestamp:new{ 1.1094 + tsec = tsec, -- seconds since January 1st 1970 00:00 1.1095 + year = year, -- year 1.1096 + month = month, -- month from 1 to 12 1.1097 + day = day, -- day from 1 to 31 1.1098 + hour = hour, -- hour from 0 to 23 1.1099 + minute = minute, -- minute from 0 to 59 1.1100 + second = second -- second from 0 to 59 1.1101 +} 1.1102 + 1.1103 +This method returns a new timestamp value, based on given data. 1.1104 + 1.1105 +--]]-- 1.1106 +function timestamp:new(args) 1.1107 + local args = args 1.1108 + if type(args) == "number" then args = { tsec = args } end 1.1109 + if type(args) == "table" then 1.1110 + if not args.second then 1.1111 + args.second = 0 1.1112 + if not args.minute then 1.1113 + args.minute = 0 1.1114 + if not args.hour then 1.1115 + args.hour = 0 1.1116 + end 1.1117 + end 1.1118 + end 1.1119 + if 1.1120 + type(args.year) == "number" and 1.1121 + type(args.month) == "number" and 1.1122 + type(args.day) == "number" and 1.1123 + type(args.hour) == "number" and 1.1124 + type(args.minute) == "number" and 1.1125 + type(args.second) == "number" 1.1126 + then 1.1127 + if 1.1128 + is_integer(args.year) and 1.1129 + args.year >= 1 and args.year <= 9999 and 1.1130 + is_integer(args.month) and 1.1131 + args.month >= 1 and args.month <= 12 and 1.1132 + is_integer(args.day) and 1.1133 + args.day >= 1 and args.day <= 31 and 1.1134 + is_integer(args.hour) and 1.1135 + args.hour >= 0 and args.hour <= 23 and 1.1136 + is_integer(args.minute) and 1.1137 + args.minute >= 0 and args.minute <= 59 and 1.1138 + is_integer(args.second) and 1.1139 + args.second >= 0 and args.second <= 59 1.1140 + then 1.1141 + return timestamp:_create{ 1.1142 + tsec = timestamp.ymdhms_to_tsec( 1.1143 + args.year, args.month, args.day, 1.1144 + args.hour, args.minute, args.second 1.1145 + ), 1.1146 + year = args.year, 1.1147 + month = args.month, 1.1148 + day = args.day, 1.1149 + hour = args.hour, 1.1150 + minute = args.minute, 1.1151 + second = args.second 1.1152 + } 1.1153 + else 1.1154 + return timestamp.invalid 1.1155 + end 1.1156 + elseif type(args.tsec) == "number" then 1.1157 + if 1.1158 + is_integer(args.tsec) and 1.1159 + args.tsec >= -62135596800 and args.tsec <= 253402300799 1.1160 + then 1.1161 + local year, month, day, hour, minute, second = 1.1162 + timestamp.tsec_to_ymdhms(args.tsec) 1.1163 + return timestamp:_create{ 1.1164 + tsec = args.tsec, 1.1165 + year = year, month = month, day = day, 1.1166 + hour = hour, minute = minute, second = second 1.1167 + } 1.1168 + else 1.1169 + return timestamp.invalid 1.1170 + end 1.1171 + end 1.1172 + end 1.1173 + error("Invalid arguments passed to timestamp constructor.") 1.1174 +end 1.1175 +--//-- 1.1176 + 1.1177 +--[[-- 1.1178 +ts = -- current date/time as timestamp 1.1179 +atom.timestamp:get_current() 1.1180 + 1.1181 +This function returns the current date and time as timestamp. 1.1182 + 1.1183 +--]]-- 1.1184 +function timestamp:get_current() 1.1185 + local now = os.date("*t") 1.1186 + return timestamp{ 1.1187 + year = now.year, month = now.month, day = now.day, 1.1188 + hour = now.hour, minute = now.min, second = now.sec 1.1189 + } 1.1190 +end 1.1191 +--//-- 1.1192 + 1.1193 +--[[-- 1.1194 +ts = -- timestamp represented by the string 1.1195 +atom.timestamp:load( 1.1196 + string -- string representing a timestamp 1.1197 +) 1.1198 + 1.1199 +This method returns a timestamp represented by the given string. 1.1200 + 1.1201 +--]]-- 1.1202 +function timestamp:load(str) 1.1203 + if str == "" then 1.1204 + return nil 1.1205 + else 1.1206 + local year, month, day, hour, minute, second = string.match( 1.1207 + str, 1.1208 + "^([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])$" 1.1209 + ) 1.1210 + if year then 1.1211 + return timestamp{ 1.1212 + year = tonumber(year), 1.1213 + month = tonumber(month), 1.1214 + day = tonumber(day), 1.1215 + hour = tonumber(hour), 1.1216 + minute = tonumber(minute), 1.1217 + second = tonumber(second) 1.1218 + } 1.1219 + else 1.1220 + return timestamp.invalid 1.1221 + end 1.1222 + end 1.1223 +end 1.1224 + 1.1225 +function timestamp:__tostring() 1.1226 + if self.invalid then 1.1227 + return "invalid_timestamp" 1.1228 + else 1.1229 + return string.format( 1.1230 + "%04i-%02i-%02i %02i:%02i:%02i", 1.1231 + self.year, self.month, self.day, self.hour, self.minute, self.second 1.1232 + ) 1.1233 + end 1.1234 +end 1.1235 + 1.1236 +function timestamp.getters:date() 1.1237 + return date{ year = self.year, month = self.month, day = self.day } 1.1238 +end 1.1239 + 1.1240 +function timestamp.getters:time() 1.1241 + return time{ 1.1242 + hour = self.hour, 1.1243 + minute = self.minute, 1.1244 + second = self.second 1.1245 + } 1.1246 +end 1.1247 + 1.1248 +function timestamp.__eq(value1, value2) 1.1249 + if value1.invalid or value2.invalid then 1.1250 + return false 1.1251 + else 1.1252 + return value1.tsec == value2.tsec 1.1253 + end 1.1254 +end 1.1255 + 1.1256 +function timestamp.__lt(value1, value2) 1.1257 + if value1.invalid or value2.invalid then 1.1258 + return false 1.1259 + else 1.1260 + return value1.tsec < value2.tsec 1.1261 + end 1.1262 +end 1.1263 + 1.1264 +function timestamp.__le(value1, value2) 1.1265 + if value1.invalid or value2.invalid then 1.1266 + return false 1.1267 + else 1.1268 + return value1.tsec <= value2.tsec 1.1269 + end 1.1270 +end 1.1271 + 1.1272 +function timestamp.__add(value1, value2) 1.1273 + if getmetatable(value1) == timestamp then 1.1274 + if getmetatable(value2) == timestamp then 1.1275 + error("Can not add two timestamps.") 1.1276 + elseif type(value2) == "number" then 1.1277 + return timestamp(value1.tsec + value2) 1.1278 + else 1.1279 + error("Right operand of '+' operator has wrong type.") 1.1280 + end 1.1281 + elseif type(value1) == "number" then 1.1282 + if getmetatable(value2) == timestamp then 1.1283 + return timestamp(value1 + value2.tsec) 1.1284 + else 1.1285 + error("Assertion failed") 1.1286 + end 1.1287 + else 1.1288 + error("Left operand of '+' operator has wrong type.") 1.1289 + end 1.1290 +end 1.1291 + 1.1292 +function timestamp.__sub(value1, value2) 1.1293 + if not getmetatable(value1) == timestamp then 1.1294 + error("Left operand of '-' operator has wrong type.") 1.1295 + end 1.1296 + if getmetatable(value2) == timestamp then 1.1297 + return value1.tsec - value2.tsec -- TODO: transform to interval 1.1298 + elseif type(value2) == "number" then 1.1299 + return timestamp(value1.tsec - value2) 1.1300 + else 1.1301 + error("Right operand of '-' operator has wrong type.") 1.1302 + end 1.1303 +end 1.1304 + 1.1305 + 1.1306 + 1.1307 +---------- 1.1308 +-- time -- 1.1309 +---------- 1.1310 + 1.1311 +time = create_new_type("time") 1.1312 + 1.1313 +function time.hms_to_dsec(hour, minute, second) 1.1314 + return 3600 * hour + 60 * minute + second 1.1315 +end 1.1316 + 1.1317 +function time.dsec_to_hms(dsec) 1.1318 + local hour = math.floor(dsec / 3600) 1.1319 + local minute = math.floor((dsec % 3600) / 60) 1.1320 + local second = dsec % 60 1.1321 + return hour, minute, second 1.1322 +end 1.1323 + 1.1324 +--[[-- 1.1325 +atom.time.invalid 1.1326 + 1.1327 +Value representing an invalid time of day. 1.1328 + 1.1329 +--]]-- 1.1330 +time.invalid = time:_create{ 1.1331 + dsec = not_a_number, 1.1332 + hour = not_a_number, minute = not_a_number, second = not_a_number, 1.1333 + invalid = true 1.1334 +} 1.1335 +--//-- 1.1336 + 1.1337 +--[[-- 1.1338 +t = -- time based on given data 1.1339 +atom.time:new{ 1.1340 + dsec = dsec, -- seconds since 00:00:00 1.1341 + hour = hour, -- hour from 0 to 23 1.1342 + minute = minute, -- minute from 0 to 59 1.1343 + second = second -- second from 0 to 59 1.1344 +} 1.1345 + 1.1346 +This method returns a new time value, based on given data. 1.1347 + 1.1348 +--]]-- 1.1349 +function time:new(args) 1.1350 + local args = args 1.1351 + if type(args) == "number" then args = { dsec = args } end 1.1352 + if type(args) == "table" then 1.1353 + if not args.second then 1.1354 + args.second = 0 1.1355 + if not args.minute then 1.1356 + args.minute = 0 1.1357 + end 1.1358 + end 1.1359 + if 1.1360 + type(args.hour) == "number" and 1.1361 + type(args.minute) == "number" and 1.1362 + type(args.second) == "number" 1.1363 + then 1.1364 + if 1.1365 + is_integer(args.hour) and 1.1366 + args.hour >= 0 and args.hour <= 23 and 1.1367 + is_integer(args.minute) and 1.1368 + args.minute >= 0 and args.minute <= 59 and 1.1369 + is_integer(args.second) and 1.1370 + args.second >= 0 and args.second <= 59 1.1371 + then 1.1372 + return time:_create{ 1.1373 + dsec = time.hms_to_dsec(args.hour, args.minute, args.second), 1.1374 + hour = args.hour, 1.1375 + minute = args.minute, 1.1376 + second = args.second 1.1377 + } 1.1378 + else 1.1379 + return time.invalid 1.1380 + end 1.1381 + elseif type(args.dsec) == "number" then 1.1382 + if 1.1383 + is_integer(args.dsec) and 1.1384 + args.dsec >= 0 and args.dsec <= 86399 1.1385 + then 1.1386 + local hour, minute, second = 1.1387 + time.dsec_to_hms(args.dsec) 1.1388 + return time:_create{ 1.1389 + dsec = args.dsec, 1.1390 + hour = hour, minute = minute, second = second 1.1391 + } 1.1392 + else 1.1393 + return time.invalid 1.1394 + end 1.1395 + end 1.1396 + end 1.1397 + error("Invalid arguments passed to time constructor.") 1.1398 +end 1.1399 +--//-- 1.1400 + 1.1401 +--[[-- 1.1402 +t = -- current time of day 1.1403 +atom.time:get_current() 1.1404 + 1.1405 +This method returns the current time of the day. 1.1406 + 1.1407 +--]]-- 1.1408 +function time:get_current() 1.1409 + local now = os.date("*t") 1.1410 + return time{ hour = now.hour, minute = now.min, second = now.sec } 1.1411 +end 1.1412 +--//-- 1.1413 + 1.1414 +--[[-- 1.1415 +t = -- time represented by the string 1.1416 +atom.time:load( 1.1417 + string -- string representing a time of day 1.1418 +) 1.1419 + 1.1420 +This method returns a time represented by the given string. 1.1421 + 1.1422 +--]]-- 1.1423 +function time:load(str) 1.1424 + if str == "" then 1.1425 + return nil 1.1426 + else 1.1427 + local hour, minute, second = string.match( 1.1428 + str, 1.1429 + "^([0-9][0-9]):([0-9][0-9]):([0-9][0-9])$" 1.1430 + ) 1.1431 + if hour then 1.1432 + return time{ 1.1433 + hour = tonumber(hour), 1.1434 + minute = tonumber(minute), 1.1435 + second = tonumber(second) 1.1436 + } 1.1437 + else 1.1438 + return time.invalid 1.1439 + end 1.1440 + end 1.1441 +end 1.1442 +--//-- 1.1443 + 1.1444 +function time:__tostring() 1.1445 + if self.invalid then 1.1446 + return "invalid_time" 1.1447 + else 1.1448 + return string.format( 1.1449 + "%02i:%02i:%02i", 1.1450 + self.hour, self.minute, self.second 1.1451 + ) 1.1452 + end 1.1453 +end 1.1454 + 1.1455 +function time.__eq(value1, value2) 1.1456 + if value1.invalid or value2.invalid then 1.1457 + return false 1.1458 + else 1.1459 + return value1.dsec == value2.dsec 1.1460 + end 1.1461 +end 1.1462 + 1.1463 +function time.__lt(value1, value2) 1.1464 + if value1.invalid or value2.invalid then 1.1465 + return false 1.1466 + else 1.1467 + return value1.dsec < value2.dsec 1.1468 + end 1.1469 +end 1.1470 + 1.1471 +function time.__le(value1, value2) 1.1472 + if value1.invalid or value2.invalid then 1.1473 + return false 1.1474 + else 1.1475 + return value1.dsec <= value2.dsec 1.1476 + end 1.1477 +end 1.1478 + 1.1479 +function time.__add(value1, value2) 1.1480 + if getmetatable(value1) == time then 1.1481 + if getmetatable(value2) == time then 1.1482 + error("Can not add two times.") 1.1483 + elseif type(value2) == "number" then 1.1484 + return time((value1.dsec + value2) % 86400) 1.1485 + else 1.1486 + error("Right operand of '+' operator has wrong type.") 1.1487 + end 1.1488 + elseif type(value1) == "number" then 1.1489 + if getmetatable(value2) == time then 1.1490 + return time((value1 + value2.dsec) % 86400) 1.1491 + else 1.1492 + error("Assertion failed") 1.1493 + end 1.1494 + else 1.1495 + error("Left operand of '+' operator has wrong type.") 1.1496 + end 1.1497 +end 1.1498 + 1.1499 +function time.__sub(value1, value2) 1.1500 + if not getmetatable(value1) == time then 1.1501 + error("Left operand of '-' operator has wrong type.") 1.1502 + end 1.1503 + if getmetatable(value2) == time then 1.1504 + return value1.dsec - value2.dsec -- TODO: transform to interval 1.1505 + elseif type(value2) == "number" then 1.1506 + return time((value1.dsec - value2) % 86400) 1.1507 + else 1.1508 + error("Right operand of '-' operator has wrong type.") 1.1509 + end 1.1510 +end 1.1511 + 1.1512 +function time.__concat(value1, value2) 1.1513 + local mt1, mt2 = getmetatable(value1), getmetatable(value2) 1.1514 + if mt1 == date and mt2 == time then 1.1515 + return timestamp{ 1.1516 + year = value1.year, month = value1.month, day = value1.day, 1.1517 + hour = value2.hour, minute = value2.minute, second = value2.second 1.1518 + } 1.1519 + elseif mt1 == time and mt2 == date then 1.1520 + return timestamp{ 1.1521 + year = value2.year, month = value2.month, day = value2.day, 1.1522 + hour = value1.hour, minute = value1.minute, second = value1.second 1.1523 + } 1.1524 + elseif mt1 == time then 1.1525 + error("Right operand of '..' operator has wrong type.") 1.1526 + elseif mt2 == time then 1.1527 + error("Left operand of '..' operator has wrong type.") 1.1528 + else 1.1529 + error("Assertion failed") 1.1530 + end 1.1531 +end 1.1532 +