webmcp
view 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 source
     1 #!/usr/bin/env lua
     3 local _G             = _G
     4 local _VERSION       = _VERSION
     5 local assert         = assert
     6 local error          = error
     7 local getfenv        = getfenv
     8 local getmetatable   = getmetatable
     9 local ipairs         = ipairs
    10 local module         = module
    11 local next           = next
    12 local pairs          = pairs
    13 local print          = print
    14 local rawequal       = rawequal
    15 local rawget         = rawget
    16 local rawset         = rawset
    17 local require        = require
    18 local select         = select
    19 local setfenv        = setfenv
    20 local setmetatable   = setmetatable
    21 local tonumber       = tonumber
    22 local tostring       = tostring
    23 local type           = type
    24 local unpack         = unpack
    26 local coroutine = coroutine
    27 local io        = io
    28 local math      = math
    29 local os        = os
    30 local string    = string
    31 local table     = table
    33 module(...)
    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 type(str) ~= "string" then
   216     error("String expected")
   217   elseif str == "" then
   218     return nil
   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 type(str) ~= "string" then
   248     error("String expected")
   249   else
   250     return str
   251   end
   252 end
   253 --//--
   257 -------------
   258 -- integer --
   259 -------------
   261 integer = { name = "integer" }
   263 --[[--
   264 int =              -- an integer or atom.integer.invalid (atom.not_a_number)
   265 atom.integer:load(
   266   string           -- a string representing an integer
   267 )
   269 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.
   271 --]]--
   272 function integer:load(str)
   273   if type(str) ~= "string" then
   274     error("String expected")
   275   elseif str == "" then
   276     return nil
   277   else
   278     local num = tonumber(str)
   279     if is_integer(num) then return num else return not_a_number end
   280   end
   281 end
   282 --//--
   284 --[[--
   285 atom.integer.invalid
   287 This represents an invalid integer.
   289 --]]--
   290 integer.invalid = not_a_number
   291 --//
   295 ------------
   296 -- number --
   297 ------------
   299 number = create_new_type("number")
   301 --[[--
   302 int =              -- a number or atom.number.invalid (atom.not_a_number)
   303 atom.number:load(
   304   string           -- a string representing a number
   305 )
   307 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.
   309 --]]--
   310 function number:load(str)
   311   if type(str) ~= "string" then
   312     error("String expected")
   313   elseif str == "" then
   314     return nil
   315   else
   316     return tonumber(str) or not_a_number
   317   end
   318 end
   319 --//--
   321 --[[--
   322 atom.number.invalid
   324 This represents an invalid number.
   326 --]]--
   327 number.invalid = not_a_number
   328 --//--
   332 --------------
   333 -- fraction --
   334 --------------
   336 fraction = create_new_type("fraction")
   338 --[[--
   339 i =        -- the greatest common divisor (GCD) of all given natural numbers
   340 atom.gcd(
   341   a,      -- a natural number
   342   b,      -- another natural number
   343   ...     -- optionally more natural numbers
   344 )
   346 This function returns the greatest common divisor (GCD) of two or more natural numbers.
   348 --]]--
   349 function gcd(a, b, ...)
   350   if a % 1 ~= 0 or a <= 0 then return 0 / 0 end
   351   if b == nil then
   352     return a
   353   else
   354     if b % 1 ~= 0 or b <= 0 then return 0 / 0 end
   355     if ... == nil then
   356       local k = 0
   357       local t
   358       while a % 2 == 0 and b % 2 == 0 do
   359         a = a / 2; b = b / 2; k = k + 1
   360       end
   361       if a % 2 == 0 then t = a else t = -b end
   362       while t ~= 0 do
   363         while t % 2 == 0 do t = t / 2 end
   364         if t > 0 then a = t else b = -t end
   365         t = a - b
   366       end
   367       return a * 2 ^ k
   368     else
   369       return gcd(gcd(a, b), ...)
   370     end
   371   end
   372 end
   373 --//--
   375 --[[--
   376 i =        --the least common multiple (LCD) of all given natural numbers
   377 atom.lcm(
   378   a,       -- a natural number
   379   b,       -- another natural number
   380   ...      -- optionally more natural numbers
   381 )
   383 This function returns the least common multiple (LCD) of two or more natural numbers.
   385 --]]--
   386 function lcm(a, b, ...)
   387   if a % 1 ~= 0 or a <= 0 then return 0 / 0 end
   388   if b == nil then
   389     return a
   390   else
   391     if b % 1 ~= 0 or b <= 0 then return 0 / 0 end
   392     if ... == nil then
   393       return a * b / gcd(a, b)
   394     else
   395       return lcm(lcm(a, b), ...)
   396     end
   397   end
   398 end
   399 --//--
   401 --[[--
   402 atom.fraction.invalid
   404 Value representing an invalid fraction.
   406 --]]--
   407 fraction.invalid = fraction:_create{
   408   numerator = not_a_number, denominator = not_a_number, invalid = true
   409 }
   410 --//--
   412 --[[--
   413 frac =              -- fraction
   414 atom.fraction:new(
   415   numerator,        -- numerator
   416   denominator       -- denominator
   417 )
   419 This method creates a new fraction.
   421 --]]--
   422 function fraction:new(numerator, denominator)
   423   if not (
   424     (numerator == nil   or type(numerator)   == "number") and
   425     (denominator == nil or type(denominator) == "number")
   426   ) then
   427     error("Invalid arguments passed to fraction constructor.")
   428   elseif
   429     (not is_integer(numerator)) or
   430     (denominator and (not is_integer(denominator)))
   431   then
   432     return fraction.invalid
   433   elseif denominator then
   434     if denominator == 0 then
   435       return fraction.invalid
   436     elseif numerator == 0 then
   437       return fraction:_create{ numerator = 0, denominator = 1, float = 0 }
   438     else
   439       local d = gcd(math.abs(numerator), math.abs(denominator))
   440       if denominator < 0 then d = -d end
   441       local numerator2, denominator2 = numerator / d, denominator / d
   442       return fraction:_create{
   443         numerator   = numerator2,
   444         denominator = denominator2,
   445         float       = numerator2 / denominator2
   446       }
   447     end
   448   else
   449     return fraction:_create{
   450       numerator = numerator, denominator = 1, float = numerator
   451     }
   452   end
   453 end
   454 --//--
   456 --[[--
   457 frac =               -- fraction represented by the given string
   458 atom.fraction:load(
   459   string             -- string representation of a fraction
   460 )
   462 This method returns a fraction represented by the given string.
   464 --]]--
   465 function fraction:load(str)
   466   if str == "" then
   467     return nil
   468   else
   469     local sign, int = string.match(str, "^(%-?)([0-9]+)$")
   470     if sign == "" then return fraction:new(tonumber(int))
   471     elseif sign == "-" then return fraction:new(- tonumber(int))
   472     end
   473     local sign, n, d = string.match(str, "^(%-?)([0-9]+)/([0-9]+)$")
   474     if sign == "" then return fraction:new(tonumber(n), tonumber(d))
   475     elseif sign == "-" then return fraction:new(- tonumber(n), tonumber(d))
   476     end
   477     return fraction.invalid
   478   end
   479 end
   480 --//--
   482 function fraction:__tostring()
   483   if self.invalid then
   484     return "not_a_fraction"
   485   else
   486     return self.numerator .. "/" .. self.denominator
   487   end
   488 end
   490 function fraction.__eq(value1, value2)
   491   if value1.invalid or value2.invalid then
   492     return false
   493   else
   494     return
   495       value1.numerator == value2.numerator and
   496       value1.denominator == value2.denominator
   497   end
   498 end
   500 function fraction.__lt(value1, value2)
   501   if value1.invalid or value2.invalid then
   502     return false
   503   else
   504     return value1.float < value2.float
   505   end
   506 end
   508 function fraction.__le(value1, value2)
   509   if value1.invalid or value2.invalid then
   510     return false
   511   else
   512     return value1.float <= value2.float
   513   end
   514 end
   516 function fraction:__unm()
   517   return fraction(-self.numerator, self.denominator)
   518 end
   520 do
   522   local function extract(value1, value2)
   523     local n1, d1, n2, d2
   524     if getmetatable(value1) == fraction then
   525       n1 = value1.numerator
   526       d1 = value1.denominator
   527     elseif type(value1) == "number" then
   528       n1 = value1
   529       d1 = 1
   530     else
   531       error("Left operand of operator has wrong type.")
   532     end
   533     if getmetatable(value2) == fraction then
   534       n2 = value2.numerator
   535       d2 = value2.denominator
   536     elseif type(value2) == "number" then
   537       n2 = value2
   538       d2 = 1
   539     else
   540       error("Right operand of operator has wrong type.")
   541     end
   542     return n1, d1, n2, d2
   543   end
   545   function fraction.__add(value1, value2)
   546     local n1, d1, n2, d2 = extract(value1, value2)
   547     return fraction(n1 * d2 + n2 * d1, d1 * d2)
   548   end
   550   function fraction.__sub(value1, value2)
   551     local n1, d1, n2, d2 = extract(value1, value2)
   552     return fraction(n1 * d2 - n2 * d1, d1 * d2)
   553   end
   555   function fraction.__mul(value1, value2)
   556     local n1, d1, n2, d2 = extract(value1, value2)
   557     return fraction(n1 * n2, d1 * d2)
   558   end
   560   function fraction.__div(value1, value2)
   561     local n1, d1, n2, d2 = extract(value1, value2)
   562     return fraction(n1 * d2, d1 * n2)
   563   end
   565   function fraction.__pow(value1, value2)
   566     local n1, d1, n2, d2 = extract(value1, value2)
   567     local n1_abs = math.abs(n1)
   568     local d1_abs = math.abs(d1)
   569     local n2_abs = math.abs(n2)
   570     local d2_abs = math.abs(d2)
   571     local numerator, denominator
   572     if d2_abs == 1 then
   573       numerator = n1_abs
   574       denominator = d1_abs
   575     else
   576       numerator = 0
   577       while true do
   578         local t = numerator ^ d2_abs
   579         if t == n1_abs then break end
   580         if not (t < n1_abs) then return value1.float / value2.float end
   581         numerator = numerator + 1
   582       end
   583       denominator = 1
   584       while true do
   585         local t = denominator ^ d2_abs
   586         if t == d1_abs then break end
   587         if not (t < d1_abs) then return value1.float / value2.float end
   588         denominator = denominator + 1
   589       end
   590     end
   591     if n1 < 0 then
   592       if d2_abs % 2 == 1 then
   593         numerator = -numerator
   594       else
   595         return fraction.invalid
   596       end
   597     end
   598     if n2 < 0 then
   599       numerator, denominator = denominator, numerator
   600     end
   601     return fraction(numerator ^ n2_abs, denominator ^ n2_abs)
   602   end
   604 end
   608 ----------
   609 -- date --
   610 ----------
   612 date = create_new_type("date")
   614 do
   615   local c1 = 365             -- days of a non-leap year
   616   local c4 = 4 * c1 + 1      -- days of a full 4 year cycle
   617   local c100 = 25 * c4 - 1   -- days of a full 100 year cycle
   618   local c400 = 4 * c100 + 1  -- days of a full 400 year cycle
   619   local get_month_offset     -- function returning days elapsed within
   620                              -- the given year until the given month
   621                              -- (exclusive the given month)
   622   do
   623     local normal_month_offsets = {}
   624     local normal_month_lengths = {
   625       31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
   626     }
   627     local sum = 0
   628     for i = 1, 12 do
   629       normal_month_offsets[i] = sum
   630       sum = sum + normal_month_lengths[i]
   631     end
   632     function get_month_offset(year, month)
   633       if
   634         (((year % 4 == 0) and not (year % 100 == 0)) or (year % 400 == 0))
   635         and month > 2
   636       then
   637         return normal_month_offsets[month] + 1
   638       else
   639         return normal_month_offsets[month]
   640       end
   641     end
   642   end
   644   --[[--
   645   jd =                  -- days from January 1st 1970
   646   atom.date.ymd_to_jd(
   647     year,               -- year
   648     month,              -- month from 1 to 12
   649     day                 -- day from 1 to 31
   650   )
   652   This function calculates the days from January 1st 1970 for a given year, month and day.
   654   --]]--
   655   local offset = 0
   656   function date.ymd_to_jd(year, month, day)
   657     assert(is_integer(year), "Invalid year specified.")
   658     assert(is_integer(month), "Invalid month specified.")
   659     assert(is_integer(day), "Invalid day specified.")
   660     local calc_year = year - 1
   661     local n400 = math.floor(calc_year / 400)
   662     local r400 = calc_year % 400
   663     local n100 = math.floor(r400 / 100)
   664     local r100 = r400 % 100
   665     local n4 = math.floor(r100 / 4)
   666     local n1 = r100 % 4
   667     local jd = (
   668       c400 * n400 + c100 * n100 + c4 * n4 + c1 * n1 +
   669       get_month_offset(year, month) + (day - 1)
   670     )
   671     return jd - offset
   672   end
   673   offset = date.ymd_to_jd(1970, 1, 1)
   674   --//--
   676   --[[--
   677   year,                 -- year
   678   month,                -- month from 1 to 12
   679   day =                 -- day from 1 to 31
   680   atom.date.jd_to_ymd(
   681     jd,                 -- days from January 1st 1970
   682   )
   684   Given the days from January 1st 1970 this function returns year, month and day.
   686   --]]--
   687   function date.jd_to_ymd(jd)
   688     assert(is_integer(jd), "Invalid julian date specified.")
   689     local calc_jd = jd + offset
   690     assert(is_integer(calc_jd), "Julian date is out of range.")
   691     local n400 = math.floor(calc_jd / c400)
   692     local r400 = calc_jd % c400
   693     local n100 = math.floor(r400 / c100)
   694     local r100 = r400 % c100
   695     if n100 == 4 then n100, r100 = 3, c100 end
   696     local n4 = math.floor(r100 / c4)
   697     local r4 = r100 % c4
   698     local n1 = math.floor(r4 / c1)
   699     local r1 = r4 % c1
   700     if n1 == 4 then n1, r1 = 3, c1 end
   701     local year = 1 + 400 * n400 + 100 * n100 + 4 * n4 + n1
   702     local month = 1 + math.floor(r1 / 31)
   703     local month_offset = get_month_offset(year, month)
   704     if month < 12 then
   705       local next_month_offset = get_month_offset(year, month + 1)
   706       if r1 >= next_month_offset then
   707         month = month + 1
   708         month_offset = next_month_offset
   709       end
   710     end
   711     local day = 1 + r1 - month_offset
   712     return year, month, day
   713   end
   714   --//--
   715 end
   717 --[[--
   718 atom.date.invalid
   720 Value representing an invalid date.
   722 --]]--
   723 date.invalid = date:_create{
   724   jd = not_a_number,
   725   year = not_a_number, month = not_a_number, day = not_a_number,
   726   invalid = true
   727 }
   728 --//--
   730 --[[--
   731 d =                             -- date based on the given data
   732 atom.date:new{
   733   jd           = jd,            -- days since January 1st 1970
   734   year         = year,          -- year
   735   month        = month,         -- month from 1 to 12
   736   day          = day,           -- day from 1 to 31
   737   iso_weekyear = iso_weekyear,  -- year according to ISO 8601
   738   iso_week     = iso_week,      -- week number according to ISO 8601
   739   iso_weekday  = iso_weekday,   -- day of week from 1 for monday to 7 for sunday
   740   us_weekyear  = us_weekyear,   -- year
   741   us_week      = us_week,       -- week number according to US style counting
   742   us_weekday   = us_weekday     -- day of week from 1 for sunday to 7 for saturday
   743 }
   745 This method returns a new date value, based on given data.
   747 --]]--
   748 function date:new(args)
   749   local args = args
   750   if type(args) == "number" then args = { jd = args } end
   751   if type(args) == "table" then
   752     local year, month, day = args.year, args.month, args.day
   753     local jd = args.jd
   754     local iso_weekyear = args.iso_weekyear
   755     local iso_week     = args.iso_week
   756     local iso_weekday  = args.iso_weekday
   757     local us_week      = args.us_week
   758     local us_weekday   = args.us_weekday
   759     if
   760       type(year)  == "number" and
   761       type(month) == "number" and
   762       type(day)   == "number"
   763     then
   764       if
   765         is_integer(year)  and year >= 1  and year <= 9999 and
   766         is_integer(month) and month >= 1 and month <= 12  and
   767         is_integer(day)   and day >= 1   and day <= 31
   768       then
   769         return date:_create{
   770           jd = date.ymd_to_jd(year, month, day),
   771           year = year, month = month, day = day
   772         }
   773       else
   774         return date.invalid
   775       end
   776     elseif type(jd) == "number" then
   777       if is_integer(jd) and jd >= -719162 and jd <= 2932896 then
   778         local year, month, day = date.jd_to_ymd(jd)
   779         return date:_create{
   780           jd = jd, year = year, month = month, day = day
   781         }
   782       else
   783         return date.invalid
   784       end
   785     elseif
   786       type(year)        == "number" and not iso_weekyear and
   787       type(iso_week)    == "number" and
   788       type(iso_weekday) == "number"
   789     then
   790       if
   791         is_integer(year) and
   792         is_integer(iso_week)     and iso_week >= 0 and iso_week <= 53 and
   793         is_integer(iso_weekday)  and iso_weekday >= 1 and iso_weekday <= 7
   794       then
   795         local jan4 = date{ year = year, month = 1, day = 4 }
   796         local reference = jan4 - jan4.iso_weekday - 7  -- Sun. of week -1
   797         return date(reference + 7 * iso_week + iso_weekday)
   798       else
   799         return date.invalid
   800       end
   801     elseif
   802       type(iso_weekyear) == "number" and not year and
   803       type(iso_week)     == "number" and
   804       type(iso_weekday)  == "number"
   805     then
   806       if
   807         is_integer(iso_weekyear) and
   808         is_integer(iso_week)     and iso_week >= 0 and iso_week <= 53 and
   809         is_integer(iso_weekday)  and iso_weekday >= 1 and iso_weekday <= 7
   810       then
   811         local guessed = date{
   812           year        = iso_weekyear,
   813           iso_week    = iso_week,
   814           iso_weekday = iso_weekday
   815         }
   816         if guessed.invalid or guessed.iso_weekyear == iso_weekyear then
   817           return guessed
   818         else
   819           local year
   820           if iso_week <= 1 then
   821             year = iso_weekyear - 1
   822           elseif iso_week >= 52 then
   823             year = iso_weekyear + 1
   824           else
   825             error("Internal error in ISO week computation occured.")
   826           end
   827           return date{
   828             year = year, iso_week = iso_week, iso_weekday = iso_weekday
   829           }
   830         end
   831       else
   832         return date.invalid
   833       end
   834     elseif
   835       type(year) == "number" and
   836       type(us_week)     == "number" and
   837       type(us_weekday)  == "number"
   838     then
   839       if
   840         is_integer(year) and
   841         is_integer(us_week)     and us_week >= 0    and us_week <= 54   and
   842         is_integer(us_weekday)  and us_weekday >= 1 and us_weekday <= 7
   843       then
   844         local jan1 = date{ year = year, month = 1, day = 1 }
   845         local reference = jan1 - jan1.us_weekday - 7  -- Sat. of week -1
   846         return date(reference + 7 * us_week + us_weekday)
   847       else
   848         return date.invalid
   849       end
   850     end
   851   end
   852   error("Illegal arguments passed to date constructor.")
   853 end
   854 --//--
   856 --[[--
   857 atom.date:get_current()
   859 This function returns today's date.
   861 --]]--
   862 function date:get_current()
   863   local now = os.date("*t")
   864   return date{
   865     year = now.year, month = now.month, day = now.day
   866   }
   867 end
   868 --//--
   870 --[[--
   871 date =           -- date represented by the string
   872 atom.date:load(
   873   string         -- string representing a date
   874 )
   876 This method returns a date represented by the given string.
   878 --]]--
   879 function date:load(str)
   880   if str == "" then
   881     return nil
   882   else
   883     local year, month, day = string.match(
   884       str, "^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$"
   885     )
   886     if year then
   887       return date{
   888         year = tonumber(year),
   889         month = tonumber(month),
   890         day = tonumber(day)
   891       }
   892     else
   893       return date.invalid
   894     end
   895   end
   896 end
   897 --//--
   899 function date:__tostring()
   900   if self.invalid then
   901     return "invalid_date"
   902   else
   903     return string.format(
   904       "%04i-%02i-%02i", self.year, self.month, self.day
   905     )
   906   end
   907 end
   909 function date.getters:midnight()
   910   return time{ year = self.year, month = self.month, day = self.day }
   911 end
   913 function date.getters:midday()
   914   return time{
   915     year = self.year, month = self.month, day = self.day,
   916     hour = 12
   917   }
   918 end
   920 function date.getters:iso_weekday()  -- 1 = Monday
   921   return (self.jd + 3) % 7 + 1
   922 end
   924 function date.getters:us_weekday()  -- 1 = Sunday
   925   return (self.jd + 4) % 7 + 1
   926 end
   928 function date.getters:iso_weekyear()  -- ISO week-numbering year
   929   local year, month, day = self.year, self.month, self.day
   930   local iso_weekday      = self.iso_weekday
   931   if month == 1 then
   932     if
   933       (day == 3 and iso_weekday == 7) or
   934       (day == 2 and iso_weekday >= 6) or
   935       (day == 1 and iso_weekday >= 5)
   936     then
   937       return year - 1
   938     end
   939   elseif month == 12 then
   940     if
   941       (day == 29 and iso_weekday == 1) or
   942       (day == 30 and iso_weekday <= 2) or
   943       (day == 31 and iso_weekday <= 3)
   944     then
   945       return year + 1
   946     end
   947   end
   948   return year
   949 end
   951 function date.getters:iso_week()
   952   local jan4 = date{ year = self.iso_weekyear, month = 1, day = 4 }
   953   local reference = jan4.jd - jan4.iso_weekday - 6  -- monday of week 0
   954   return math.floor((self.jd - reference) / 7)
   955 end
   957 function date.getters:us_week()
   958   local jan1 = date{ year = self.year, month = 1, day = 1 }
   959   local reference = jan1.jd - jan1.us_weekday - 6  -- sunday of week 0
   960   return math.floor((self.jd - reference) / 7)
   961 end
   963 function date.__eq(value1, value2)
   964   if value1.invalid or value2.invalid then
   965     return false
   966   else
   967     return value1.jd == value2.jd
   968   end
   969 end
   971 function date.__lt(value1, value2)
   972   if value1.invalid or value2.invalid then
   973     return false
   974   else
   975     return value1.jd < value2.jd
   976   end
   977 end
   979 function date.__le(value1, value2)
   980   if value1.invalid or value2.invalid then
   981     return false
   982   else
   983     return value1.jd <= value2.jd
   984   end
   985 end
   987 function date.__add(value1, value2)
   988   if getmetatable(value1) == date then
   989     if getmetatable(value2) == date then
   990       error("Can not add two dates.")
   991     elseif type(value2) == "number" then
   992       return date(value1.jd + value2)
   993     else
   994       error("Right operand of '+' operator has wrong type.")
   995     end
   996   elseif type(value1) == "number" then
   997     if getmetatable(value2) == date then
   998       return date(value1 + value2.jd)
   999     else
  1000       error("Assertion failed")
  1001     end
  1002   else
  1003     error("Left operand of '+' operator has wrong type.")
  1004   end
  1005 end
  1007 function date.__sub(value1, value2)
  1008   if not getmetatable(value1) == date then
  1009     error("Left operand of '-' operator has wrong type.")
  1010   end
  1011   if getmetatable(value2) == date then
  1012     return value1.jd - value2.jd  -- TODO: transform to interval
  1013   elseif type(value2) == "number" then
  1014     return date(value1.jd - value2)
  1015   else
  1016     error("Right operand of '-' operator has wrong type.")
  1017   end
  1018 end
  1022 ---------------
  1023 -- timestamp --
  1024 ---------------
  1026 timestamp = create_new_type("timestamp")
  1028 --[[--
  1029 tsec =                          -- seconds since January 1st 1970 00:00
  1030 atom.timestamp.ymdhms_to_tsec(
  1031   year,                         -- year
  1032   month,                        -- month from 1 to 12
  1033   day,                          -- day from 1 to 31
  1034   hour,                         -- hour from 0 to 23
  1035   minute,                       -- minute from 0 to 59
  1036   second                        -- second from 0 to 59
  1037 )
  1039 Given the year, month, day, hour, minute and second, this function returns the number of seconds since January 1st 1970 00:00.
  1041 --]]--
  1042 function timestamp.ymdhms_to_tsec(year, month, day, hour, minute, second)
  1043   return
  1044     86400 * date.ymd_to_jd(year, month, day) +
  1045     3600 * hour + 60 * minute + second
  1046 end
  1047 --//--
  1049 --[[--
  1050 year,                      -- year
  1051 month,                     -- month from 1 to 12
  1052 day,                       -- day from 1 to 31
  1053 hour,                      -- hour from 0 to 23
  1054 minute,                    -- minute from 0 to 59
  1055 second =                   -- second from 0 to 59
  1056 atom.timestamp.tsec_to_ymdhms(
  1057   tsec                     -- seconds since January 1st 1970 00:00
  1058 )
  1060 Given the seconds since January 1st 1970 00:00, this function returns the year, month, day, hour, minute and second.
  1062 --]]--
  1063 function timestamp.tsec_to_ymdhms(tsec)
  1064   local jd   = math.floor(tsec / 86400)
  1065   local dsec = tsec % 86400
  1066   local year, month, day = date.jd_to_ymd(jd)
  1067   local hour   = math.floor(dsec / 3600)
  1068   local minute = math.floor((dsec % 3600) / 60)
  1069   local second = dsec % 60
  1070   return year, month, day, hour, minute, second
  1071 end
  1072 --//--
  1074 --[[--
  1075 timestamp.invalid
  1077 Value representing an invalid timestamp.
  1079 --]]--
  1080 timestamp.invalid = timestamp:_create{
  1081   tsec = not_a_number,
  1082   year = not_a_number, month = not_a_number, day = not_a_number,
  1083   hour = not_a_number, minute = not_a_number, second = not_a_number,
  1084   invalid = true
  1085 }
  1086 --//--
  1088 --[[--
  1089 ts =                 -- timestamp based on given data
  1090 atom.timestamp:new{
  1091   tsec   = tsec,     -- seconds since January 1st 1970 00:00
  1092   year   = year,     -- year
  1093   month  = month,    -- month from 1 to 12
  1094   day    = day,      -- day from 1 to 31
  1095   hour   = hour,     -- hour from 0 to 23
  1096   minute = minute,   -- minute from 0 to 59
  1097   second = second    -- second from 0 to 59
  1098 }
  1100 This method returns a new timestamp value, based on given data.
  1102 --]]--
  1103 function timestamp:new(args)
  1104   local args = args
  1105   if type(args) == "number" then args = { tsec = args } end
  1106   if type(args) == "table" then
  1107     if not args.second then
  1108       args.second = 0
  1109       if not args.minute then
  1110         args.minute = 0
  1111         if not args.hour then
  1112           args.hour = 0
  1113         end
  1114       end
  1115     end
  1116     if
  1117       type(args.year)   == "number" and
  1118       type(args.month)  == "number" and
  1119       type(args.day)    == "number" and
  1120       type(args.hour)   == "number" and
  1121       type(args.minute) == "number" and
  1122       type(args.second) == "number"
  1123     then
  1124       if
  1125         is_integer(args.year) and
  1126         args.year >= 1 and args.year <= 9999 and
  1127         is_integer(args.month) and
  1128         args.month >= 1 and args.month <= 12 and
  1129         is_integer(args.day) and
  1130         args.day >= 1 and args.day <= 31 and
  1131         is_integer(args.hour) and
  1132         args.hour >= 0 and args.hour <= 23 and
  1133         is_integer(args.minute) and
  1134         args.minute >= 0 and args.minute <= 59 and
  1135         is_integer(args.second) and
  1136         args.second >= 0 and args.second <= 59
  1137       then
  1138         return timestamp:_create{
  1139           tsec = timestamp.ymdhms_to_tsec(
  1140             args.year, args.month, args.day,
  1141             args.hour, args.minute, args.second
  1142           ),
  1143           year   = args.year,
  1144           month  = args.month,
  1145           day    = args.day,
  1146           hour   = args.hour,
  1147           minute = args.minute,
  1148           second = args.second
  1149         }
  1150       else
  1151         return timestamp.invalid
  1152       end
  1153     elseif type(args.tsec) == "number" then
  1154       if
  1155         is_integer(args.tsec) and
  1156         args.tsec >= -62135596800 and args.tsec <= 253402300799
  1157       then
  1158         local year, month, day, hour, minute, second =
  1159           timestamp.tsec_to_ymdhms(args.tsec)
  1160         return timestamp:_create{
  1161           tsec = args.tsec,
  1162           year = year, month = month, day = day,
  1163           hour = hour, minute = minute, second = second
  1164         }
  1165       else
  1166         return timestamp.invalid
  1167       end
  1168     end
  1169   end
  1170   error("Invalid arguments passed to timestamp constructor.")
  1171 end
  1172 --//--
  1174 --[[--
  1175 ts =                          -- current date/time as timestamp
  1176 atom.timestamp:get_current()
  1178 This function returns the current date and time as timestamp.
  1180 --]]--
  1181 function timestamp:get_current()
  1182   local now = os.date("*t")
  1183   return timestamp{
  1184     year = now.year, month = now.month, day = now.day,
  1185     hour = now.hour, minute = now.min, second = now.sec
  1186   }
  1187 end
  1188 --//--
  1190 --[[--
  1191 ts =             -- timestamp represented by the string
  1192 atom.timestamp:load(
  1193   string         -- string representing a timestamp
  1194 )
  1196 This method returns a timestamp represented by the given string.
  1198 --]]--
  1199 function timestamp:load(str)
  1200   if str == "" then
  1201     return nil
  1202   else
  1203     local year, month, day, hour, minute, second = string.match(
  1204       str,
  1205       "^([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])$"
  1206     )
  1207     if year then
  1208       return timestamp{
  1209         year   = tonumber(year),
  1210         month  = tonumber(month),
  1211         day    = tonumber(day),
  1212         hour   = tonumber(hour),
  1213         minute = tonumber(minute),
  1214         second = tonumber(second)
  1215       }
  1216     else
  1217       return timestamp.invalid
  1218     end
  1219   end
  1220 end
  1222 function timestamp:__tostring()
  1223   if self.invalid then
  1224     return "invalid_timestamp"
  1225   else
  1226     return string.format(
  1227       "%04i-%02i-%02i %02i:%02i:%02i",
  1228       self.year, self.month, self.day, self.hour, self.minute, self.second
  1229     )
  1230   end
  1231 end
  1233 function timestamp.getters:date()
  1234   return date{ year = self.year, month = self.month, day = self.day }
  1235 end
  1237 function timestamp.getters:time()
  1238   return time{
  1239     hour = self.hour,
  1240     minute = self.minute,
  1241     second = self.second
  1242   }
  1243 end
  1245 function timestamp.__eq(value1, value2)
  1246   if value1.invalid or value2.invalid then
  1247     return false
  1248   else
  1249     return value1.tsec == value2.tsec
  1250   end
  1251 end
  1253 function timestamp.__lt(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.__le(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.__add(value1, value2)
  1270   if getmetatable(value1) == timestamp then
  1271     if getmetatable(value2) == timestamp then
  1272       error("Can not add two timestamps.")
  1273     elseif type(value2) == "number" then
  1274       return timestamp(value1.tsec + value2)
  1275     else
  1276       error("Right operand of '+' operator has wrong type.")
  1277     end
  1278   elseif type(value1) == "number" then
  1279     if getmetatable(value2) == timestamp then
  1280       return timestamp(value1 + value2.tsec)
  1281     else
  1282       error("Assertion failed")
  1283     end
  1284   else
  1285     error("Left operand of '+' operator has wrong type.")
  1286   end
  1287 end
  1289 function timestamp.__sub(value1, value2)
  1290   if not getmetatable(value1) == timestamp then
  1291     error("Left operand of '-' operator has wrong type.")
  1292   end
  1293   if getmetatable(value2) == timestamp then
  1294     return value1.tsec - value2.tsec  -- TODO: transform to interval
  1295   elseif type(value2) == "number" then
  1296     return timestamp(value1.tsec - value2)
  1297   else
  1298     error("Right operand of '-' operator has wrong type.")
  1299   end
  1300 end
  1304 ----------
  1305 -- time --
  1306 ----------
  1308 time = create_new_type("time")
  1310 function time.hms_to_dsec(hour, minute, second)
  1311   return 3600 * hour + 60 * minute + second
  1312 end
  1314 function time.dsec_to_hms(dsec)
  1315   local hour   = math.floor(dsec / 3600)
  1316   local minute = math.floor((dsec % 3600) / 60)
  1317   local second = dsec % 60
  1318   return hour, minute, second
  1319 end
  1321 --[[--
  1322 atom.time.invalid
  1324 Value representing an invalid time of day.
  1326 --]]--
  1327 time.invalid = time:_create{
  1328   dsec = not_a_number,
  1329   hour = not_a_number, minute = not_a_number, second = not_a_number,
  1330   invalid = true
  1331 }
  1332 --//--
  1334 --[[--
  1335 t =                 -- time based on given data
  1336 atom.time:new{
  1337   dsec = dsec,      -- seconds since 00:00:00
  1338   hour = hour,      -- hour from 0 to 23
  1339   minute = minute,  -- minute from 0 to 59
  1340   second = second   -- second from 0 to 59
  1341 }
  1343 This method returns a new time value, based on given data.
  1345 --]]--
  1346 function time:new(args)
  1347   local args = args
  1348   if type(args) == "number" then args = { dsec = args } end
  1349   if type(args) == "table" then
  1350     if not args.second then
  1351       args.second = 0
  1352       if not args.minute then
  1353         args.minute = 0
  1354       end
  1355     end
  1356     if
  1357       type(args.hour)   == "number" and
  1358       type(args.minute) == "number" and
  1359       type(args.second) == "number"
  1360     then
  1361       if
  1362         is_integer(args.hour) and
  1363         args.hour >= 0 and args.hour <= 23 and
  1364         is_integer(args.minute) and
  1365         args.minute >= 0 and args.minute <= 59 and
  1366         is_integer(args.second) and
  1367         args.second >= 0 and args.second <= 59
  1368       then
  1369         return time:_create{
  1370           dsec = time.hms_to_dsec(args.hour, args.minute, args.second),
  1371           hour   = args.hour,
  1372           minute = args.minute,
  1373           second = args.second
  1374         }
  1375       else
  1376         return time.invalid
  1377       end
  1378     elseif type(args.dsec) == "number" then
  1379       if
  1380         is_integer(args.dsec) and
  1381         args.dsec >= 0 and args.dsec <= 86399
  1382       then
  1383         local hour, minute, second =
  1384           time.dsec_to_hms(args.dsec)
  1385         return time:_create{
  1386           dsec = args.dsec,
  1387           hour = hour, minute = minute, second = second
  1388         }
  1389       else
  1390         return time.invalid
  1391       end
  1392     end
  1393   end
  1394   error("Invalid arguments passed to time constructor.")
  1395 end
  1396 --//--
  1398 --[[--
  1399 t =                      -- current time of day
  1400 atom.time:get_current()
  1402 This method returns the current time of the day.
  1404 --]]--
  1405 function time:get_current()
  1406   local now = os.date("*t")
  1407   return time{ hour = now.hour, minute = now.min, second = now.sec }
  1408 end
  1409 --//--
  1411 --[[--
  1412 t =              -- time represented by the string
  1413 atom.time:load(
  1414   string         -- string representing a time of day
  1415 )
  1417 This method returns a time represented by the given string.
  1419 --]]--
  1420 function time:load(str)
  1421   if str == "" then
  1422     return nil
  1423   else
  1424     local hour, minute, second = string.match(
  1425       str,
  1426       "^([0-9][0-9]):([0-9][0-9]):([0-9][0-9])$"
  1427     )
  1428     if hour then
  1429       return time{
  1430         hour   = tonumber(hour),
  1431         minute = tonumber(minute),
  1432         second = tonumber(second)
  1433       }
  1434     else
  1435       return time.invalid
  1436     end
  1437   end
  1438 end
  1439 --//--
  1441 function time:__tostring()
  1442   if self.invalid then
  1443     return "invalid_time"
  1444   else
  1445     return string.format(
  1446       "%02i:%02i:%02i",
  1447       self.hour, self.minute, self.second
  1448     )
  1449   end
  1450 end
  1452 function time.__eq(value1, value2)
  1453   if value1.invalid or value2.invalid then
  1454     return false
  1455   else
  1456     return value1.dsec == value2.dsec
  1457   end
  1458 end
  1460 function time.__lt(value1, value2)
  1461   if value1.invalid or value2.invalid then
  1462     return false
  1463   else
  1464     return value1.dsec < value2.dsec
  1465   end
  1466 end
  1468 function time.__le(value1, value2)
  1469   if value1.invalid or value2.invalid then
  1470     return false
  1471   else
  1472     return value1.dsec <= value2.dsec
  1473   end
  1474 end
  1476 function time.__add(value1, value2)
  1477   if getmetatable(value1) == time then
  1478     if getmetatable(value2) == time then
  1479       error("Can not add two times.")
  1480     elseif type(value2) == "number" then
  1481       return time((value1.dsec + value2) % 86400)
  1482     else
  1483       error("Right operand of '+' operator has wrong type.")
  1484     end
  1485   elseif type(value1) == "number" then
  1486     if getmetatable(value2) == time then
  1487       return time((value1 + value2.dsec) % 86400)
  1488     else
  1489       error("Assertion failed")
  1490     end
  1491   else
  1492     error("Left operand of '+' operator has wrong type.")
  1493   end
  1494 end
  1496 function time.__sub(value1, value2)
  1497   if not getmetatable(value1) == time then
  1498     error("Left operand of '-' operator has wrong type.")
  1499   end
  1500   if getmetatable(value2) == time then
  1501     return value1.dsec - value2.dsec  -- TODO: transform to interval
  1502   elseif type(value2) == "number" then
  1503     return time((value1.dsec - value2) % 86400)
  1504   else
  1505     error("Right operand of '-' operator has wrong type.")
  1506   end
  1507 end
  1509 function time.__concat(value1, value2)
  1510   local mt1, mt2 = getmetatable(value1), getmetatable(value2)
  1511   if mt1 == date and mt2 == time then
  1512     return timestamp{
  1513       year = value1.year, month = value1.month, day = value1.day,
  1514       hour = value2.hour, minute = value2.minute, second = value2.second
  1515     }
  1516   elseif mt1 == time and mt2 == date then
  1517     return timestamp{
  1518       year = value2.year, month = value2.month, day = value2.day,
  1519       hour = value1.hour, minute = value1.minute, second = value1.second
  1520     }
  1521   elseif mt1 == time then
  1522     error("Right operand of '..' operator has wrong type.")
  1523   elseif mt2 == time then
  1524     error("Left operand of '..' operator has wrong type.")
  1525   else
  1526     error("Assertion failed")
  1527   end
  1528 end
