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
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
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
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
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
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
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
1187 end
1188 --//--
1190 --[[--
1191 ts = -- timestamp represented by the string
1192 atom.timestamp:load(
1193 string -- string representing a timestamp
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])$"
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)
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
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
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
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
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
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
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
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])$"
1428 if hour then
1429 return time{
1430 hour = tonumber(hour),
1431 minute = tonumber(minute),
1432 second = tonumber(second)
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
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
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
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

Impressum / About Us