webmcp
view libraries/atom/atom.lua @ 273:5ff8d300817b
Tiny optimization in slot.put_into(...)
| author | jbe | 
|---|---|
| date | Sat Mar 21 14:37:11 2015 +0100 (2015-03-21) | 
| parents | ce8fd7767b38 | 
| children | e3e2a03f75b2 | 
 line source
     1 #!/usr/bin/env lua
     3 local _G             = _G
     4 local _VERSION       = _VERSION
     5 local assert         = assert
     6 local error          = error
     7 local getmetatable   = getmetatable
     8 local ipairs         = ipairs
     9 local next           = next
    10 local pairs          = pairs
    11 local print          = print
    12 local rawequal       = rawequal
    13 local rawget         = rawget
    14 local rawlen         = rawlen
    15 local rawset         = rawset
    16 local select         = select
    17 local setmetatable   = setmetatable
    18 local tonumber       = tonumber
    19 local tostring       = tostring
    20 local type           = type
    22 local math      = math
    23 local os        = os
    24 local string    = string
    25 local table     = table
    27 local _M = {}
    28 if _ENV then
    29   _ENV = _M
    30 else
    31   _G[...] = _M
    32   setfenv(1, _M)
    33 end
    37 ---------------------------------------
    38 -- general functions and definitions --
    39 ---------------------------------------
    41 --[[--
    42 bool =            -- true, if value is an integer within resolution
    43 atom.is_integer(
    44   value           -- value to be tested
    45 )
    47 This function returns true if the given object is an integer within resolution.
    49 --]]--
    50 function is_integer(i)
    51   return
    52     type(i) == "number" and i % 1 == 0 and
    53     (i + 1) - i == 1 and i - (i - 1) == 1
    54 end
    55 --//--
    57 --[[--
    58 atom.not_a_number
    60 Value representing an invalid numeric result. Used for atom.integer.invalid and atom.number.invalid.
    62 --]]--
    63 not_a_number = 0 / 0
    64 --//--
    66 do
    68   local shadow = setmetatable({}, { __mode = "k" })
    70   local type_mt = { __index = {} }
    72   function type_mt:__call(...)
    73     return self:new(...)
    74   end
    76   function type_mt.__index:_create(data)
    77     local value = setmetatable({}, self)
    78     shadow[value] = data
    79     return value
    80   end
    82   local function write_prohibited()
    83     error("Modification of an atom is prohibited.")
    84   end
    86   -- returns a new type as a table, which serves also as metatable
    87   function create_new_type(name)
    88     local t = setmetatable(
    89       { methods = {}, getters = {}, name = name },
    90       type_mt
    91     )
    92     function t.__index(self, key)
    93       local data = shadow[self]
    94       local value = data[key]
    95       if value ~= nil then return value end
    96       local method = t.methods[key]
    97       if method then return method end
    98       local getter = t.getters[key]
    99       if getter then return getter(self) end
   100     end
   101     t.__newindex = write_prohibited
   102     return t
   103   end
   105   --[[--
   106   bool =          -- true, if 'value' is of type 't'
   107   atom.has_type(
   108     value,        -- any value
   109     t             -- atom time, e.g. atom.date, or lua type, e.g. "string"
   110   )
   112   This function checks, if a value is of a given type. The value may be an invalid value though, e.g. atom.date.invalid.
   114   --]]--
   115   function has_type(value, t)
   116     if t == nil then error("No type passed to has_type(...) function.") end
   117     local lua_type = type(value)
   118     return
   119       lua_type == t or
   120       getmetatable(value) == t or
   121       (lua_type == "boolean" and t == _M.boolean) or
   122       (lua_type == "string" and t == _M.string) or (
   123         lua_type == "number" and
   124         (t == _M.number or (
   125           t == _M.integer and (
   126             not (value <= 0 or value >= 0) or (
   127               value % 1 == 0 and
   128               (value + 1) - value == 1 and
   129               value - (value - 1) == 1
   130             )
   131           )
   132         ))
   133       )
   134   end
   135   --//--
   137   --[[--
   138   bool =          -- true, if 'value' is of type 't'
   139   atom.is_valid(
   140     value,        -- any value
   141     t             -- atom time, e.g. atom.date, or lua type, e.g. "string"
   142   )
   144   This function checks, if a value is valid. It optionally checks, if the value is of a given type.
   146   --]]--
   147   function is_valid(value, t)
   148     local lua_type = type(value)
   149     if lua_type == "table" then
   150       local mt = getmetatable(value)
   151       if t then
   152         return t == mt and not value.invalid
   153       else
   154         return (getmetatable(mt) == type_mt) and not value.invalid
   155       end
   156     elseif lua_type == "boolean" then
   157       return not t or t == "boolean" or t == _M.boolean
   158     elseif lua_type == "string" then
   159       return not t or t == "string" or t == _M.string
   160     elseif lua_type == "number" then
   161       if t == _M.integer then
   162         return
   163           value % 1 == 0 and
   164           (value + 1) - value == 1 and
   165           value - (value - 1) == 1
   166       else
   167         return
   168           (not t or t == "number" or t == _M.number) and
   169           (value <= 0 or value >= 0)
   170       end
   171     else
   172       return false
   173     end
   174   end
   175   --//--
   177 end
   179 --[[--
   180 string =    -- string representation to be passed to a load function
   181 atom.dump(
   182   value     -- value to be dumped
   183 )
   185 This function returns a string representation of the given value.
   187 --]]--
   188 function dump(obj)
   189   if obj == nil then
   190     return ""
   191   else
   192     return tostring(obj)
   193   end
   194 end
   195 --//--
   199 -------------
   200 -- boolean --
   201 -------------
   203 boolean = { name = "boolean" }
   205 --[[--
   206 bool =              -- true, false, or nil
   207 atom.boolean:load(
   208   string            -- string to be interpreted as boolean
   209 )
   211 This method returns true or false or nil, depending on the input string.
   213 --]]--
   214 function boolean:load(str)
   215   if str == nil or str == "" then
   216     return nil
   217   elseif type(str) ~= "string" then
   218     error("String expected")
   219   elseif string.find(str, "^[TtYy1]") then
   220     return true
   221   elseif string.find(str, "^[FfNn0]") then
   222     return false
   223   else
   224     return nil  -- we don't have an undefined bool
   225   end
   226 end
   227 --//--
   231 ------------
   232 -- string --
   233 ------------
   235 _M.string = { name = "string" }
   237 --[[--
   238 string =            -- the same string
   239 atom.string:load(
   240   string            -- a string
   241 )
   243 This method returns the passed string, or throws an error, if the passed argument is not a string.
   245 --]]--
   246 function _M.string:load(str)
   247   if str == nil then
   248     return nil
   249   elseif type(str) ~= "string" then
   250     error("String expected")
   251   else
   252     return str
   253   end
   254 end
   255 --//--
   259 -------------
   260 -- integer --
   261 -------------
   263 integer = { name = "integer" }
   265 --[[--
   266 int =              -- an integer or atom.integer.invalid (atom.not_a_number)
   267 atom.integer:load(
   268   string           -- a string representing an integer
   269 )
   271 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.
   273 --]]--
   274 function integer:load(str)
   275   if str == nil or str == "" then
   276     return nil
   277   elseif type(str) ~= "string" then
   278     error("String expected")
   279   else
   280     local num = tonumber(str)
   281     if is_integer(num) then return num else return not_a_number end
   282   end
   283 end
   284 --//--
   286 --[[--
   287 atom.integer.invalid
   289 This represents an invalid integer.
   291 --]]--
   292 integer.invalid = not_a_number
   293 --//--
   297 ------------
   298 -- number --
   299 ------------
   301 number = create_new_type("number")
   303 --[[--
   304 int =              -- a number or atom.number.invalid (atom.not_a_number)
   305 atom.number:load(
   306   string           -- a string representing a number
   307 )
   309 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.
   311 --]]--
   312 function number:load(str)
   313   if str == nil or str == "" then
   314     return nil
   315   elseif type(str) ~= "string" then
   316     error("String expected")
   317   else
   318     return tonumber(str) or not_a_number
   319   end
   320 end
   321 --//--
   323 --[[--
   324 atom.number.invalid
   326 This represents an invalid number.
   328 --]]--
   329 number.invalid = not_a_number
   330 --//--
   334 --------------
   335 -- fraction --
   336 --------------
   338 fraction = create_new_type("fraction")
   340 --[[--
   341 i =        -- the greatest common divisor (GCD) of all given natural numbers
   342 atom.gcd(
   343   a,      -- a natural number
   344   b,      -- another natural number
   345   ...     -- optionally more natural numbers
   346 )
   348 This function returns the greatest common divisor (GCD) of two or more natural numbers.
   350 --]]--
   351 function gcd(a, b, ...)
   352   if a % 1 ~= 0 or a <= 0 then return 0 / 0 end
   353   if b == nil then
   354     return a
   355   else
   356     if b % 1 ~= 0 or b <= 0 then return 0 / 0 end
   357     if ... == nil then
   358       local k = 0
   359       local t
   360       while a % 2 == 0 and b % 2 == 0 do
   361         a = a / 2; b = b / 2; k = k + 1
   362       end
   363       if a % 2 == 0 then t = a else t = -b end
   364       while t ~= 0 do
   365         while t % 2 == 0 do t = t / 2 end
   366         if t > 0 then a = t else b = -t end
   367         t = a - b
   368       end
   369       return a * 2 ^ k
   370     else
   371       return gcd(gcd(a, b), ...)
   372     end
   373   end
   374 end
   375 --//--
   377 --[[--
   378 i =        --the least common multiple (LCD) of all given natural numbers
   379 atom.lcm(
   380   a,       -- a natural number
   381   b,       -- another natural number
   382   ...      -- optionally more natural numbers
   383 )
   385 This function returns the least common multiple (LCD) of two or more natural numbers.
   387 --]]--
   388 function lcm(a, b, ...)
   389   if a % 1 ~= 0 or a <= 0 then return 0 / 0 end
   390   if b == nil then
   391     return a
   392   else
   393     if b % 1 ~= 0 or b <= 0 then return 0 / 0 end
   394     if ... == nil then
   395       return a * b / gcd(a, b)
   396     else
   397       return lcm(lcm(a, b), ...)
   398     end
   399   end
   400 end
   401 --//--
   403 --[[--
   404 atom.fraction.invalid
   406 Value representing an invalid fraction.
   408 --]]--
   409 fraction.invalid = fraction:_create{
   410   numerator = not_a_number, denominator = not_a_number, invalid = true
   411 }
   412 --//--
   414 --[[--
   415 frac =              -- fraction
   416 atom.fraction:new(
   417   numerator,        -- numerator
   418   denominator       -- denominator
   419 )
   421 This method creates a new fraction.
   423 --]]--
   424 function fraction:new(numerator, denominator)
   425   if not (
   426     (numerator == nil   or type(numerator)   == "number") and
   427     (denominator == nil or type(denominator) == "number")
   428   ) then
   429     error("Invalid arguments passed to fraction constructor.")
   430   elseif
   431     (not is_integer(numerator)) or
   432     (denominator and (not is_integer(denominator)))
   433   then
   434     return fraction.invalid
   435   elseif denominator then
   436     if denominator == 0 then
   437       return fraction.invalid
   438     elseif numerator == 0 then
   439       return fraction:_create{ numerator = 0, denominator = 1, float = 0 }
   440     else
   441       local d = gcd(math.abs(numerator), math.abs(denominator))
   442       if denominator < 0 then d = -d end
   443       local numerator2, denominator2 = numerator / d, denominator / d
   444       return fraction:_create{
   445         numerator   = numerator2,
   446         denominator = denominator2,
   447         float       = numerator2 / denominator2
   448       }
   449     end
   450   else
   451     return fraction:_create{
   452       numerator = numerator, denominator = 1, float = numerator
   453     }
   454   end
   455 end
   456 --//--
   458 --[[--
   459 frac =               -- fraction represented by the given string
   460 atom.fraction:load(
   461   string             -- string representation of a fraction
   462 )
   464 This method returns a fraction represented by the given string.
   466 --]]--
   467 function fraction:load(str)
   468   if str == nil or str == "" then
   469     return nil
   470   elseif type(str) ~= "string" then
   471     error("String expected")
   472   else
   473     local sign, int = string.match(str, "^(%-?)([0-9]+)$")
   474     if sign == "" then return fraction:new(tonumber(int))
   475     elseif sign == "-" then return fraction:new(- tonumber(int))
   476     end
   477     local sign, n, d = string.match(str, "^(%-?)([0-9]+)/([0-9]+)$")
   478     if sign == "" then return fraction:new(tonumber(n), tonumber(d))
   479     elseif sign == "-" then return fraction:new(- tonumber(n), tonumber(d))
   480     end
   481     return fraction.invalid
   482   end
   483 end
   484 --//--
   486 function fraction:__tostring()
   487   if self.invalid then
   488     return "not_a_fraction"
   489   else
   490     return self.numerator .. "/" .. self.denominator
   491   end
   492 end
   494 function fraction.__eq(value1, value2)
   495   if value1.invalid or value2.invalid then
   496     return false
   497   else
   498     return
   499       value1.numerator == value2.numerator and
   500       value1.denominator == value2.denominator
   501   end
   502 end
   504 function fraction.__lt(value1, value2)
   505   if value1.invalid or value2.invalid then
   506     return false
   507   else
   508     return value1.float < value2.float
   509   end
   510 end
   512 function fraction.__le(value1, value2)
   513   if value1.invalid or value2.invalid then
   514     return false
   515   else
   516     return value1.float <= value2.float
   517   end
   518 end
   520 function fraction:__unm()
   521   return fraction(-self.numerator, self.denominator)
   522 end
   524 do
   526   local function extract(value1, value2)
   527     local n1, d1, n2, d2
   528     if getmetatable(value1) == fraction then
   529       n1 = value1.numerator
   530       d1 = value1.denominator
   531     elseif type(value1) == "number" then
   532       n1 = value1
   533       d1 = 1
   534     else
   535       error("Left operand of operator has wrong type.")
   536     end
   537     if getmetatable(value2) == fraction then
   538       n2 = value2.numerator
   539       d2 = value2.denominator
   540     elseif type(value2) == "number" then
   541       n2 = value2
   542       d2 = 1
   543     else
   544       error("Right operand of operator has wrong type.")
   545     end
   546     return n1, d1, n2, d2
   547   end
   549   function fraction.__add(value1, value2)
   550     local n1, d1, n2, d2 = extract(value1, value2)
   551     return fraction(n1 * d2 + n2 * d1, d1 * d2)
   552   end
   554   function fraction.__sub(value1, value2)
   555     local n1, d1, n2, d2 = extract(value1, value2)
   556     return fraction(n1 * d2 - n2 * d1, d1 * d2)
   557   end
   559   function fraction.__mul(value1, value2)
   560     local n1, d1, n2, d2 = extract(value1, value2)
   561     return fraction(n1 * n2, d1 * d2)
   562   end
   564   function fraction.__div(value1, value2)
   565     local n1, d1, n2, d2 = extract(value1, value2)
   566     return fraction(n1 * d2, d1 * n2)
   567   end
   569   function fraction.__pow(value1, value2)
   570     local n1, d1, n2, d2 = extract(value1, value2)
   571     local n1_abs = math.abs(n1)
   572     local d1_abs = math.abs(d1)
   573     local n2_abs = math.abs(n2)
   574     local d2_abs = math.abs(d2)
   575     local numerator, denominator
   576     if d2_abs == 1 then
   577       numerator = n1_abs
   578       denominator = d1_abs
   579     else
   580       numerator = 0
   581       while true do
   582         local t = numerator ^ d2_abs
   583         if t == n1_abs then break end
   584         if not (t < n1_abs) then return value1.float / value2.float end
   585         numerator = numerator + 1
   586       end
   587       denominator = 1
   588       while true do
   589         local t = denominator ^ d2_abs
   590         if t == d1_abs then break end
   591         if not (t < d1_abs) then return value1.float / value2.float end
   592         denominator = denominator + 1
   593       end
   594     end
   595     if n1 < 0 then
   596       if d2_abs % 2 == 1 then
   597         numerator = -numerator
   598       else
   599         return fraction.invalid
   600       end
   601     end
   602     if n2 < 0 then
   603       numerator, denominator = denominator, numerator
   604     end
   605     return fraction(numerator ^ n2_abs, denominator ^ n2_abs)
   606   end
   608 end
   612 ----------
   613 -- date --
   614 ----------
   616 date = create_new_type("date")
   618 do
   619   local c1 = 365             -- days of a non-leap year
   620   local c4 = 4 * c1 + 1      -- days of a full 4 year cycle
   621   local c100 = 25 * c4 - 1   -- days of a full 100 year cycle
   622   local c400 = 4 * c100 + 1  -- days of a full 400 year cycle
   623   local get_month_offset     -- function returning days elapsed within
   624                              -- the given year until the given month
   625                              -- (exclusive the given month)
   626   do
   627     local normal_month_offsets = {}
   628     local normal_month_lengths = {
   629       31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
   630     }
   631     local sum = 0
   632     for i = 1, 12 do
   633       normal_month_offsets[i] = sum
   634       sum = sum + normal_month_lengths[i]
   635     end
   636     function get_month_offset(year, month)
   637       if
   638         (((year % 4 == 0) and not (year % 100 == 0)) or (year % 400 == 0))
   639         and month > 2
   640       then
   641         return normal_month_offsets[month] + 1
   642       else
   643         return normal_month_offsets[month]
   644       end
   645     end
   646   end
   648   --[[--
   649   jd =                  -- days from January 1st 1970
   650   atom.date.ymd_to_jd(
   651     year,               -- year
   652     month,              -- month from 1 to 12
   653     day                 -- day from 1 to 31
   654   )
   656   This function calculates the days from January 1st 1970 for a given year, month and day.
   658   --]]--
   659   local offset = 0
   660   function date.ymd_to_jd(year, month, day)
   661     assert(is_integer(year), "Invalid year specified.")
   662     assert(is_integer(month), "Invalid month specified.")
   663     assert(is_integer(day), "Invalid day specified.")
   664     local calc_year = year - 1
   665     local n400 = math.floor(calc_year / 400)
   666     local r400 = calc_year % 400
   667     local n100 = math.floor(r400 / 100)
   668     local r100 = r400 % 100
   669     local n4 = math.floor(r100 / 4)
   670     local n1 = r100 % 4
   671     local jd = (
   672       c400 * n400 + c100 * n100 + c4 * n4 + c1 * n1 +
   673       get_month_offset(year, month) + (day - 1)
   674     )
   675     return jd - offset
   676   end
   677   offset = date.ymd_to_jd(1970, 1, 1)
   678   --//--
   680   --[[--
   681   year,                 -- year
   682   month,                -- month from 1 to 12
   683   day =                 -- day from 1 to 31
   684   atom.date.jd_to_ymd(
   685     jd,                 -- days from January 1st 1970
   686   )
   688   Given the days from January 1st 1970 this function returns year, month and day.
   690   --]]--
   691   function date.jd_to_ymd(jd)
   692     assert(is_integer(jd), "Invalid julian date specified.")
   693     local calc_jd = jd + offset
   694     assert(is_integer(calc_jd), "Julian date is out of range.")
   695     local n400 = math.floor(calc_jd / c400)
   696     local r400 = calc_jd % c400
   697     local n100 = math.floor(r400 / c100)
   698     local r100 = r400 % c100
   699     if n100 == 4 then n100, r100 = 3, c100 end
   700     local n4 = math.floor(r100 / c4)
   701     local r4 = r100 % c4
   702     local n1 = math.floor(r4 / c1)
   703     local r1 = r4 % c1
   704     if n1 == 4 then n1, r1 = 3, c1 end
   705     local year = 1 + 400 * n400 + 100 * n100 + 4 * n4 + n1
   706     local month = 1 + math.floor(r1 / 31)
   707     local month_offset = get_month_offset(year, month)
   708     if month < 12 then
   709       local next_month_offset = get_month_offset(year, month + 1)
   710       if r1 >= next_month_offset then
   711         month = month + 1
   712         month_offset = next_month_offset
   713       end
   714     end
   715     local day = 1 + r1 - month_offset
   716     return year, month, day
   717   end
   718   --//--
   719 end
   721 --[[--
   722 atom.date.invalid
   724 Value representing an invalid date.
   726 --]]--
   727 date.invalid = date:_create{
   728   jd = not_a_number,
   729   year = not_a_number, month = not_a_number, day = not_a_number,
   730   invalid = true
   731 }
   732 --//--
   734 --[[--
   735 d =                             -- date based on the given data
   736 atom.date:new{
   737   jd           = jd,            -- days since January 1st 1970
   738   year         = year,          -- year
   739   month        = month,         -- month from 1 to 12
   740   day          = day,           -- day from 1 to 31
   741   iso_weekyear = iso_weekyear,  -- year according to ISO 8601
   742   iso_week     = iso_week,      -- week number according to ISO 8601
   743   iso_weekday  = iso_weekday,   -- day of week from 1 for monday to 7 for sunday
   744   us_weekyear  = us_weekyear,   -- year
   745   us_week      = us_week,       -- week number according to US style counting
   746   us_weekday   = us_weekday     -- day of week from 1 for sunday to 7 for saturday
   747 }
   749 This method returns a new date value, based on given data.
   751 --]]--
   752 function date:new(args)
   753   local args = args
   754   if type(args) == "number" then args = { jd = args } end
   755   if type(args) == "table" then
   756     local year, month, day = args.year, args.month, args.day
   757     local jd = args.jd
   758     local iso_weekyear = args.iso_weekyear
   759     local iso_week     = args.iso_week
   760     local iso_weekday  = args.iso_weekday
   761     local us_week      = args.us_week
   762     local us_weekday   = args.us_weekday
   763     if
   764       type(year)  == "number" and
   765       type(month) == "number" and
   766       type(day)   == "number"
   767     then
   768       if
   769         is_integer(year)  and year >= 1  and year <= 9999 and
   770         is_integer(month) and month >= 1 and month <= 12  and
   771         is_integer(day)   and day >= 1   and day <= 31
   772       then
   773         return date:_create{
   774           jd = date.ymd_to_jd(year, month, day),
   775           year = year, month = month, day = day
   776         }
   777       else
   778         return date.invalid
   779       end
   780     elseif type(jd) == "number" then
   781       if is_integer(jd) and jd >= -719162 and jd <= 2932896 then
   782         local year, month, day = date.jd_to_ymd(jd)
   783         return date:_create{
   784           jd = jd, year = year, month = month, day = day
   785         }
   786       else
   787         return date.invalid
   788       end
   789     elseif
   790       type(year)        == "number" and not iso_weekyear and
   791       type(iso_week)    == "number" and
   792       type(iso_weekday) == "number"
   793     then
   794       if
   795         is_integer(year) and
   796         is_integer(iso_week)     and iso_week >= 0 and iso_week <= 53 and
   797         is_integer(iso_weekday)  and iso_weekday >= 1 and iso_weekday <= 7
   798       then
   799         local jan4 = date{ year = year, month = 1, day = 4 }
   800         local reference = jan4 - jan4.iso_weekday - 7  -- Sun. of week -1
   801         return date(reference + 7 * iso_week + iso_weekday)
   802       else
   803         return date.invalid
   804       end
   805     elseif
   806       type(iso_weekyear) == "number" and not year and
   807       type(iso_week)     == "number" and
   808       type(iso_weekday)  == "number"
   809     then
   810       if
   811         is_integer(iso_weekyear) and
   812         is_integer(iso_week)     and iso_week >= 0 and iso_week <= 53 and
   813         is_integer(iso_weekday)  and iso_weekday >= 1 and iso_weekday <= 7
   814       then
   815         local guessed = date{
   816           year        = iso_weekyear,
   817           iso_week    = iso_week,
   818           iso_weekday = iso_weekday
   819         }
   820         if guessed.invalid or guessed.iso_weekyear == iso_weekyear then
   821           return guessed
   822         else
   823           local year
   824           if iso_week <= 1 then
   825             year = iso_weekyear - 1
   826           elseif iso_week >= 52 then
   827             year = iso_weekyear + 1
   828           else
   829             error("Internal error in ISO week computation occured.")
   830           end
   831           return date{
   832             year = year, iso_week = iso_week, iso_weekday = iso_weekday
   833           }
   834         end
   835       else
   836         return date.invalid
   837       end
   838     elseif
   839       type(year) == "number" and
   840       type(us_week)     == "number" and
   841       type(us_weekday)  == "number"
   842     then
   843       if
   844         is_integer(year) and
   845         is_integer(us_week)     and us_week >= 0    and us_week <= 54   and
   846         is_integer(us_weekday)  and us_weekday >= 1 and us_weekday <= 7
   847       then
   848         local jan1 = date{ year = year, month = 1, day = 1 }
   849         local reference = jan1 - jan1.us_weekday - 7  -- Sat. of week -1
   850         return date(reference + 7 * us_week + us_weekday)
   851       else
   852         return date.invalid
   853       end
   854     end
   855   end
   856   error("Illegal arguments passed to date constructor.")
   857 end
   858 --//--
   860 --[[--
   861 atom.date:get_current()
   863 This function returns today's date.
   865 --]]--
   866 function date:get_current()
   867   local now = os.date("*t")
   868   return date{
   869     year = now.year, month = now.month, day = now.day
   870   }
   871 end
   872 --//--
   874 --[[--
   875 date =           -- date represented by the string
   876 atom.date:load(
   877   string         -- string representing a date
   878 )
   880 This method returns a date represented by the given string.
   882 --]]--
   883 function date:load(str)
   884   if str == nil or str == "" then
   885     return nil
   886   elseif type(str) ~= "string" then
   887     error("String expected")
   888   else
   889     local year, month, day = string.match(
   890       str, "^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$"
   891     )
   892     if year then
   893       return date{
   894         year = tonumber(year),
   895         month = tonumber(month),
   896         day = tonumber(day)
   897       }
   898     else
   899       return date.invalid
   900     end
   901   end
   902 end
   903 --//--
   905 function date:__tostring()
   906   if self.invalid then
   907     return "invalid_date"
   908   else
   909     return string.format(
   910       "%04i-%02i-%02i", self.year, self.month, self.day
   911     )
   912   end
   913 end
   915 function date.getters:midnight()
   916   return timestamp{ year = self.year, month = self.month, day = self.day }
   917 end
   919 function date.getters:midday()
   920   return timestamp{
   921     year = self.year, month = self.month, day = self.day,
   922     hour = 12
   923   }
   924 end
   926 function date.getters:iso_weekday()  -- 1 = Monday
   927   return (self.jd + 3) % 7 + 1
   928 end
   930 function date.getters:us_weekday()  -- 1 = Sunday
   931   return (self.jd + 4) % 7 + 1
   932 end
   934 function date.getters:iso_weekyear()  -- ISO week-numbering year
   935   local year, month, day = self.year, self.month, self.day
   936   local iso_weekday      = self.iso_weekday
   937   if month == 1 then
   938     if
   939       (day == 3 and iso_weekday == 7) or
   940       (day == 2 and iso_weekday >= 6) or
   941       (day == 1 and iso_weekday >= 5)
   942     then
   943       return year - 1
   944     end
   945   elseif month == 12 then
   946     if
   947       (day == 29 and iso_weekday == 1) or
   948       (day == 30 and iso_weekday <= 2) or
   949       (day == 31 and iso_weekday <= 3)
   950     then
   951       return year + 1
   952     end
   953   end
   954   return year
   955 end
   957 function date.getters:iso_week()
   958   local jan4 = date{ year = self.iso_weekyear, month = 1, day = 4 }
   959   local reference = jan4.jd - jan4.iso_weekday - 6  -- monday of week 0
   960   return math.floor((self.jd - reference) / 7)
   961 end
   963 function date.getters:us_week()
   964   local jan1 = date{ year = self.year, month = 1, day = 1 }
   965   local reference = jan1.jd - jan1.us_weekday - 6  -- sunday of week 0
   966   return math.floor((self.jd - reference) / 7)
   967 end
   969 function date.__eq(value1, value2)
   970   if value1.invalid or value2.invalid then
   971     return false
   972   else
   973     return value1.jd == value2.jd
   974   end
   975 end
   977 function date.__lt(value1, value2)
   978   if value1.invalid or value2.invalid then
   979     return false
   980   else
   981     return value1.jd < value2.jd
   982   end
   983 end
   985 function date.__le(value1, value2)
   986   if value1.invalid or value2.invalid then
   987     return false
   988   else
   989     return value1.jd <= value2.jd
   990   end
   991 end
   993 function date.__add(value1, value2)
   994   if getmetatable(value1) == date then
   995     if getmetatable(value2) == date then
   996       error("Can not add two dates.")
   997     elseif type(value2) == "number" then
   998       return date(value1.jd + value2)
   999     else
  1000       error("Right operand of '+' operator has wrong type.")
  1001     end
  1002   elseif type(value1) == "number" then
  1003     if getmetatable(value2) == date then
  1004       return date(value1 + value2.jd)
  1005     else
  1006       error("Assertion failed")
  1007     end
  1008   else
  1009     error("Left operand of '+' operator has wrong type.")
  1010   end
  1011 end
  1013 function date.__sub(value1, value2)
  1014   if not getmetatable(value1) == date then
  1015     error("Left operand of '-' operator has wrong type.")
  1016   end
  1017   if getmetatable(value2) == date then
  1018     return value1.jd - value2.jd  -- TODO: transform to interval
  1019   elseif type(value2) == "number" then
  1020     return date(value1.jd - value2)
  1021   else
  1022     error("Right operand of '-' operator has wrong type.")
  1023   end
  1024 end
  1028 ---------------
  1029 -- timestamp --
  1030 ---------------
  1032 timestamp = create_new_type("timestamp")
  1034 --[[--
  1035 tsec =                          -- seconds since January 1st 1970 00:00
  1036 atom.timestamp.ymdhms_to_tsec(
  1037   year,                         -- year
  1038   month,                        -- month from 1 to 12
  1039   day,                          -- day from 1 to 31
  1040   hour,                         -- hour from 0 to 23
  1041   minute,                       -- minute from 0 to 59
  1042   second                        -- second from 0 to 59
  1043 )
  1045 Given the year, month, day, hour, minute and second, this function returns the number of seconds since January 1st 1970 00:00.
  1047 --]]--
  1048 function timestamp.ymdhms_to_tsec(year, month, day, hour, minute, second)
  1049   return
  1050     86400 * date.ymd_to_jd(year, month, day) +
  1051     3600 * hour + 60 * minute + second
  1052 end
  1053 --//--
  1055 --[[--
  1056 year,                      -- year
  1057 month,                     -- month from 1 to 12
  1058 day,                       -- day from 1 to 31
  1059 hour,                      -- hour from 0 to 23
  1060 minute,                    -- minute from 0 to 59
  1061 second =                   -- second from 0 to 59
  1062 atom.timestamp.tsec_to_ymdhms(
  1063   tsec                     -- seconds since January 1st 1970 00:00
  1064 )
  1066 Given the seconds since January 1st 1970 00:00, this function returns the year, month, day, hour, minute and second.
  1068 --]]--
  1069 function timestamp.tsec_to_ymdhms(tsec)
  1070   local jd   = math.floor(tsec / 86400)
  1071   local dsec = tsec % 86400
  1072   local year, month, day = date.jd_to_ymd(jd)
  1073   local hour   = math.floor(dsec / 3600)
  1074   local minute = math.floor((dsec % 3600) / 60)
  1075   local second = dsec % 60
  1076   return year, month, day, hour, minute, second
  1077 end
  1078 --//--
  1080 --[[--
  1081 timestamp.invalid
  1083 Value representing an invalid timestamp.
  1085 --]]--
  1086 timestamp.invalid = timestamp:_create{
  1087   tsec = not_a_number,
  1088   year = not_a_number, month = not_a_number, day = not_a_number,
  1089   hour = not_a_number, minute = not_a_number, second = not_a_number,
  1090   invalid = true
  1091 }
  1092 --//--
  1094 --[[--
  1095 ts =                 -- timestamp based on given data
  1096 atom.timestamp:new{
  1097   tsec   = tsec,     -- seconds since January 1st 1970 00:00
  1098   year   = year,     -- year
  1099   month  = month,    -- month from 1 to 12
  1100   day    = day,      -- day from 1 to 31
  1101   hour   = hour,     -- hour from 0 to 23
  1102   minute = minute,   -- minute from 0 to 59
  1103   second = second    -- second from 0 to 59
  1104 }
  1106 This method returns a new timestamp value, based on given data.
  1108 --]]--
  1109 function timestamp:new(args)
  1110   local args = args
  1111   if type(args) == "number" then args = { tsec = args } end
  1112   if type(args) == "table" then
  1113     if not args.second then
  1114       args.second = 0
  1115       if not args.minute then
  1116         args.minute = 0
  1117         if not args.hour then
  1118           args.hour = 0
  1119         end
  1120       end
  1121     end
  1122     if
  1123       type(args.year)   == "number" and
  1124       type(args.month)  == "number" and
  1125       type(args.day)    == "number" and
  1126       type(args.hour)   == "number" and
  1127       type(args.minute) == "number" and
  1128       type(args.second) == "number"
  1129     then
  1130       if
  1131         is_integer(args.year) and
  1132         args.year >= 1 and args.year <= 9999 and
  1133         is_integer(args.month) and
  1134         args.month >= 1 and args.month <= 12 and
  1135         is_integer(args.day) and
  1136         args.day >= 1 and args.day <= 31 and
  1137         is_integer(args.hour) and
  1138         args.hour >= 0 and args.hour <= 23 and
  1139         is_integer(args.minute) and
  1140         args.minute >= 0 and args.minute <= 59 and
  1141         is_integer(args.second) and
  1142         args.second >= 0 and args.second <= 59
  1143       then
  1144         return timestamp:_create{
  1145           tsec = timestamp.ymdhms_to_tsec(
  1146             args.year, args.month, args.day,
  1147             args.hour, args.minute, args.second
  1148           ),
  1149           year   = args.year,
  1150           month  = args.month,
  1151           day    = args.day,
  1152           hour   = args.hour,
  1153           minute = args.minute,
  1154           second = args.second
  1155         }
  1156       else
  1157         return timestamp.invalid
  1158       end
  1159     elseif type(args.tsec) == "number" then
  1160       if
  1161         is_integer(args.tsec) and
  1162         args.tsec >= -62135596800 and args.tsec <= 253402300799
  1163       then
  1164         local year, month, day, hour, minute, second =
  1165           timestamp.tsec_to_ymdhms(args.tsec)
  1166         return timestamp:_create{
  1167           tsec = args.tsec,
  1168           year = year, month = month, day = day,
  1169           hour = hour, minute = minute, second = second
  1170         }
  1171       else
  1172         return timestamp.invalid
  1173       end
  1174     end
  1175   end
  1176   error("Invalid arguments passed to timestamp constructor.")
  1177 end
  1178 --//--
  1180 --[[--
  1181 ts =                          -- current date/time as timestamp
  1182 atom.timestamp:get_current()
  1184 This function returns the current date and time as timestamp.
  1186 --]]--
  1187 function timestamp:get_current()
  1188   local now = os.date("*t")
  1189   return timestamp{
  1190     year = now.year, month = now.month, day = now.day,
  1191     hour = now.hour, minute = now.min, second = now.sec
  1192   }
  1193 end
  1194 --//--
  1196 --[[--
  1197 ts =             -- timestamp represented by the string
  1198 atom.timestamp:load(
  1199   string         -- string representing a timestamp
  1200 )
  1202 This method returns a timestamp represented by the given string.
  1204 --]]--
  1205 function timestamp:load(str)
  1206   if str == nil or str == "" then
  1207     return nil
  1208   elseif type(str) ~= "string" then
  1209     error("String expected")
  1210   else
  1211     local year, month, day, hour, minute, second = string.match(
  1212       str,
  1213       "^([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])$"
  1214     )
  1215     if year then
  1216       return timestamp{
  1217         year   = tonumber(year),
  1218         month  = tonumber(month),
  1219         day    = tonumber(day),
  1220         hour   = tonumber(hour),
  1221         minute = tonumber(minute),
  1222         second = tonumber(second)
  1223       }
  1224     else
  1225       return timestamp.invalid
  1226     end
  1227   end
  1228 end
  1230 function timestamp:__tostring()
  1231   if self.invalid then
  1232     return "invalid_timestamp"
  1233   else
  1234     return string.format(
  1235       "%04i-%02i-%02i %02i:%02i:%02i",
  1236       self.year, self.month, self.day, self.hour, self.minute, self.second
  1237     )
  1238   end
  1239 end
  1241 function timestamp.getters:date()
  1242   return date{ year = self.year, month = self.month, day = self.day }
  1243 end
  1245 function timestamp.getters:time()
  1246   return time{
  1247     hour = self.hour,
  1248     minute = self.minute,
  1249     second = self.second
  1250   }
  1251 end
  1253 function timestamp.__eq(value1, value2)
  1254   if value1.invalid or value2.invalid then
  1255     return false
  1256   else
  1257     return value1.tsec == value2.tsec
  1258   end
  1259 end
  1261 function timestamp.__lt(value1, value2)
  1262   if value1.invalid or value2.invalid then
  1263     return false
  1264   else
  1265     return value1.tsec < value2.tsec
  1266   end
  1267 end
  1269 function timestamp.__le(value1, value2)
  1270   if value1.invalid or value2.invalid then
  1271     return false
  1272   else
  1273     return value1.tsec <= value2.tsec
  1274   end
  1275 end
  1277 function timestamp.__add(value1, value2)
  1278   if getmetatable(value1) == timestamp then
  1279     if getmetatable(value2) == timestamp then
  1280       error("Can not add two timestamps.")
  1281     elseif type(value2) == "number" then
  1282       return timestamp(value1.tsec + value2)
  1283     else
  1284       error("Right operand of '+' operator has wrong type.")
  1285     end
  1286   elseif type(value1) == "number" then
  1287     if getmetatable(value2) == timestamp then
  1288       return timestamp(value1 + value2.tsec)
  1289     else
  1290       error("Assertion failed")
  1291     end
  1292   else
  1293     error("Left operand of '+' operator has wrong type.")
  1294   end
  1295 end
  1297 function timestamp.__sub(value1, value2)
  1298   if not getmetatable(value1) == timestamp then
  1299     error("Left operand of '-' operator has wrong type.")
  1300   end
  1301   if getmetatable(value2) == timestamp then
  1302     return value1.tsec - value2.tsec  -- TODO: transform to interval
  1303   elseif type(value2) == "number" then
  1304     return timestamp(value1.tsec - value2)
  1305   else
  1306     error("Right operand of '-' operator has wrong type.")
  1307   end
  1308 end
  1312 ----------
  1313 -- time --
  1314 ----------
  1316 time = create_new_type("time")
  1318 function time.hms_to_dsec(hour, minute, second)
  1319   return 3600 * hour + 60 * minute + second
  1320 end
  1322 function time.dsec_to_hms(dsec)
  1323   local hour   = math.floor(dsec / 3600)
  1324   local minute = math.floor((dsec % 3600) / 60)
  1325   local second = dsec % 60
  1326   return hour, minute, second
  1327 end
  1329 --[[--
  1330 atom.time.invalid
  1332 Value representing an invalid time of day.
  1334 --]]--
  1335 time.invalid = time:_create{
  1336   dsec = not_a_number,
  1337   hour = not_a_number, minute = not_a_number, second = not_a_number,
  1338   invalid = true
  1339 }
  1340 --//--
  1342 --[[--
  1343 t =                 -- time based on given data
  1344 atom.time:new{
  1345   dsec = dsec,      -- seconds since 00:00:00
  1346   hour = hour,      -- hour from 0 to 23
  1347   minute = minute,  -- minute from 0 to 59
  1348   second = second   -- second from 0 to 59
  1349 }
  1351 This method returns a new time value, based on given data.
  1353 --]]--
  1354 function time:new(args)
  1355   local args = args
  1356   if type(args) == "number" then args = { dsec = args } end
  1357   if type(args) == "table" then
  1358     if not args.second then
  1359       args.second = 0
  1360       if not args.minute then
  1361         args.minute = 0
  1362       end
  1363     end
  1364     if
  1365       type(args.hour)   == "number" and
  1366       type(args.minute) == "number" and
  1367       type(args.second) == "number"
  1368     then
  1369       if
  1370         is_integer(args.hour) and
  1371         args.hour >= 0 and args.hour <= 23 and
  1372         is_integer(args.minute) and
  1373         args.minute >= 0 and args.minute <= 59 and
  1374         is_integer(args.second) and
  1375         args.second >= 0 and args.second <= 59
  1376       then
  1377         return time:_create{
  1378           dsec = time.hms_to_dsec(args.hour, args.minute, args.second),
  1379           hour   = args.hour,
  1380           minute = args.minute,
  1381           second = args.second
  1382         }
  1383       else
  1384         return time.invalid
  1385       end
  1386     elseif type(args.dsec) == "number" then
  1387       if
  1388         is_integer(args.dsec) and
  1389         args.dsec >= 0 and args.dsec <= 86399
  1390       then
  1391         local hour, minute, second =
  1392           time.dsec_to_hms(args.dsec)
  1393         return time:_create{
  1394           dsec = args.dsec,
  1395           hour = hour, minute = minute, second = second
  1396         }
  1397       else
  1398         return time.invalid
  1399       end
  1400     end
  1401   end
  1402   error("Invalid arguments passed to time constructor.")
  1403 end
  1404 --//--
  1406 --[[--
  1407 t =                      -- current time of day
  1408 atom.time:get_current()
  1410 This method returns the current time of the day.
  1412 --]]--
  1413 function time:get_current()
  1414   local now = os.date("*t")
  1415   return time{ hour = now.hour, minute = now.min, second = now.sec }
  1416 end
  1417 --//--
  1419 --[[--
  1420 t =              -- time represented by the string
  1421 atom.time:load(
  1422   string         -- string representing a time of day
  1423 )
  1425 This method returns a time represented by the given string.
  1427 --]]--
  1428 function time:load(str)
  1429   if str == nil or str == "" then
  1430     return nil
  1431   elseif type(str) ~= "string" then
  1432     error("String expected")
  1433   else
  1434     local hour, minute, second = string.match(
  1435       str,
  1436       "^([0-9][0-9]):([0-9][0-9]):([0-9][0-9])$"
  1437     )
  1438     if hour then
  1439       return time{
  1440         hour   = tonumber(hour),
  1441         minute = tonumber(minute),
  1442         second = tonumber(second)
  1443       }
  1444     else
  1445       return time.invalid
  1446     end
  1447   end
  1448 end
  1449 --//--
  1451 function time:__tostring()
  1452   if self.invalid then
  1453     return "invalid_time"
  1454   else
  1455     return string.format(
  1456       "%02i:%02i:%02i",
  1457       self.hour, self.minute, self.second
  1458     )
  1459   end
  1460 end
  1462 function time.__eq(value1, value2)
  1463   if value1.invalid or value2.invalid then
  1464     return false
  1465   else
  1466     return value1.dsec == value2.dsec
  1467   end
  1468 end
  1470 function time.__lt(value1, value2)
  1471   if value1.invalid or value2.invalid then
  1472     return false
  1473   else
  1474     return value1.dsec < value2.dsec
  1475   end
  1476 end
  1478 function time.__le(value1, value2)
  1479   if value1.invalid or value2.invalid then
  1480     return false
  1481   else
  1482     return value1.dsec <= value2.dsec
  1483   end
  1484 end
  1486 function time.__add(value1, value2)
  1487   if getmetatable(value1) == time then
  1488     if getmetatable(value2) == time then
  1489       error("Can not add two times.")
  1490     elseif type(value2) == "number" then
  1491       return time((value1.dsec + value2) % 86400)
  1492     else
  1493       error("Right operand of '+' operator has wrong type.")
  1494     end
  1495   elseif type(value1) == "number" then
  1496     if getmetatable(value2) == time then
  1497       return time((value1 + value2.dsec) % 86400)
  1498     else
  1499       error("Assertion failed")
  1500     end
  1501   else
  1502     error("Left operand of '+' operator has wrong type.")
  1503   end
  1504 end
  1506 function time.__sub(value1, value2)
  1507   if not getmetatable(value1) == time then
  1508     error("Left operand of '-' operator has wrong type.")
  1509   end
  1510   if getmetatable(value2) == time then
  1511     return value1.dsec - value2.dsec  -- TODO: transform to interval
  1512   elseif type(value2) == "number" then
  1513     return time((value1.dsec - value2) % 86400)
  1514   else
  1515     error("Right operand of '-' operator has wrong type.")
  1516   end
  1517 end
  1519 function time.__concat(value1, value2)
  1520   local mt1, mt2 = getmetatable(value1), getmetatable(value2)
  1521   if mt1 == date and mt2 == time then
  1522     return timestamp{
  1523       year = value1.year, month = value1.month, day = value1.day,
  1524       hour = value2.hour, minute = value2.minute, second = value2.second
  1525     }
  1526   elseif mt1 == time and mt2 == date then
  1527     return timestamp{
  1528       year = value2.year, month = value2.month, day = value2.day,
  1529       hour = value1.hour, minute = value1.minute, second = value1.second
  1530     }
  1531   elseif mt1 == time then
  1532     error("Right operand of '..' operator has wrong type.")
  1533   elseif mt2 == time then
  1534     error("Left operand of '..' operator has wrong type.")
  1535   else
  1536     error("Assertion failed")
  1537   end
  1538 end
  1542 return _M
