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 +

Impressum / About Us