webmcp

view libraries/atom/atom.lua @ 531:b19a6b4f61f3

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

Impressum / About Us