l43_time 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9e00494e65d3ddd6c92edf5c8a7afe23b051f9d1e5f2a7290b4262e2ba97f467
4
+ data.tar.gz: 0ef11ca9be18c6843c7bfb36a72f67a7d37cfdb430571a95e92e33735bc0e06c
5
+ SHA512:
6
+ metadata.gz: f2436b9759c0afdb3c9ead2ed7505732d8093a4d3c4efa5e55f35407a83ceb1ac7ad67dafc3b12e2803f72299944a43fb79e40f46eb9ad4f87003a830ddf7f12
7
+ data.tar.gz: 60bd5b65ebbf69c2292f35b4cebe1c2eaad95848256a5e3de917210e40eff0c34249242404ad31e267f03c90a7f323e54db752f7a49a56c4a4aeb935f1cde1b1
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43
4
+ module Time
5
+ SPERM = 60
6
+ SPERH = 60*SPERM
7
+ SPERD = 24*SPERH
8
+ end
9
+ end
10
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'errors'
4
+ require_relative 'parser'
5
+
6
+ module L43
7
+ module Time
8
+ module Delta
9
+ include Parser
10
+
11
+ def after(duration, from: nil, format: :to_f) = _delta(duration, from:, format:)
12
+ def before(duration, from: nil, format: :to_f) = _delta(duration, from:, format:, factor: -1)
13
+
14
+ def after!(duration, from: nil, format: :to_f)
15
+ case after(duration, from:, format:)
16
+ in :ok, result
17
+ result
18
+ in :error, error
19
+ raise BadFormat, error
20
+ end
21
+ end
22
+
23
+ def before!(duration, from: nil, format: :to_f)
24
+ case before(duration, from:, format:)
25
+ in :ok, result
26
+ result
27
+ in :error, error
28
+ raise BadFormat, error
29
+ end
30
+ end
31
+
32
+ private
33
+ def _delta(duration, from: nil, format: :to_f, factor: 1)
34
+ from ||= ::Time.now
35
+ case parse_or_duration(duration)
36
+ in :ok, result
37
+ _format_date(from + factor * result.to_f, format:)
38
+ in error
39
+ error
40
+ end
41
+ end
42
+
43
+ def _format_date(date, format:)
44
+ case format
45
+ in Symbol => sym
46
+ date.send(sym).to_s
47
+ else
48
+ raise ArgumentError, 'only symbols are supported for the format: argument'
49
+ end
50
+ rescue NoMethodError
51
+ raise ArgumentError, 'only symbols representing instance methods of the class Time are supported for the format: argument'
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43
4
+ module Time
5
+ BadFormat = Class.new RuntimeError
6
+ end
7
+ end
8
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'consts'
4
+ require_relative 'errors'
5
+ require_relative 'tools/scanner'
6
+ require_relative 'ts'
7
+
8
+ module L43
9
+ module Time
10
+ module Parser
11
+ include Ts
12
+
13
+ Prefix =
14
+ %r{
15
+ (?<unit>[[:lower:]]+) [[:space:]]*:[[:space:]]* (?<value>[[:digit:]]+ (?:\.[[:digit:]]+)? )
16
+ }x
17
+
18
+ Suffix =
19
+ %r{
20
+ (?<value>[[:digit:]]+ (?:\.[[:digit:]]+)?) [[:space:]]* (?<unit>[[:lower:]]+)
21
+ }x
22
+
23
+ def parse_duration!(str)
24
+ case parse_duration(str)
25
+ in :ok, value
26
+ value
27
+ in :error, msg
28
+ raise BadFormat, msg
29
+ end
30
+ end
31
+
32
+ def parse_duration(str)
33
+ if str[Prefix]
34
+ Helper.parse_prefix(str)
35
+ else
36
+ Helper.parse_suffix(str)
37
+ end
38
+ end
39
+
40
+ def parse_or_duration!(str)
41
+ case parse_or_duration(str)
42
+ in :ok, value
43
+ value
44
+ in :error, msg
45
+ raise BadFormat, msg
46
+ end
47
+ end
48
+
49
+ def parse_or_duration(str)
50
+ case duration_as_string(str)
51
+ in :ok, value
52
+ [:ok, value]
53
+ else
54
+ parse_duration(str)
55
+ end
56
+ end
57
+
58
+ module Helper extend self
59
+ Units = {
60
+ 's' => [1, 'seconds'],
61
+ 'second' => [1, 'seconds'],
62
+ 'seconds' => [1, 'seconds'],
63
+
64
+ 'm' => [60, 'minutes'],
65
+ 'minute' => [60, 'minutes'],
66
+ 'minutes' => [60, 'minutes'],
67
+
68
+ 'h' => [3600, 'hours'],
69
+ 'hour' => [3600, 'hours'],
70
+ 'hours' => [3600, 'hours'],
71
+
72
+ 'd' => [86400, 'days'],
73
+ 'day' => [86400, 'days'],
74
+ 'days' => [86400, 'days'],
75
+ }
76
+
77
+ def parse_prefix(str) = _parse_with(str, rgx: Prefix)
78
+ def parse_suffix(str) = _parse_with(str, rgx: Suffix)
79
+
80
+ private
81
+ def _parse_with(str, rgx:)
82
+ sign = ""
83
+ if str.start_with?("-")
84
+ sign = "-"
85
+ str = str[1..]
86
+ end
87
+
88
+ parsed = Tools::Scanner.scan_with_captures(str:, rgx:)
89
+ .inject([Set.new, 0, nil]) { |(used, result, error), captures| _update_duration(used:, result:, error:, unit: captures.unit, value: captures.value) }
90
+
91
+ case parsed
92
+ in [_, result, nil]
93
+ [:ok, sign + result.to_s]
94
+ in [_, _, error]
95
+ [:error, error]
96
+ end
97
+ end
98
+
99
+ def _seconds(unit:, used:, value:)
100
+ case Units.fetch(unit, :not_found)
101
+ in :not_found
102
+ [:error, "#{unit} is not supported, use d(ays?), h(ours?), m(inutes), s(econds?) only"]
103
+ in factor, name
104
+ if used.member?(name)
105
+ [:error, "#{name} already used"]
106
+ else
107
+ used << name
108
+ [:ok, factor * value.to_f]
109
+ end
110
+ end
111
+ end
112
+
113
+ def _update_duration(used:, result:, error:, unit:, value:)
114
+ return [used, result, error] if error
115
+ case _seconds(unit:, used:, value:)
116
+ in :error, error
117
+ [used, result, error]
118
+ in :ok, value
119
+ [used, result+value, nil]
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'l43/open_object'
4
+ module L43
5
+ module Time
6
+ class Range
7
+ extend L43::OpenObject
8
+
9
+ attributes from: nil, to: nil
10
+ def ===(other)
11
+ other = _time(other)
12
+ return false if from&.> other
13
+ return false if to&.< other
14
+ true
15
+ end
16
+
17
+ private
18
+ def initialize(from: nil, to: nil)
19
+ raise ArgumentError, "need at least one of: from:, to:" unless from || to
20
+ @from = _time(from)
21
+ @to = _time(to)
22
+ end
23
+
24
+ def _time(str_or_time)
25
+ return Time.str_to_time(str_or_time) if String === str_or_time
26
+ str_or_time
27
+ end
28
+
29
+ end
30
+ end
31
+ end
32
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'range'
4
+ module L43
5
+ module Time
6
+ module TimeRange
7
+ def str_to_range(str)
8
+ case str.split("..")
9
+ in from, to
10
+ time_range(from:, to:)
11
+ else
12
+ raise ArgumentError, "must use a str of format '<time>..<time>'"
13
+ end
14
+ end
15
+
16
+ def time_range(from: nil, to: nil)
17
+ Range.new(from:, to:)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'parser'
4
+ module L43
5
+ module Time
6
+ module Duration
7
+ extend Parser
8
+ end
9
+
10
+ module ToTime
11
+ T = ::Time
12
+ def str_to_time(str)
13
+ case str
14
+ when /\A\d{14}\z/
15
+ T.strptime(str, "%Y%m%d%H%M%S")
16
+ when /\A\d{12}\z/
17
+ T.strptime(str, "%y%m%d%H%M%S")
18
+ when /\A\d{6}\z/
19
+ T.strptime(str, "%y%m%d")
20
+ when /\A\d{4}\z/
21
+ T.strptime(str, "%m%d")
22
+ when /\A\d{2}\z/
23
+ T.strptime(str, "%d")
24
+ else
25
+ parse_symbolic_or_duration(str)
26
+ end
27
+ end
28
+
29
+ private
30
+ def parse_symbolic_or_duration(str)
31
+ case symbolic_time(str)
32
+ in :ok, time
33
+ time
34
+ else
35
+ d = Duration.parse_duration!(str).to_f
36
+ T.now + d
37
+ end
38
+ end
39
+
40
+ def sym_today = T.strptime(format("%02d", T.now.day), "%d")
41
+ def sym_yesterday = T.strptime(format("%02d", T.now.day), "%d") - SPERD
42
+
43
+ SymbolicTimes = {
44
+ "today" => :sym_today,
45
+ "yesterday" => :sym_yesterday,
46
+ "yd" => :sym_yesterday,
47
+ }
48
+
49
+ def symbolic_time(str)
50
+ # TODO: Use &"sym...".to_sym
51
+ map_ok(exception: KeyError) {
52
+ send SymbolicTimes.fetch(str)
53
+ }
54
+ end
55
+
56
+ def map_ok(exception: RuntimeError, &blk)
57
+ [:ok, blk.()]
58
+ rescue exception => e
59
+ [:error, e.message]
60
+ end
61
+
62
+
63
+ end
64
+ end
65
+ end
66
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ostruct'
4
+ module L43
5
+ module Time
6
+ module Tools
7
+ module Scanner extend self
8
+ def scan_with_captures(str:, rgx:, names: :keys)
9
+ result = []
10
+ str
11
+ .scan(rgx) { |*| result << _extract(from: Regexp.last_match, names:) }
12
+ result
13
+ end
14
+
15
+ private
16
+ def _extract(from:, names:)
17
+ case names
18
+ in :keys
19
+ OpenStruct.new(from.named_captures).freeze
20
+ in :all
21
+ from.captures.freeze
22
+ else
23
+ OpenStruct.new(from.named_captures.slice(names.map(&:to_s))).freeze
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'consts'
4
+ require_relative 'errors'
5
+
6
+ module L43
7
+ module Time
8
+ module Ts
9
+ DecimalPart = %r{\A(.*)\.(\d+)\z}
10
+ Seperators =
11
+ Regexp.compile(%w[. : _ -].map { Regexp.escape(it) }.join('|'))
12
+
13
+ def duration_as_string!(str)
14
+ case duration_as_string(str)
15
+ in :ok, value
16
+ value
17
+ in :error, msg
18
+ raise BadFormat, msg
19
+ end
20
+ end
21
+ alias_method :duration_s!, :duration_as_string!
22
+
23
+ def duration_as_string(str)
24
+ header = ''
25
+ if str.start_with?('+')
26
+ str = str[1..]
27
+ elsif str.start_with?('-')
28
+ str = str[1..]
29
+ header = '-'
30
+ end
31
+
32
+ if DecimalPart === str
33
+ Helper.duration_as_string($1, $2, header)
34
+ else
35
+ Helper.duration_as_string(str, "0", header)
36
+ end
37
+ end
38
+ alias_method :duration_s, :duration_as_string
39
+
40
+ module Helper extend self
41
+
42
+ def duration_as_string(secs, millisecs, header)
43
+ case _duration_as_string(secs)
44
+ in :ok, value
45
+ [:ok, [header, value, '.', millisecs].join]
46
+ in error
47
+ error
48
+ end
49
+ end
50
+
51
+ private
52
+ def _compute(parts)
53
+ [:ok,
54
+ parts
55
+ .reverse
56
+ .zip([1, SPERM, SPERH, SPERD])
57
+ .inject(0) { |s, (v,f)| s + f * Integer(v) }
58
+ ]
59
+ rescue ArgumentError
60
+ [:error, "Only digits or seperators are allowed in a duration string"]
61
+ end
62
+
63
+ def _duration_as_string(secs)
64
+ case secs.split(Seperators)
65
+ in [m, _, _, _, _, *]
66
+ [:error, "Must not specify more than day values, got leading #{m}"]
67
+ in parts
68
+ _compute(parts)
69
+ end
70
+ end
71
+ end
72
+
73
+ end
74
+ end
75
+ end
76
+
77
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43
4
+ module Time
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
8
+ # SPDX-License-Identifier: AGPL-3.0-or-later
data/lib/l43/time.rb ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+
5
+ require_relative 'time/delta'
6
+ require_relative 'time/time_range'
7
+ require_relative 'time/to_time'
8
+ require_relative 'time/version'
9
+ module L43
10
+ module Time
11
+
12
+ extend Delta
13
+ include ToTime
14
+ include TimeRange
15
+
16
+ end
17
+ end
18
+ # SPDX-License-Identifier: AGPL-3.0-or-later
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: l43_time
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Robert Dober
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2026-03-30 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: ostruct
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 0.6.3
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: 0.6.3
26
+ - !ruby/object:Gem::Dependency
27
+ name: l43_open_object
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 0.2.5
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 0.2.5
40
+ description: 'Time related tools
41
+
42
+ '
43
+ email: robert.dober@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - lib/l43/time.rb
49
+ - lib/l43/time/consts.rb
50
+ - lib/l43/time/delta.rb
51
+ - lib/l43/time/errors.rb
52
+ - lib/l43/time/parser.rb
53
+ - lib/l43/time/range.rb
54
+ - lib/l43/time/time_range.rb
55
+ - lib/l43/time/to_time.rb
56
+ - lib/l43/time/tools/scanner.rb
57
+ - lib/l43/time/ts.rb
58
+ - lib/l43/time/version.rb
59
+ homepage: https://codeberg.org/lab419/l43_time
60
+ licenses:
61
+ - AGPL-3.0-or-later
62
+ metadata: {}
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: 4.0.1
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubygems_version: 4.0.9
78
+ specification_version: 4
79
+ summary: Time related tools
80
+ test_files: []