webmcp

view libraries/atom/atom.lua @ 64:3d43a5cf17c1

Compatibility with Lua 5.2
author jbe
date Sun Apr 15 16:04:33 2012 +0200 (2012-04-15)
parents 985024b16520
children 24ed2cd053aa
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 string = string
24 local table = table
26 local _M = {}
27 if _ENV then
28 _ENV = _M
29 else
30 _G[...] = _M
31 setfenv(1, _M)
32 end
36 ---------------------------------------
37 -- general functions and definitions --
38 ---------------------------------------
40 --[[--
41 bool = -- true, if value is an integer within resolution
42 atom.is_integer(
43 value -- value to be tested
44 )
46 This function returns true if the given object is an integer within resolution.
48 --]]--
49 function is_integer(i)
50 return
51 type(i) == "number" and i % 1 == 0 and
52 (i + 1) - i == 1 and i - (i - 1) == 1
53 end
54 --//--
56 --[[--
57 atom.not_a_number
59 Value representing an invalid numeric result. Used for atom.integer.invalid and atom.number.invalid.
61 --]]--
62 not_a_number = 0 / 0
63 --//--
65 do
67 local shadow = setmetatable({}, { __mode = "k" })
69 local type_mt = { __index = {} }
71 function type_mt:__call(...)
72 return self:new(...)
73 end
75 function type_mt.__index:_create(data)
76 local value = setmetatable({}, self)
77 shadow[value] = data
78 return value
79 end
81 local function write_prohibited()
82 error("Modification of an atom is prohibited.")
83 end
85 -- returns a new type as a table, which serves also as metatable
86 function create_new_type(name)
87 local t = setmetatable(
88 { methods = {}, getters = {}, name = name },
89 type_mt
90 )
91 function t.__index(self, key)
92 local data = shadow[self]
93 local value = data[key]
94 if value ~= nil then return value end
95 local method = t.methods[key]
96 if method then return method end
97 local getter = t.getters[key]
98 if getter then return getter(self) end
99 end
100 t.__newindex = write_prohibited
101 return t
102 end
104 --[[--
105 bool = -- true, if 'value' is of type 't'
106 atom.has_type(
107 value, -- any value
108 t -- atom time, e.g. atom.date, or lua type, e.g. "string"
109 )
111 This function checks, if a value is of a given type. The value may be an invalid value though, e.g. atom.date.invalid.
113 --]]--
114 function has_type(value, t)
115 if t == nil then error("No type passed to has_type(...) function.") end
116 local lua_type = type(value)
117 return
118 lua_type == t or
119 getmetatable(value) == t or
120 (lua_type == "boolean" and t == _M.boolean) or
121 (lua_type == "string" and t == _M.string) or (
122 lua_type == "number" and
123 (t == _M.number or (
124 t == _M.integer and (
125 not (value <= 0 or value >= 0) or (
126 value % 1 == 0 and
127 (value + 1) - value == 1 and
128 value - (value - 1) == 1
129 )
130 )
131 ))
132 )
133 end
134 --//--
136 --[[--
137 bool = -- true, if 'value' is of type 't'
138 atom.is_valid(
139 value, -- any value
140 t -- atom time, e.g. atom.date, or lua type, e.g. "string"
141 )
143 This function checks, if a value is valid. It optionally checks, if the value is of a given type.
145 --]]--
146 function is_valid(value, t)
147 local lua_type = type(value)
148 if lua_type == "table" then
149 local mt = getmetatable(value)
150 if t then
151 return t == mt and not value.invalid
152 else
153 return (getmetatable(mt) == type_mt) and not value.invalid
154 end
155 elseif lua_type == "boolean" then
156 return not t or t == "boolean" or t == _M.boolean
157 elseif lua_type == "string" then
158 return not t or t == "string" or t == _M.string
159 elseif lua_type == "number" then
160 if t == _M.integer then
161 return
162 value % 1 == 0 and
163 (value + 1) - value == 1 and
164 value - (value - 1) == 1
165 else
166 return
167 (not t or t == "number" or t == _M.number) and
168 (value <= 0 or value >= 0)
169 end
170 else
171 return false
172 end
173 end
174 --//--
176 end
178 --[[--
179 string = -- string representation to be passed to a load function
180 atom.dump(
181 value -- value to be dumped
182 )
184 This function returns a string representation of the given value.
186 --]]--
187 function dump(obj)
188 if obj == nil then
189 return ""
190 else
191 return tostring(obj)
192 end
193 end
194 --//--
198 -------------
199 -- boolean --
200 -------------
202 boolean = { name = "boolean" }
204 --[[--
205 bool = -- true, false, or nil
206 atom.boolean:load(
207 string -- string to be interpreted as boolean
208 )
210 This method returns true or false or nil, depending on the input string.
212 --]]--
213 function boolean:load(str)
214 if str == nil or str == "" then
215 return nil
216 elseif type(str) ~= "string" then
217 error("String expected")
218 elseif string.find(str, "^[TtYy1]") then
219 return true
220 elseif string.find(str, "^[FfNn0]") then
221 return false
222 else
223 return nil -- we don't have an undefined bool
224 end
225 end
226 --//--
230 ------------
231 -- string --
232 ------------
234 _M.string = { name = "string" }
236 --[[--
237 string = -- the same string
238 atom.string:load(
239 string -- a string
240 )
242 This method returns the passed string, or throws an error, if the passed argument is not a string.
244 --]]--
245 function _M.string:load(str)
246 if str == nil then
247 return nil
248 elseif type(str) ~= "string" then
249 error("String expected")
250 else
251 return str
252 end
253 end
254 --//--
258 -------------
259 -- integer --
260 -------------
262 integer = { name = "integer" }
264 --[[--
265 int = -- an integer or atom.integer.invalid (atom.not_a_number)
266 atom.integer:load(
267 string -- a string representing an integer
268 )
270 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.
272 --]]--
273 function integer:load(str)
274 if str == nil or str == "" then
275 return nil
276 elseif type(str) ~= "string" then
277 error("String expected")
278 else
279 local num = tonumber(str)
280 if is_integer(num) then return num else return not_a_number end
281 end
282 end
283 --//--
285 --[[--
286 atom.integer.invalid
288 This represents an invalid integer.
290 --]]--
291 integer.invalid = not_a_number
292 --//
296 ------------
297 -- number --
298 ------------
300 number = create_new_type("number")
302 --[[--
303 int = -- a number or atom.number.invalid (atom.not_a_number)
304 atom.number:load(
305 string -- a string representing a number
306 )
308 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.
310 --]]--
311 function number:load(str)
312 if str == nil or str == "" then
313 return nil
314 elseif type(str) ~= "string" then
315 error("String expected")
316 else
317 return tonumber(str) or not_a_number
318 end
319 end
320 --//--
322 --[[--
323 atom.number.invalid
325 This represents an invalid number.
327 --]]--
328 number.invalid = not_a_number
329 --//--
333 --------------
334 -- fraction --
335 --------------
337 fraction = create_new_type("fraction")
339 --[[--
340 i = -- the greatest common divisor (GCD) of all given natural numbers
341 atom.gcd(
342 a, -- a natural number
343 b, -- another natural number
344 ... -- optionally more natural numbers
345 )
347 This function returns the greatest common divisor (GCD) of two or more natural numbers.
349 --]]--
350 function gcd(a, b, ...)
351 if a % 1 ~= 0 or a <= 0 then return 0 / 0 end
352 if b == nil then
353 return a
354 else
355 if b % 1 ~= 0 or b <= 0 then return 0 / 0 end
356 if ... == nil then
357 local k = 0
358 local t
359 while a % 2 == 0 and b % 2 == 0 do
360 a = a / 2; b = b / 2; k = k + 1
361 end
362 if a % 2 == 0 then t = a else t = -b end
363 while t ~= 0 do
364 while t % 2 == 0 do t = t / 2 end
365 if t > 0 then a = t else b = -t end
366 t = a - b
367 end
368 return a * 2 ^ k
369 else
370 return gcd(gcd(a, b), ...)
371 end
372 end
373 end
374 --//--
376 --[[--
377 i = --the least common multiple (LCD) of all given natural numbers
378 atom.lcm(
379 a, -- a natural number
380 b, -- another natural number
381 ... -- optionally more natural numbers
382 )
384 This function returns the least common multiple (LCD) of two or more natural numbers.
386 --]]--
387 function lcm(a, b, ...)
388 if a % 1 ~= 0 or a <= 0 then return 0 / 0 end
389 if b == nil then
390 return a
391 else
392 if b % 1 ~= 0 or b <= 0 then return 0 / 0 end
393 if ... == nil then
394 return a * b / gcd(a, b)
395 else
396 return lcm(lcm(a, b), ...)
397 end
398 end
399 end
400 --//--
402 --[[--
403 atom.fraction.invalid
405 Value representing an invalid fraction.
407 --]]--
408 fraction.invalid = fraction:_create{
409 numerator = not_a_number, denominator = not_a_number, invalid = true
410 }
411 --//--
413 --[[--
414 frac = -- fraction
415 atom.fraction:new(
416 numerator, -- numerator
417 denominator -- denominator
418 )
420 This method creates a new fraction.
422 --]]--
423 function fraction:new(numerator, denominator)
424 if not (
425 (numerator == nil or type(numerator) == "number") and
426 (denominator == nil or type(denominator) == "number")
427 ) then
428 error("Invalid arguments passed to fraction constructor.")
429 elseif
430 (not is_integer(numerator)) or
431 (denominator and (not is_integer(denominator)))
432 then
433 return fraction.invalid
434 elseif denominator then
435 if denominator == 0 then
436 return fraction.invalid
437 elseif numerator == 0 then
438 return fraction:_create{ numerator = 0, denominator = 1, float = 0 }
439 else
440 local d = gcd(math.abs(numerator), math.abs(denominator))
441 if denominator < 0 then d = -d end
442 local numerator2, denominator2 = numerator / d, denominator / d
443 return fraction:_create{
444 numerator = numerator2,
445 denominator = denominator2,
446 float = numerator2 / denominator2
447 }
448 end
449 else
450 return fraction:_create{
451 numerator = numerator, denominator = 1, float = numerator
452 }
453 end
454 end
455 --//--
457 --[[--
458 frac = -- fraction represented by the given string
459 atom.fraction:load(
460 string -- string representation of a fraction
461 )
463 This method returns a fraction represented by the given string.
465 --]]--
466 function fraction:load(str)
467 if str == nil or str == "" then
468 return nil
469 elseif type(str) ~= "string" then
470 error("String expected")
471 else
472 local sign, int = string.match(str, "^(%-?)([0-9]+)$")
473 if sign == "" then return fraction:new(tonumber(int))
474 elseif sign == "-" then return fraction:new(- tonumber(int))
475 end
476 local sign, n, d = string.match(str, "^(%-?)([0-9]+)/([0-9]+)$")
477 if sign == "" then return fraction:new(tonumber(n), tonumber(d))
478 elseif sign == "-" then return fraction:new(- tonumber(n), tonumber(d))
479 end
480 return fraction.invalid
481 end
482 end
483 --//--
485 function fraction:__tostring()
486 if self.invalid then
487 return "not_a_fraction"
488 else
489 return self.numerator .. "/" .. self.denominator
490 end
491 end
493 function fraction.__eq(value1, value2)
494 if value1.invalid or value2.invalid then
495 return false
496 else
497 return
498 value1.numerator == value2.numerator and
499 value1.denominator == value2.denominator
500 end
501 end
503 function fraction.__lt(value1, value2)
504 if value1.invalid or value2.invalid then
505 return false
506 else
507 return value1.float < value2.float
508 end
509 end
511 function fraction.__le(value1, value2)
512 if value1.invalid or value2.invalid then
513 return false
514 else
515 return value1.float <= value2.float
516 end
517 end
519 function fraction:__unm()
520 return fraction(-self.numerator, self.denominator)
521 end
523 do
525 local function extract(value1, value2)
526 local n1, d1, n2, d2
527 if getmetatable(value1) == fraction then
528 n1 = value1.numerator
529 d1 = value1.denominator
530 elseif type(value1) == "number" then
531 n1 = value1
532 d1 = 1
533 else
534 error("Left operand of operator has wrong type.")
535 end
536 if getmetatable(value2) == fraction then
537 n2 = value2.numerator
538 d2 = value2.denominator
539 elseif type(value2) == "number" then
540 n2 = value2
541 d2 = 1
542 else
543 error("Right operand of operator has wrong type.")
544 end
545 return n1, d1, n2, d2
546 end
548 function fraction.__add(value1, value2)
549 local n1, d1, n2, d2 = extract(value1, value2)
550 return fraction(n1 * d2 + n2 * d1, d1 * d2)
551 end
553 function fraction.__sub(value1, value2)
554 local n1, d1, n2, d2 = extract(value1, value2)
555 return fraction(n1 * d2 - n2 * d1, d1 * d2)
556 end
558 function fraction.__mul(value1, value2)
559 local n1, d1, n2, d2 = extract(value1, value2)
560 return fraction(n1 * n2, d1 * d2)
561 end
563 function fraction.__div(value1, value2)
564 local n1, d1, n2, d2 = extract(value1, value2)
565 return fraction(n1 * d2, d1 * n2)
566 end
568 function fraction.__pow(value1, value2)
569 local n1, d1, n2, d2 = extract(value1, value2)
570 local n1_abs = math.abs(n1)
571 local d1_abs = math.abs(d1)
572 local n2_abs = math.abs(n2)
573 local d2_abs = math.abs(d2)
574 local numerator, denominator
575 if d2_abs == 1 then
576 numerator = n1_abs
577 denominator = d1_abs
578 else
579 numerator = 0
580 while true do
581 local t = numerator ^ d2_abs
582 if t == n1_abs then break end
583 if not (t < n1_abs) then return value1.float / value2.float end
584 numerator = numerator + 1
585 end
586 denominator = 1
587 while true do
588 local t = denominator ^ d2_abs
589 if t == d1_abs then break end
590 if not (t < d1_abs) then return value1.float / value2.float end
591 denominator = denominator + 1
592 end
593 end
594 if n1 < 0 then
595 if d2_abs % 2 == 1 then
596 numerator = -numerator
597 else
598 return fraction.invalid
599 end
600 end
601 if n2 < 0 then
602 numerator, denominator = denominator, numerator
603 end
604 return fraction(numerator ^ n2_abs, denominator ^ n2_abs)
605 end
607 end
611 ----------
612 -- date --
613 ----------
615 date = create_new_type("date")
617 do
618 local c1 = 365 -- days of a non-leap year
619 local c4 = 4 * c1 + 1 -- days of a full 4 year cycle
620 local c100 = 25 * c4 - 1 -- days of a full 100 year cycle
621 local c400 = 4 * c100 + 1 -- days of a full 400 year cycle
622 local get_month_offset -- function returning days elapsed within
623 -- the given year until the given month
624 -- (exclusive the given month)
625 do
626 local normal_month_offsets = {}
627 local normal_month_lengths = {
628 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
629 }
630 local sum = 0
631 for i = 1, 12 do
632 normal_month_offsets[i] = sum
633 sum = sum + normal_month_lengths[i]
634 end
635 function get_month_offset(year, month)
636 if
637 (((year % 4 == 0) and not (year % 100 == 0)) or (year % 400 == 0))
638 and month > 2
639 then
640 return normal_month_offsets[month] + 1
641 else
642 return normal_month_offsets[month]
643 end
644 end
645 end
647 --[[--
648 jd = -- days from January 1st 1970
649 atom.date.ymd_to_jd(
650 year, -- year
651 month, -- month from 1 to 12
652 day -- day from 1 to 31
653 )
655 This function calculates the days from January 1st 1970 for a given year, month and day.
657 --]]--
658 local offset = 0
659 function date.ymd_to_jd(year, month, day)
660 assert(is_integer(year), "Invalid year specified.")
661 assert(is_integer(month), "Invalid month specified.")
662 assert(is_integer(day), "Invalid day specified.")
663 local calc_year = year - 1
664 local n400 = math.floor(calc_year / 400)
665 local r400 = calc_year % 400
666 local n100 = math.floor(r400 / 100)
667 local r100 = r400 % 100
668 local n4 = math.floor(r100 / 4)
669 local n1 = r100 % 4
670 local jd = (
671 c400 * n400 + c100 * n100 + c4 * n4 + c1 * n1 +
672 get_month_offset(year, month) + (day - 1)
673 )
674 return jd - offset
675 end
676 offset = date.ymd_to_jd(1970, 1, 1)
677 --//--
679 --[[--
680 year, -- year
681 month, -- month from 1 to 12
682 day = -- day from 1 to 31
683 atom.date.jd_to_ymd(
684 jd, -- days from January 1st 1970
685 )
687 Given the days from January 1st 1970 this function returns year, month and day.
689 --]]--
690 function date.jd_to_ymd(jd)
691 assert(is_integer(jd), "Invalid julian date specified.")
692 local calc_jd = jd + offset
693 assert(is_integer(calc_jd), "Julian date is out of range.")
694 local n400 = math.floor(calc_jd / c400)
695 local r400 = calc_jd % c400
696 local n100 = math.floor(r400 / c100)
697 local r100 = r400 % c100
698 if n100 == 4 then n100, r100 = 3, c100 end
699 local n4 = math.floor(r100 / c4)
700 local r4 = r100 % c4
701 local n1 = math.floor(r4 / c1)
702 local r1 = r4 % c1
703 if n1 == 4 then n1, r1 = 3, c1 end
704 local year = 1 + 400 * n400 + 100 * n100 + 4 * n4 + n1
705 local month = 1 + math.floor(r1 / 31)
706 local month_offset = get_month_offset(year, month)
707 if month < 12 then
708 local next_month_offset = get_month_offset(year, month + 1)
709 if r1 >= next_month_offset then
710 month = month + 1
711 month_offset = next_month_offset
712 end
713 end
714 local day = 1 + r1 - month_offset
715 return year, month, day
716 end
717 --//--
718 end
720 --[[--
721 atom.date.invalid
723 Value representing an invalid date.
725 --]]--
726 date.invalid = date:_create{
727 jd = not_a_number,
728 year = not_a_number, month = not_a_number, day = not_a_number,
729 invalid = true
730 }
731 --//--
733 --[[--
734 d = -- date based on the given data
735 atom.date:new{
736 jd = jd, -- days since January 1st 1970
737 year = year, -- year
738 month = month, -- month from 1 to 12
739 day = day, -- day from 1 to 31
740 iso_weekyear = iso_weekyear, -- year according to ISO 8601
741 iso_week = iso_week, -- week number according to ISO 8601
742 iso_weekday = iso_weekday, -- day of week from 1 for monday to 7 for sunday
743 us_weekyear = us_weekyear, -- year
744 us_week = us_week, -- week number according to US style counting
745 us_weekday = us_weekday -- day of week from 1 for sunday to 7 for saturday
746 }
748 This method returns a new date value, based on given data.
750 --]]--
751 function date:new(args)
752 local args = args
753 if type(args) == "number" then args = { jd = args } end
754 if type(args) == "table" then
755 local year, month, day = args.year, args.month, args.day
756 local jd = args.jd
757 local iso_weekyear = args.iso_weekyear
758 local iso_week = args.iso_week
759 local iso_weekday = args.iso_weekday
760 local us_week = args.us_week
761 local us_weekday = args.us_weekday
762 if
763 type(year) == "number" and
764 type(month) == "number" and
765 type(day) == "number"
766 then
767 if
768 is_integer(year) and year >= 1 and year <= 9999 and
769 is_integer(month) and month >= 1 and month <= 12 and
770 is_integer(day) and day >= 1 and day <= 31
771 then
772 return date:_create{
773 jd = date.ymd_to_jd(year, month, day),
774 year = year, month = month, day = day
775 }
776 else
777 return date.invalid
778 end
779 elseif type(jd) == "number" then
780 if is_integer(jd) and jd >= -719162 and jd <= 2932896 then
781 local year, month, day = date.jd_to_ymd(jd)
782 return date:_create{
783 jd = jd, year = year, month = month, day = day
784 }
785 else
786 return date.invalid
787 end
788 elseif
789 type(year) == "number" and not iso_weekyear and
790 type(iso_week) == "number" and
791 type(iso_weekday) == "number"
792 then
793 if
794 is_integer(year) and
795 is_integer(iso_week) and iso_week >= 0 and iso_week <= 53 and
796 is_integer(iso_weekday) and iso_weekday >= 1 and iso_weekday <= 7
797 then
798 local jan4 = date{ year = year, month = 1, day = 4 }
799 local reference = jan4 - jan4.iso_weekday - 7 -- Sun. of week -1
800 return date(reference + 7 * iso_week + iso_weekday)
801 else
802 return date.invalid
803 end
804 elseif
805 type(iso_weekyear) == "number" and not year and
806 type(iso_week) == "number" and
807 type(iso_weekday) == "number"
808 then
809 if
810 is_integer(iso_weekyear) and
811 is_integer(iso_week) and iso_week >= 0 and iso_week <= 53 and
812 is_integer(iso_weekday) and iso_weekday >= 1 and iso_weekday <= 7
813 then
814 local guessed = date{
815 year = iso_weekyear,
816 iso_week = iso_week,
817 iso_weekday = iso_weekday
818 }
819 if guessed.invalid or guessed.iso_weekyear == iso_weekyear then
820 return guessed
821 else
822 local year
823 if iso_week <= 1 then
824 year = iso_weekyear - 1
825 elseif iso_week >= 52 then
826 year = iso_weekyear + 1
827 else
828 error("Internal error in ISO week computation occured.")
829 end
830 return date{
831 year = year, iso_week = iso_week, iso_weekday = iso_weekday
832 }
833 end
834 else
835 return date.invalid
836 end
837 elseif
838 type(year) == "number" and
839 type(us_week) == "number" and
840 type(us_weekday) == "number"
841 then
842 if
843 is_integer(year) and
844 is_integer(us_week) and us_week >= 0 and us_week <= 54 and
845 is_integer(us_weekday) and us_weekday >= 1 and us_weekday <= 7
846 then
847 local jan1 = date{ year = year, month = 1, day = 1 }
848 local reference = jan1 - jan1.us_weekday - 7 -- Sat. of week -1
849 return date(reference + 7 * us_week + us_weekday)
850 else
851 return date.invalid
852 end
853 end
854 end
855 error("Illegal arguments passed to date constructor.")
856 end
857 --//--
859 --[[--
860 atom.date:get_current()
862 This function returns today's date.
864 --]]--
865 function date:get_current()
866 local now = os.date("*t")
867 return date{
868 year = now.year, month = now.month, day = now.day
869 }
870 end
871 --//--
873 --[[--
874 date = -- date represented by the string
875 atom.date:load(
876 string -- string representing a date
877 )
879 This method returns a date represented by the given string.
881 --]]--
882 function date:load(str)
883 if str == nil or str == "" then
884 return nil
885 elseif type(str) ~= "string" then
886 error("String expected")
887 else
888 local year, month, day = string.match(
889 str, "^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$"
890 )
891 if year then
892 return date{
893 year = tonumber(year),
894 month = tonumber(month),
895 day = tonumber(day)
896 }
897 else
898 return date.invalid
899 end
900 end
901 end
902 --//--
904 function date:__tostring()
905 if self.invalid then
906 return "invalid_date"
907 else
908 return string.format(
909 "%04i-%02i-%02i", self.year, self.month, self.day
910 )
911 end
912 end
914 function date.getters:midnight()
915 return time{ year = self.year, month = self.month, day = self.day }
916 end
918 function date.getters:midday()
919 return time{
920 year = self.year, month = self.month, day = self.day,
921 hour = 12
922 }
923 end
925 function date.getters:iso_weekday() -- 1 = Monday
926 return (self.jd + 3) % 7 + 1
927 end
929 function date.getters:us_weekday() -- 1 = Sunday
930 return (self.jd + 4) % 7 + 1
931 end
933 function date.getters:iso_weekyear() -- ISO week-numbering year
934 local year, month, day = self.year, self.month, self.day
935 local iso_weekday = self.iso_weekday
936 if month == 1 then
937 if
938 (day == 3 and iso_weekday == 7) or
939 (day == 2 and iso_weekday >= 6) or
940 (day == 1 and iso_weekday >= 5)
941 then
942 return year - 1
943 end
944 elseif month == 12 then
945 if
946 (day == 29 and iso_weekday == 1) or
947 (day == 30 and iso_weekday <= 2) or
948 (day == 31 and iso_weekday <= 3)
949 then
950 return year + 1
951 end
952 end
953 return year
954 end
956 function date.getters:iso_week()
957 local jan4 = date{ year = self.iso_weekyear, month = 1, day = 4 }
958 local reference = jan4.jd - jan4.iso_weekday - 6 -- monday of week 0
959 return math.floor((self.jd - reference) / 7)
960 end
962 function date.getters:us_week()
963 local jan1 = date{ year = self.year, month = 1, day = 1 }
964 local reference = jan1.jd - jan1.us_weekday - 6 -- sunday of week 0
965 return math.floor((self.jd - reference) / 7)
966 end
968 function date.__eq(value1, value2)
969 if value1.invalid or value2.invalid then
970 return false
971 else
972 return value1.jd == value2.jd
973 end
974 end
976 function date.__lt(value1, value2)
977 if value1.invalid or value2.invalid then
978 return false
979 else
980 return value1.jd < value2.jd
981 end
982 end
984 function date.__le(value1, value2)
985 if value1.invalid or value2.invalid then
986 return false
987 else
988 return value1.jd <= value2.jd
989 end
990 end
992 function date.__add(value1, value2)
993 if getmetatable(value1) == date then
994 if getmetatable(value2) == date then
995 error("Can not add two dates.")
996 elseif type(value2) == "number" then
997 return date(value1.jd + value2)
998 else
999 error("Right operand of '+' operator has wrong type.")
1000 end
1001 elseif type(value1) == "number" then
1002 if getmetatable(value2) == date then
1003 return date(value1 + value2.jd)
1004 else
1005 error("Assertion failed")
1006 end
1007 else
1008 error("Left operand of '+' operator has wrong type.")
1009 end
1010 end
1012 function date.__sub(value1, value2)
1013 if not getmetatable(value1) == date then
1014 error("Left operand of '-' operator has wrong type.")
1015 end
1016 if getmetatable(value2) == date then
1017 return value1.jd - value2.jd -- TODO: transform to interval
1018 elseif type(value2) == "number" then
1019 return date(value1.jd - value2)
1020 else
1021 error("Right operand of '-' operator has wrong type.")
1022 end
1023 end
1027 ---------------
1028 -- timestamp --
1029 ---------------
1031 timestamp = create_new_type("timestamp")
1033 --[[--
1034 tsec = -- seconds since January 1st 1970 00:00
1035 atom.timestamp.ymdhms_to_tsec(
1036 year, -- year
1037 month, -- month from 1 to 12
1038 day, -- day from 1 to 31
1039 hour, -- hour from 0 to 23
1040 minute, -- minute from 0 to 59
1041 second -- second from 0 to 59
1044 Given the year, month, day, hour, minute and second, this function returns the number of seconds since January 1st 1970 00:00.
1046 --]]--
1047 function timestamp.ymdhms_to_tsec(year, month, day, hour, minute, second)
1048 return
1049 86400 * date.ymd_to_jd(year, month, day) +
1050 3600 * hour + 60 * minute + second
1051 end
1052 --//--
1054 --[[--
1055 year, -- year
1056 month, -- month from 1 to 12
1057 day, -- day from 1 to 31
1058 hour, -- hour from 0 to 23
1059 minute, -- minute from 0 to 59
1060 second = -- second from 0 to 59
1061 atom.timestamp.tsec_to_ymdhms(
1062 tsec -- seconds since January 1st 1970 00:00
1065 Given the seconds since January 1st 1970 00:00, this function returns the year, month, day, hour, minute and second.
1067 --]]--
1068 function timestamp.tsec_to_ymdhms(tsec)
1069 local jd = math.floor(tsec / 86400)
1070 local dsec = tsec % 86400
1071 local year, month, day = date.jd_to_ymd(jd)
1072 local hour = math.floor(dsec / 3600)
1073 local minute = math.floor((dsec % 3600) / 60)
1074 local second = dsec % 60
1075 return year, month, day, hour, minute, second
1076 end
1077 --//--
1079 --[[--
1080 timestamp.invalid
1082 Value representing an invalid timestamp.
1084 --]]--
1085 timestamp.invalid = timestamp:_create{
1086 tsec = not_a_number,
1087 year = not_a_number, month = not_a_number, day = not_a_number,
1088 hour = not_a_number, minute = not_a_number, second = not_a_number,
1089 invalid = true
1091 --//--
1093 --[[--
1094 ts = -- timestamp based on given data
1095 atom.timestamp:new{
1096 tsec = tsec, -- seconds since January 1st 1970 00:00
1097 year = year, -- year
1098 month = month, -- month from 1 to 12
1099 day = day, -- day from 1 to 31
1100 hour = hour, -- hour from 0 to 23
1101 minute = minute, -- minute from 0 to 59
1102 second = second -- second from 0 to 59
1105 This method returns a new timestamp value, based on given data.
1107 --]]--
1108 function timestamp:new(args)
1109 local args = args
1110 if type(args) == "number" then args = { tsec = args } end
1111 if type(args) == "table" then
1112 if not args.second then
1113 args.second = 0
1114 if not args.minute then
1115 args.minute = 0
1116 if not args.hour then
1117 args.hour = 0
1118 end
1119 end
1120 end
1121 if
1122 type(args.year) == "number" and
1123 type(args.month) == "number" and
1124 type(args.day) == "number" and
1125 type(args.hour) == "number" and
1126 type(args.minute) == "number" and
1127 type(args.second) == "number"
1128 then
1129 if
1130 is_integer(args.year) and
1131 args.year >= 1 and args.year <= 9999 and
1132 is_integer(args.month) and
1133 args.month >= 1 and args.month <= 12 and
1134 is_integer(args.day) and
1135 args.day >= 1 and args.day <= 31 and
1136 is_integer(args.hour) and
1137 args.hour >= 0 and args.hour <= 23 and
1138 is_integer(args.minute) and
1139 args.minute >= 0 and args.minute <= 59 and
1140 is_integer(args.second) and
1141 args.second >= 0 and args.second <= 59
1142 then
1143 return timestamp:_create{
1144 tsec = timestamp.ymdhms_to_tsec(
1145 args.year, args.month, args.day,
1146 args.hour, args.minute, args.second
1147 ),
1148 year = args.year,
1149 month = args.month,
1150 day = args.day,
1151 hour = args.hour,
1152 minute = args.minute,
1153 second = args.second
1155 else
1156 return timestamp.invalid
1157 end
1158 elseif type(args.tsec) == "number" then
1159 if
1160 is_integer(args.tsec) and
1161 args.tsec >= -62135596800 and args.tsec <= 253402300799
1162 then
1163 local year, month, day, hour, minute, second =
1164 timestamp.tsec_to_ymdhms(args.tsec)
1165 return timestamp:_create{
1166 tsec = args.tsec,
1167 year = year, month = month, day = day,
1168 hour = hour, minute = minute, second = second
1170 else
1171 return timestamp.invalid
1172 end
1173 end
1174 end
1175 error("Invalid arguments passed to timestamp constructor.")
1176 end
1177 --//--
1179 --[[--
1180 ts = -- current date/time as timestamp
1181 atom.timestamp:get_current()
1183 This function returns the current date and time as timestamp.
1185 --]]--
1186 function timestamp:get_current()
1187 local now = os.date("*t")
1188 return timestamp{
1189 year = now.year, month = now.month, day = now.day,
1190 hour = now.hour, minute = now.min, second = now.sec
1192 end
1193 --//--
1195 --[[--
1196 ts = -- timestamp represented by the string
1197 atom.timestamp:load(
1198 string -- string representing a timestamp
1201 This method returns a timestamp represented by the given string.
1203 --]]--
1204 function timestamp:load(str)
1205 if str == nil or str == "" then
1206 return nil
1207 elseif type(str) ~= "string" then
1208 error("String expected")
1209 else
1210 local year, month, day, hour, minute, second = string.match(
1211 str,
1212 "^([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 if year then
1215 return timestamp{
1216 year = tonumber(year),
1217 month = tonumber(month),
1218 day = tonumber(day),
1219 hour = tonumber(hour),
1220 minute = tonumber(minute),
1221 second = tonumber(second)
1223 else
1224 return timestamp.invalid
1225 end
1226 end
1227 end
1229 function timestamp:__tostring()
1230 if self.invalid then
1231 return "invalid_timestamp"
1232 else
1233 return string.format(
1234 "%04i-%02i-%02i %02i:%02i:%02i",
1235 self.year, self.month, self.day, self.hour, self.minute, self.second
1237 end
1238 end
1240 function timestamp.getters:date()
1241 return date{ year = self.year, month = self.month, day = self.day }
1242 end
1244 function timestamp.getters:time()
1245 return time{
1246 hour = self.hour,
1247 minute = self.minute,
1248 second = self.second
1250 end
1252 function timestamp.__eq(value1, value2)
1253 if value1.invalid or value2.invalid then
1254 return false
1255 else
1256 return value1.tsec == value2.tsec
1257 end
1258 end
1260 function timestamp.__lt(value1, value2)
1261 if value1.invalid or value2.invalid then
1262 return false
1263 else
1264 return value1.tsec < value2.tsec
1265 end
1266 end
1268 function timestamp.__le(value1, value2)
1269 if value1.invalid or value2.invalid then
1270 return false
1271 else
1272 return value1.tsec <= value2.tsec
1273 end
1274 end
1276 function timestamp.__add(value1, value2)
1277 if getmetatable(value1) == timestamp then
1278 if getmetatable(value2) == timestamp then
1279 error("Can not add two timestamps.")
1280 elseif type(value2) == "number" then
1281 return timestamp(value1.tsec + value2)
1282 else
1283 error("Right operand of '+' operator has wrong type.")
1284 end
1285 elseif type(value1) == "number" then
1286 if getmetatable(value2) == timestamp then
1287 return timestamp(value1 + value2.tsec)
1288 else
1289 error("Assertion failed")
1290 end
1291 else
1292 error("Left operand of '+' operator has wrong type.")
1293 end
1294 end
1296 function timestamp.__sub(value1, value2)
1297 if not getmetatable(value1) == timestamp then
1298 error("Left operand of '-' operator has wrong type.")
1299 end
1300 if getmetatable(value2) == timestamp then
1301 return value1.tsec - value2.tsec -- TODO: transform to interval
1302 elseif type(value2) == "number" then
1303 return timestamp(value1.tsec - value2)
1304 else
1305 error("Right operand of '-' operator has wrong type.")
1306 end
1307 end
1311 ----------
1312 -- time --
1313 ----------
1315 time = create_new_type("time")
1317 function time.hms_to_dsec(hour, minute, second)
1318 return 3600 * hour + 60 * minute + second
1319 end
1321 function time.dsec_to_hms(dsec)
1322 local hour = math.floor(dsec / 3600)
1323 local minute = math.floor((dsec % 3600) / 60)
1324 local second = dsec % 60
1325 return hour, minute, second
1326 end
1328 --[[--
1329 atom.time.invalid
1331 Value representing an invalid time of day.
1333 --]]--
1334 time.invalid = time:_create{
1335 dsec = not_a_number,
1336 hour = not_a_number, minute = not_a_number, second = not_a_number,
1337 invalid = true
1339 --//--
1341 --[[--
1342 t = -- time based on given data
1343 atom.time:new{
1344 dsec = dsec, -- seconds since 00:00:00
1345 hour = hour, -- hour from 0 to 23
1346 minute = minute, -- minute from 0 to 59
1347 second = second -- second from 0 to 59
1350 This method returns a new time value, based on given data.
1352 --]]--
1353 function time:new(args)
1354 local args = args
1355 if type(args) == "number" then args = { dsec = args } end
1356 if type(args) == "table" then
1357 if not args.second then
1358 args.second = 0
1359 if not args.minute then
1360 args.minute = 0
1361 end
1362 end
1363 if
1364 type(args.hour) == "number" and
1365 type(args.minute) == "number" and
1366 type(args.second) == "number"
1367 then
1368 if
1369 is_integer(args.hour) and
1370 args.hour >= 0 and args.hour <= 23 and
1371 is_integer(args.minute) and
1372 args.minute >= 0 and args.minute <= 59 and
1373 is_integer(args.second) and
1374 args.second >= 0 and args.second <= 59
1375 then
1376 return time:_create{
1377 dsec = time.hms_to_dsec(args.hour, args.minute, args.second),
1378 hour = args.hour,
1379 minute = args.minute,
1380 second = args.second
1382 else
1383 return time.invalid
1384 end
1385 elseif type(args.dsec) == "number" then
1386 if
1387 is_integer(args.dsec) and
1388 args.dsec >= 0 and args.dsec <= 86399
1389 then
1390 local hour, minute, second =
1391 time.dsec_to_hms(args.dsec)
1392 return time:_create{
1393 dsec = args.dsec,
1394 hour = hour, minute = minute, second = second
1396 else
1397 return time.invalid
1398 end
1399 end
1400 end
1401 error("Invalid arguments passed to time constructor.")
1402 end
1403 --//--
1405 --[[--
1406 t = -- current time of day
1407 atom.time:get_current()
1409 This method returns the current time of the day.
1411 --]]--
1412 function time:get_current()
1413 local now = os.date("*t")
1414 return time{ hour = now.hour, minute = now.min, second = now.sec }
1415 end
1416 --//--
1418 --[[--
1419 t = -- time represented by the string
1420 atom.time:load(
1421 string -- string representing a time of day
1424 This method returns a time represented by the given string.
1426 --]]--
1427 function time:load(str)
1428 if str == nil or str == "" then
1429 return nil
1430 elseif type(str) ~= "string" then
1431 error("String expected")
1432 else
1433 local hour, minute, second = string.match(
1434 str,
1435 "^([0-9][0-9]):([0-9][0-9]):([0-9][0-9])$"
1437 if hour then
1438 return time{
1439 hour = tonumber(hour),
1440 minute = tonumber(minute),
1441 second = tonumber(second)
1443 else
1444 return time.invalid
1445 end
1446 end
1447 end
1448 --//--
1450 function time:__tostring()
1451 if self.invalid then
1452 return "invalid_time"
1453 else
1454 return string.format(
1455 "%02i:%02i:%02i",
1456 self.hour, self.minute, self.second
1458 end
1459 end
1461 function time.__eq(value1, value2)
1462 if value1.invalid or value2.invalid then
1463 return false
1464 else
1465 return value1.dsec == value2.dsec
1466 end
1467 end
1469 function time.__lt(value1, value2)
1470 if value1.invalid or value2.invalid then
1471 return false
1472 else
1473 return value1.dsec < value2.dsec
1474 end
1475 end
1477 function time.__le(value1, value2)
1478 if value1.invalid or value2.invalid then
1479 return false
1480 else
1481 return value1.dsec <= value2.dsec
1482 end
1483 end
1485 function time.__add(value1, value2)
1486 if getmetatable(value1) == time then
1487 if getmetatable(value2) == time then
1488 error("Can not add two times.")
1489 elseif type(value2) == "number" then
1490 return time((value1.dsec + value2) % 86400)
1491 else
1492 error("Right operand of '+' operator has wrong type.")
1493 end
1494 elseif type(value1) == "number" then
1495 if getmetatable(value2) == time then
1496 return time((value1 + value2.dsec) % 86400)
1497 else
1498 error("Assertion failed")
1499 end
1500 else
1501 error("Left operand of '+' operator has wrong type.")
1502 end
1503 end
1505 function time.__sub(value1, value2)
1506 if not getmetatable(value1) == time then
1507 error("Left operand of '-' operator has wrong type.")
1508 end
1509 if getmetatable(value2) == time then
1510 return value1.dsec - value2.dsec -- TODO: transform to interval
1511 elseif type(value2) == "number" then
1512 return time((value1.dsec - value2) % 86400)
1513 else
1514 error("Right operand of '-' operator has wrong type.")
1515 end
1516 end
1518 function time.__concat(value1, value2)
1519 local mt1, mt2 = getmetatable(value1), getmetatable(value2)
1520 if mt1 == date and mt2 == time then
1521 return timestamp{
1522 year = value1.year, month = value1.month, day = value1.day,
1523 hour = value2.hour, minute = value2.minute, second = value2.second
1525 elseif mt1 == time and mt2 == date then
1526 return timestamp{
1527 year = value2.year, month = value2.month, day = value2.day,
1528 hour = value1.hour, minute = value1.minute, second = value1.second
1530 elseif mt1 == time then
1531 error("Right operand of '..' operator has wrong type.")
1532 elseif mt2 == time then
1533 error("Left operand of '..' operator has wrong type.")
1534 else
1535 error("Assertion failed")
1536 end
1537 end
1541 return _M

Impressum / About Us