openhab-scripting 4.1.4 → 4.5.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 +4 -4
- data/lib/openhab/core/entity_lookup.rb +1 -57
- data/lib/openhab/dsl/actions.rb +2 -3
- data/lib/openhab/dsl/dsl.rb +8 -12
- data/lib/openhab/dsl/group.rb +1 -5
- data/lib/openhab/dsl/items/color_item.rb +60 -0
- data/lib/openhab/dsl/items/comparable_item.rb +49 -0
- data/lib/openhab/dsl/items/contact_item.rb +41 -0
- data/lib/openhab/dsl/items/date_time_item.rb +64 -0
- data/lib/openhab/dsl/items/dimmer_item.rb +59 -0
- data/lib/openhab/dsl/items/ensure.rb +93 -0
- data/lib/openhab/dsl/items/generic_item.rb +174 -0
- data/lib/openhab/dsl/items/group_item.rb +121 -89
- data/lib/openhab/dsl/items/image_item.rb +5 -41
- data/lib/openhab/dsl/items/item_equality.rb +36 -0
- data/lib/openhab/dsl/items/item_registry.rb +49 -0
- data/lib/openhab/dsl/items/items.rb +81 -35
- data/lib/openhab/dsl/items/metadata.rb +325 -0
- data/lib/openhab/dsl/items/number_item.rb +6 -312
- data/lib/openhab/dsl/items/numeric_item.rb +68 -0
- data/lib/openhab/dsl/items/persistence.rb +122 -0
- data/lib/openhab/dsl/items/player_item.rb +49 -40
- data/lib/openhab/dsl/items/rollershutter_item.rb +25 -77
- data/lib/openhab/dsl/items/string_item.rb +16 -58
- data/lib/openhab/dsl/items/switch_item.rb +62 -0
- data/lib/openhab/dsl/lazy_array.rb +8 -6
- data/lib/openhab/dsl/monkey_patch/events/events.rb +2 -2
- data/lib/openhab/dsl/monkey_patch/events/item_command.rb +67 -24
- data/lib/openhab/dsl/monkey_patch/events/item_event.rb +5 -5
- data/lib/openhab/dsl/monkey_patch/events/item_state.rb +10 -11
- data/lib/openhab/dsl/monkey_patch/events/item_state_changed.rb +10 -11
- data/lib/openhab/dsl/monkey_patch/ruby/number.rb +25 -2
- data/lib/openhab/dsl/monkey_patch/ruby/ruby.rb +0 -3
- data/lib/openhab/dsl/monkey_patch/ruby/string.rb +24 -24
- data/lib/openhab/dsl/rules/terse.rb +24 -0
- data/lib/openhab/dsl/states.rb +1 -1
- data/lib/openhab/dsl/time_of_day.rb +3 -5
- data/lib/openhab/dsl/types/comparable_type.rb +21 -0
- data/lib/openhab/dsl/types/date_time_type.rb +334 -0
- data/lib/openhab/dsl/types/decimal_type.rb +187 -0
- data/lib/openhab/dsl/types/hsb_type.rb +201 -0
- data/lib/openhab/dsl/types/increase_decrease_type.rb +23 -0
- data/lib/openhab/dsl/types/next_previous_type.rb +23 -0
- data/lib/openhab/dsl/types/numeric_type.rb +39 -0
- data/lib/openhab/dsl/types/on_off_type.rb +29 -0
- data/lib/openhab/dsl/types/open_closed_type.rb +29 -0
- data/lib/openhab/dsl/types/percent_type.rb +70 -0
- data/lib/openhab/dsl/types/play_pause_type.rb +27 -0
- data/lib/openhab/dsl/types/quantity_type.rb +275 -0
- data/lib/openhab/dsl/types/refresh_type.rb +18 -0
- data/lib/openhab/dsl/types/rewind_fastforward_type.rb +33 -0
- data/lib/openhab/dsl/types/stop_move_type.rb +23 -0
- data/lib/openhab/dsl/types/string_type.rb +88 -0
- data/lib/openhab/dsl/types/type.rb +72 -0
- data/lib/openhab/dsl/types/types.rb +78 -0
- data/lib/openhab/dsl/types/un_def_type.rb +22 -0
- data/lib/openhab/dsl/types/up_down_type.rb +32 -0
- data/lib/openhab/dsl/units.rb +11 -6
- data/lib/openhab/version.rb +1 -1
- data/lib/openhab.rb +0 -1
- metadata +36 -28
- data/lib/openhab/dsl/items/datetime_item.rb +0 -75
- data/lib/openhab/dsl/items/item_command.rb +0 -90
- data/lib/openhab/dsl/items/item_delegate.rb +0 -125
- data/lib/openhab/dsl/monkey_patch/items/contact_item.rb +0 -51
- data/lib/openhab/dsl/monkey_patch/items/dimmer_item.rb +0 -140
- data/lib/openhab/dsl/monkey_patch/items/items.rb +0 -142
- data/lib/openhab/dsl/monkey_patch/items/metadata.rb +0 -328
- data/lib/openhab/dsl/monkey_patch/items/persistence.rb +0 -123
- data/lib/openhab/dsl/monkey_patch/items/switch_item.rb +0 -71
- data/lib/openhab/dsl/monkey_patch/ruby/range.rb +0 -47
- data/lib/openhab/dsl/monkey_patch/ruby/time.rb +0 -32
- data/lib/openhab/dsl/monkey_patch/types/decimal_type.rb +0 -97
- data/lib/openhab/dsl/monkey_patch/types/increase_decrease_type.rb +0 -23
- data/lib/openhab/dsl/monkey_patch/types/next_previous_type.rb +0 -23
- data/lib/openhab/dsl/monkey_patch/types/on_off_type.rb +0 -79
- data/lib/openhab/dsl/monkey_patch/types/open_closed_type.rb +0 -71
- data/lib/openhab/dsl/monkey_patch/types/percent_type.rb +0 -77
- data/lib/openhab/dsl/monkey_patch/types/play_pause_type.rb +0 -23
- data/lib/openhab/dsl/monkey_patch/types/quantity_type.rb +0 -69
- data/lib/openhab/dsl/monkey_patch/types/refresh_type.rb +0 -23
- data/lib/openhab/dsl/monkey_patch/types/rewind_fastforward_type.rb +0 -23
- data/lib/openhab/dsl/monkey_patch/types/stop_move_type.rb +0 -23
- data/lib/openhab/dsl/monkey_patch/types/types.rb +0 -15
- data/lib/openhab/dsl/monkey_patch/types/up_down_type.rb +0 -72
- data/lib/openhab/dsl/types/datetime.rb +0 -338
- data/lib/openhab/dsl/types/quantity.rb +0 -300
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'openhab/dsl/types/quantity'
|
|
4
|
-
require 'openhab/dsl/types/datetime'
|
|
5
|
-
require 'openhab/dsl/items/datetime_item'
|
|
6
|
-
|
|
7
3
|
module OpenHAB
|
|
8
4
|
module DSL
|
|
5
|
+
# monkey patches
|
|
9
6
|
module MonkeyPatch
|
|
7
|
+
# extensions to core Ruby objects
|
|
10
8
|
module Ruby
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
9
|
+
# extend String class so that it will do semantic comparisons against
|
|
10
|
+
# DateTimeType and QuantityType, instead of converting the latter to
|
|
11
|
+
# String and doing an exact match
|
|
14
12
|
module StringExtensions
|
|
15
|
-
include
|
|
16
|
-
|
|
17
|
-
#
|
|
18
|
-
# Compares String to another object
|
|
19
|
-
#
|
|
20
|
-
# @param [Object] other object to compare to
|
|
21
|
-
#
|
|
22
|
-
# @return [Boolean] true if the two objects contain the same value, false otherwise
|
|
23
|
-
#
|
|
13
|
+
# {include:StringExtensions}
|
|
24
14
|
def ==(other)
|
|
25
15
|
case other
|
|
26
|
-
when
|
|
27
|
-
|
|
16
|
+
when Types::QuantityType,
|
|
17
|
+
Types::DateTimeType,
|
|
18
|
+
Items::DateTimeItem,
|
|
19
|
+
Items::NumericItem
|
|
28
20
|
other == self
|
|
29
21
|
else
|
|
30
22
|
super
|
|
31
23
|
end
|
|
32
24
|
end
|
|
25
|
+
|
|
26
|
+
# {include:StringExtensions}
|
|
27
|
+
def <=>(other)
|
|
28
|
+
case other
|
|
29
|
+
when Types::QuantityType,
|
|
30
|
+
Types::DateTimeType,
|
|
31
|
+
Items::DateTimeItem,
|
|
32
|
+
Items::NumericItem
|
|
33
|
+
(other <=> self)&.-@()
|
|
34
|
+
else
|
|
35
|
+
super
|
|
36
|
+
end
|
|
37
|
+
end
|
|
33
38
|
end
|
|
34
39
|
end
|
|
35
40
|
end
|
|
36
41
|
end
|
|
37
42
|
end
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
# Prepend String class with comparison extensions
|
|
41
|
-
#
|
|
42
|
-
class String
|
|
43
|
-
prepend OpenHAB::DSL::MonkeyPatch::Ruby::StringExtensions
|
|
44
|
-
end
|
|
44
|
+
String.prepend(OpenHAB::DSL::MonkeyPatch::Ruby::StringExtensions)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenHAB
|
|
4
|
+
module DSL
|
|
5
|
+
module Rules
|
|
6
|
+
# Module containing terse rule stubs
|
|
7
|
+
module Terse
|
|
8
|
+
%i[changed channel cron every updated received_command].each do |trigger|
|
|
9
|
+
class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
|
10
|
+
def #{trigger}(*args, name: nil, **kwargs, &block) # def changed(*args, name: nil, **kwargs, &block)
|
|
11
|
+
# if no name is given, just default to the name of the rules file # # if no name is given, just default to the name of the rules file
|
|
12
|
+
name ||= File.basename(caller_locations.last.path) # name ||= File.basename(caller_locations.last.path)
|
|
13
|
+
rule name do # rule name do
|
|
14
|
+
#{trigger}(*args, **kwargs) # changed(*args, **kwargs)
|
|
15
|
+
run(&block) # run(&block)
|
|
16
|
+
end # end
|
|
17
|
+
end # end
|
|
18
|
+
module_function #{trigger.inspect} # module_function :changed
|
|
19
|
+
RUBY
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/openhab/dsl/states.rb
CHANGED
|
@@ -48,7 +48,7 @@ module OpenHAB
|
|
|
48
48
|
# @return [StateStorage] item states
|
|
49
49
|
#
|
|
50
50
|
def store_states(*items)
|
|
51
|
-
items = items.flatten
|
|
51
|
+
items = items.flatten
|
|
52
52
|
states = StateStorage.new(BusEvent.storeStates(*items).to_h)
|
|
53
53
|
if block_given?
|
|
54
54
|
yield
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'java'
|
|
4
3
|
require 'openhab/log/logger'
|
|
5
|
-
require 'openhab/dsl/
|
|
6
|
-
require 'openhab/dsl/types/datetime'
|
|
4
|
+
require 'openhab/dsl/types/date_time_type'
|
|
7
5
|
require 'time'
|
|
8
6
|
|
|
9
7
|
module OpenHAB
|
|
@@ -173,7 +171,7 @@ module OpenHAB
|
|
|
173
171
|
case object
|
|
174
172
|
when TimeOfDay then adjust_second_of_day(object.local_time.to_second_of_day)
|
|
175
173
|
when String then adjust_second_of_day(TimeOfDay.parse(object).local_time.to_second_of_day)
|
|
176
|
-
when Time, OpenHAB::DSL::Types::
|
|
174
|
+
when Time, OpenHAB::DSL::Types::DateTimeType, OpenHAB::DSL::Items::DateTimeItem
|
|
177
175
|
adjust_second_of_day(TimeOfDay.new(h: object.hour, m: object.min, s: object.sec)
|
|
178
176
|
.local_time.to_second_of_day)
|
|
179
177
|
when TimeOfDayRangeElement then object.sod
|
|
@@ -218,7 +216,7 @@ module OpenHAB
|
|
|
218
216
|
private_class_method def to_time_of_day(object)
|
|
219
217
|
case object
|
|
220
218
|
when String then TimeOfDay.parse(object)
|
|
221
|
-
when Time, OpenHAB::DSL::Types::
|
|
219
|
+
when Time, OpenHAB::DSL::Types::DateTimeType, OpenHAB::DSL::Items::DateTimeItem
|
|
222
220
|
TimeOfDay.new(h: object.hour, m: object.min, s: object.sec)
|
|
223
221
|
else object
|
|
224
222
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenHAB
|
|
4
|
+
module DSL
|
|
5
|
+
# Comparable#== is overwritten by Type, because DecimalType etc.
|
|
6
|
+
# inherits from Comparable on the Java side, so it's in the wrong place
|
|
7
|
+
# in the ancestor list
|
|
8
|
+
# @!visibility private
|
|
9
|
+
module ComparableType
|
|
10
|
+
# re-implement
|
|
11
|
+
# @!visibility private
|
|
12
|
+
def ==(other)
|
|
13
|
+
r = self <=> other
|
|
14
|
+
|
|
15
|
+
return false if r.nil?
|
|
16
|
+
|
|
17
|
+
r.zero?
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'forwardable'
|
|
4
|
+
require 'time'
|
|
5
|
+
|
|
6
|
+
module OpenHAB
|
|
7
|
+
module DSL
|
|
8
|
+
module Types
|
|
9
|
+
java_import org.openhab.core.library.types.DateTimeType
|
|
10
|
+
|
|
11
|
+
# global alias
|
|
12
|
+
::DateTimeType = DateTimeType
|
|
13
|
+
|
|
14
|
+
# @deprecated
|
|
15
|
+
# Backwards-compatible alias
|
|
16
|
+
DateTime = DateTimeType
|
|
17
|
+
|
|
18
|
+
# rubocop: disable Metrics/ClassLength class has a single responsibility
|
|
19
|
+
|
|
20
|
+
#
|
|
21
|
+
# Add methods to core OpenHAB DateTimeType to make it behave as a Ruby
|
|
22
|
+
# Time object
|
|
23
|
+
#
|
|
24
|
+
# Any method not explicitly defined is forwarded to the +ZonedDateTime+
|
|
25
|
+
# or +Time+ representation of this object as appropriate.
|
|
26
|
+
#
|
|
27
|
+
class DateTimeType
|
|
28
|
+
# @!parse include Type
|
|
29
|
+
|
|
30
|
+
# remove the JRuby default == so that we can inherit the Ruby method
|
|
31
|
+
remove_method :==
|
|
32
|
+
|
|
33
|
+
extend Forwardable
|
|
34
|
+
include Comparable
|
|
35
|
+
|
|
36
|
+
#
|
|
37
|
+
# Regex expression to identify strings defining a time in hours, minutes and optionally seconds
|
|
38
|
+
#
|
|
39
|
+
TIME_ONLY_REGEX = /\A(?<hours>\d\d):(?<minutes>\d\d)(?<seconds>:\d\d)?\Z/.freeze
|
|
40
|
+
|
|
41
|
+
#
|
|
42
|
+
# Regex expression to identify strings defining a time in year, month, and day
|
|
43
|
+
#
|
|
44
|
+
DATE_ONLY_REGEX = /\A\d{4}-\d\d-\d\d\Z/.freeze
|
|
45
|
+
private_constant :TIME_ONLY_REGEX, :DATE_ONLY_REGEX
|
|
46
|
+
|
|
47
|
+
class << self
|
|
48
|
+
#
|
|
49
|
+
# Parses a String representing a time into an OpenHAB DateTimeType. First tries to parse it
|
|
50
|
+
# using java's DateTimeType's parser, then falls back to the Ruby Time.parse
|
|
51
|
+
#
|
|
52
|
+
# @param [String] time_string
|
|
53
|
+
#
|
|
54
|
+
# @return [DateTimeType]
|
|
55
|
+
#
|
|
56
|
+
def parse(time_string)
|
|
57
|
+
time_string = "#{time_string}Z" if TIME_ONLY_REGEX.match?(time_string)
|
|
58
|
+
DateTimeType.new(time_string)
|
|
59
|
+
rescue java.lang.StringIndexOutOfBoundsException, java.lang.IllegalArgumentException
|
|
60
|
+
# Try ruby's Time.parse if OpenHAB's DateTimeType parser fails
|
|
61
|
+
begin
|
|
62
|
+
DateTimeType.new(Time.parse(time_string))
|
|
63
|
+
rescue ArgumentError
|
|
64
|
+
raise ArgumentError, "Unable to parse #{time_string} into a DateTimeType"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# parses a String representing a duration
|
|
69
|
+
#
|
|
70
|
+
# for internal use
|
|
71
|
+
#
|
|
72
|
+
# @return [java.time.Duration]
|
|
73
|
+
#
|
|
74
|
+
# @!visibility private
|
|
75
|
+
def parse_duration(time_string)
|
|
76
|
+
# convert from common HH:MM to ISO8601 for parsing
|
|
77
|
+
if (match = time_string.match(TIME_ONLY_REGEX))
|
|
78
|
+
time_string = "PT#{match[:hours]}H#{match[:minutes]}M#{match[:seconds] || 0}S"
|
|
79
|
+
end
|
|
80
|
+
java.time.Duration.parse(time_string)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# act like a ruby Time
|
|
85
|
+
def_delegator :zoned_date_time, :month_value, :month
|
|
86
|
+
def_delegator :zoned_date_time, :day_of_month, :mday
|
|
87
|
+
def_delegator :zoned_date_time, :day_of_year, :yday
|
|
88
|
+
def_delegator :zoned_date_time, :minute, :min
|
|
89
|
+
def_delegator :zoned_date_time, :second, :sec
|
|
90
|
+
def_delegator :zoned_date_time, :nano, :nsec
|
|
91
|
+
def_delegator :zoned_date_time, :to_epoch_second, :to_i
|
|
92
|
+
|
|
93
|
+
alias day mday
|
|
94
|
+
|
|
95
|
+
#
|
|
96
|
+
# Create a new instance of DateTimeType
|
|
97
|
+
#
|
|
98
|
+
# @param value [ZonedDateTime, Time, String, Numeric]
|
|
99
|
+
#
|
|
100
|
+
def initialize(value = nil) # rubocop:disable Metrics
|
|
101
|
+
if value.respond_to?(:to_time)
|
|
102
|
+
time = value.to_time
|
|
103
|
+
instant = java.time.Instant.ofEpochSecond(time.to_i, time.nsec)
|
|
104
|
+
zone_id = java.time.ZoneId.of_offset('UTC', java.time.ZoneOffset.of_total_seconds(time.utc_offset))
|
|
105
|
+
super(ZonedDateTime.ofInstant(instant, zone_id))
|
|
106
|
+
return
|
|
107
|
+
elsif value.respond_to?(:to_str)
|
|
108
|
+
# strings respond_do?(:to_d), but we want to avoid that conversion
|
|
109
|
+
super(value.to_str)
|
|
110
|
+
return
|
|
111
|
+
elsif value.respond_to?(:to_d)
|
|
112
|
+
time = value.to_d
|
|
113
|
+
super(ZonedDateTime.ofInstant(
|
|
114
|
+
java.time.Instant.ofEpochSecond(time.to_i,
|
|
115
|
+
((time % 1) * 1_000_000_000).to_i),
|
|
116
|
+
java.time.ZoneId.systemDefault
|
|
117
|
+
))
|
|
118
|
+
return
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
super
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
#
|
|
125
|
+
# Check equality without type conversion
|
|
126
|
+
#
|
|
127
|
+
# @return [Boolean] if the same value is represented, without type
|
|
128
|
+
# conversion
|
|
129
|
+
def eql?(other)
|
|
130
|
+
return false unless other.instance_of?(self.class)
|
|
131
|
+
|
|
132
|
+
zoned_date_time.compare_to(other.zoned_date_time).zero?
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
#
|
|
136
|
+
# Comparison
|
|
137
|
+
#
|
|
138
|
+
# @param [DateTimeType, Items::DateTimeItem, Time,
|
|
139
|
+
# String] other object to compare to
|
|
140
|
+
#
|
|
141
|
+
# @return [Integer, nil] -1, 0, +1 depending on whether +other+ is
|
|
142
|
+
# less than, equal to, or greater than self
|
|
143
|
+
#
|
|
144
|
+
# nil is returned if the two values are incomparable
|
|
145
|
+
#
|
|
146
|
+
def <=>(other) # rubocop:disable Metrics
|
|
147
|
+
logger.trace("(#{self.class}) #{self} <=> #{other} (#{other.class})")
|
|
148
|
+
if other.is_a?(self.class)
|
|
149
|
+
zoned_date_time.to_instant.compare_to(other.zoned_date_time.to_instant)
|
|
150
|
+
elsif other.is_a?(Items::DateTimeItem) ||
|
|
151
|
+
(other.is_a?(Items::GroupItem) && other.base_item.is_a?(Items::DateTimeItem))
|
|
152
|
+
return nil unless other.state?
|
|
153
|
+
|
|
154
|
+
zoned_date_time.to_instant.compare_to(other.state.zoned_date_time.to_instant)
|
|
155
|
+
elsif other.is_a?(TimeOfDay::TimeOfDay) || other.is_a?(TimeOfDay::TimeOfDayRangeElement)
|
|
156
|
+
to_tod <=> other
|
|
157
|
+
elsif other.respond_to?(:to_time)
|
|
158
|
+
to_time <=> other.to_time
|
|
159
|
+
elsif other.respond_to?(:to_str)
|
|
160
|
+
time_string = other.to_str
|
|
161
|
+
time_string = "#{time_string}T00:00:00#{zone}" if DATE_ONLY_REGEX.match?(time_string)
|
|
162
|
+
self <=> DateTimeType.parse(time_string)
|
|
163
|
+
elsif other.respond_to?(:coerce)
|
|
164
|
+
lhs, rhs = other.coerce(self)
|
|
165
|
+
lhs <=> rhs
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
#
|
|
170
|
+
# Type Coercion
|
|
171
|
+
#
|
|
172
|
+
# Coerce object to a DateTimeType
|
|
173
|
+
#
|
|
174
|
+
# @param [Items::DateTimeItem, Time] other object to coerce to a
|
|
175
|
+
# DateTimeType
|
|
176
|
+
#
|
|
177
|
+
# @return [[DateTimeType, DateTimeType]]
|
|
178
|
+
#
|
|
179
|
+
def coerce(other)
|
|
180
|
+
logger.trace("Coercing #{self} as a request from #{other.class}")
|
|
181
|
+
if other.is_a?(Items::DateTimeItem)
|
|
182
|
+
raise TypeError, "can't convert #{UnDefType} into #{self.class}" unless other.state?
|
|
183
|
+
|
|
184
|
+
[other.state, self]
|
|
185
|
+
elsif other.respond_to?(:to_time)
|
|
186
|
+
[DateTimeType.new(other), self]
|
|
187
|
+
else
|
|
188
|
+
raise TypeError, "can't convert #{other.class} into #{self.class}"
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
#
|
|
193
|
+
# Convert this DateTimeType to a ruby Time object
|
|
194
|
+
#
|
|
195
|
+
# @return [Time] A Time object representing the same instant and timezone
|
|
196
|
+
#
|
|
197
|
+
def to_time
|
|
198
|
+
Time.at(to_i, nsec, :nsec).localtime(utc_offset)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
#
|
|
202
|
+
# Convert the time part of this DateTimeType to a TimeOfDay object
|
|
203
|
+
#
|
|
204
|
+
# @return [TimeOfDay] A TimeOfDay object representing the time
|
|
205
|
+
#
|
|
206
|
+
def to_time_of_day
|
|
207
|
+
TimeOfDay::TimeOfDay.new(h: hour, m: minute, s: second)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
alias to_tod to_time_of_day
|
|
211
|
+
|
|
212
|
+
#
|
|
213
|
+
# Returns the value of time as a floating point number of seconds since the Epoch
|
|
214
|
+
#
|
|
215
|
+
# @return [Float] Number of seconds since the Epoch, with nanosecond presicion
|
|
216
|
+
#
|
|
217
|
+
def to_f
|
|
218
|
+
zoned_date_time.to_epoch_second + (zoned_date_time.nano / 1_000_000_000)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
#
|
|
222
|
+
# The offset in seconds from UTC
|
|
223
|
+
#
|
|
224
|
+
# @return [Integer] The offset from UTC, in seconds
|
|
225
|
+
#
|
|
226
|
+
def utc_offset
|
|
227
|
+
zoned_date_time.offset.total_seconds
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
#
|
|
231
|
+
# Returns true if time represents a time in UTC (GMT)
|
|
232
|
+
#
|
|
233
|
+
# @return [Boolean] true if utc_offset == 0, false otherwise
|
|
234
|
+
#
|
|
235
|
+
def utc?
|
|
236
|
+
utc_offset.zero?
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
#
|
|
240
|
+
# Returns an integer representing the day of the week, 0..6, with Sunday == 0.
|
|
241
|
+
#
|
|
242
|
+
# @return [Integer] The day of week
|
|
243
|
+
#
|
|
244
|
+
def wday
|
|
245
|
+
zoned_date_time.day_of_week.value % 7
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
#
|
|
249
|
+
# The timezone
|
|
250
|
+
#
|
|
251
|
+
# @return [String] The timezone in `[+-]hh:mm(:ss)` format (`Z` for UTC)
|
|
252
|
+
#
|
|
253
|
+
def zone
|
|
254
|
+
zoned_date_time.zone.id
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# @!visibility private
|
|
258
|
+
def respond_to_missing?(method, _include_private = false)
|
|
259
|
+
return true if zoned_date_time.respond_to?(method)
|
|
260
|
+
return true if Time.instance_methods.include?(method.to_sym)
|
|
261
|
+
|
|
262
|
+
super
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
#
|
|
266
|
+
# Forward missing methods to the +ZonedDateTime+ object or a ruby +Time+
|
|
267
|
+
# object representing the same instant
|
|
268
|
+
#
|
|
269
|
+
def method_missing(method, *args, &block)
|
|
270
|
+
return zoned_date_time.send(method, *args, &block) if zoned_date_time.respond_to?(method)
|
|
271
|
+
return to_time.send(method, *args, &block) if Time.instance_methods.include?(method.to_sym)
|
|
272
|
+
|
|
273
|
+
super
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Add other to self
|
|
277
|
+
#
|
|
278
|
+
# @param other [java.time.Duration, String, Numeric]
|
|
279
|
+
#
|
|
280
|
+
# @return [DateTimeType]
|
|
281
|
+
def +(other) # rubocop:disable Metrics
|
|
282
|
+
if other.is_a?(java.time.Duration)
|
|
283
|
+
DateTimeType.new(zoned_date_time.plus(other))
|
|
284
|
+
elsif other.respond_to?(:to_str)
|
|
285
|
+
other = self.class.parse_duration(other.to_str)
|
|
286
|
+
self + other
|
|
287
|
+
elsif other.respond_to?(:to_d)
|
|
288
|
+
DateTimeType.new(zoned_date_time.plusNanos((other.to_d * 1_000_000_000).to_i))
|
|
289
|
+
elsif other.respond_to?(:coerce)
|
|
290
|
+
lhs, rhs = other.coerce(to_d)
|
|
291
|
+
lhs + rhs
|
|
292
|
+
else
|
|
293
|
+
raise TypeError, "\#{other.class} can't be coerced into \#{self.class}"
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Subtract other from self
|
|
298
|
+
#
|
|
299
|
+
# if other is a Duration-like object, the result is a new
|
|
300
|
+
# {DateTimeType} of duration seconds earlier in time.
|
|
301
|
+
#
|
|
302
|
+
# if other is a DateTime-like object, the result is a Duration
|
|
303
|
+
# representing how long between the two instants in time.
|
|
304
|
+
#
|
|
305
|
+
# @param other [java.time.Duration, Time, String, Numeric]
|
|
306
|
+
#
|
|
307
|
+
# @return [DateTimeType, java.Time.Duration]
|
|
308
|
+
def -(other) # rubocop:disable Metrics
|
|
309
|
+
if other.is_a?(java.time.Duration)
|
|
310
|
+
DateTimeType.new(zoned_date_time.minus(other))
|
|
311
|
+
elsif other.respond_to?(:to_time)
|
|
312
|
+
to_time - other.to_time
|
|
313
|
+
elsif other.respond_to?(:to_str)
|
|
314
|
+
time_string = other.to_str
|
|
315
|
+
other = if TIME_ONLY_REGEX.match?(time_string)
|
|
316
|
+
self.class.parse_duration(time_string)
|
|
317
|
+
else
|
|
318
|
+
DateTimeType.parse(time_string)
|
|
319
|
+
end
|
|
320
|
+
self - other
|
|
321
|
+
elsif other.respond_to?(:to_d)
|
|
322
|
+
DateTimeType.new(zoned_date_time.minusNanos((other.to_d * 1_000_000_000).to_i))
|
|
323
|
+
elsif other.respond_to?(:coerce)
|
|
324
|
+
lhs, rhs = other.coerce(to_d)
|
|
325
|
+
lhs - rhs
|
|
326
|
+
else
|
|
327
|
+
raise TypeError, "\#{other.class} can't be coerced into \#{self.class}"
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
# rubocop: enable Metrics/ClassLength
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
end
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'comparable_type'
|
|
4
|
+
require_relative 'numeric_type'
|
|
5
|
+
|
|
6
|
+
module OpenHAB
|
|
7
|
+
module DSL
|
|
8
|
+
module Types
|
|
9
|
+
java_import org.openhab.core.library.types.DecimalType
|
|
10
|
+
|
|
11
|
+
#
|
|
12
|
+
# Add methods to core OpenHAB DecimalType to make it behave as a Ruby
|
|
13
|
+
# BigDecimal object
|
|
14
|
+
#
|
|
15
|
+
#
|
|
16
|
+
# Any method not explicitly defined is forwarded to the +BigDecimal+
|
|
17
|
+
# representation of this object.
|
|
18
|
+
#
|
|
19
|
+
class DecimalType
|
|
20
|
+
# @!parse include Type
|
|
21
|
+
include NumericType
|
|
22
|
+
include ComparableType
|
|
23
|
+
|
|
24
|
+
#
|
|
25
|
+
# Create a new instance of DateTimeType
|
|
26
|
+
#
|
|
27
|
+
# @param value [java.math.BigDecimal, Items::NumericItem, Numeric]
|
|
28
|
+
def initialize(*args) # rubocop:disable Metrics
|
|
29
|
+
unless args.length == 1
|
|
30
|
+
super
|
|
31
|
+
return
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
value = args.first
|
|
35
|
+
if value.is_a?(java.math.BigDecimal)
|
|
36
|
+
super
|
|
37
|
+
elsif value.is_a?(BigDecimal)
|
|
38
|
+
super(value.to_java)
|
|
39
|
+
elsif value.is_a?(DecimalType)
|
|
40
|
+
super(value.to_big_decimal)
|
|
41
|
+
elsif value.is_a?(Items::NumericItem) ||
|
|
42
|
+
(value.is_a?(Items::GroupItem) && value.base_item.is_a?(Items::NumericItem))
|
|
43
|
+
super(value.state)
|
|
44
|
+
elsif value.respond_to?(:to_d)
|
|
45
|
+
super(value.to_d.to_java)
|
|
46
|
+
else # rubocop:disable Lint/DuplicateBranch
|
|
47
|
+
# duplicates the Java BigDecimal branch, but that needs to go first
|
|
48
|
+
# in order to avoid unnecessary conversions
|
|
49
|
+
super
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
#
|
|
54
|
+
# Convert DecimalType to a QuantityType
|
|
55
|
+
#
|
|
56
|
+
# @param [Object] other String or Unit representing an OpenHAB Unit
|
|
57
|
+
#
|
|
58
|
+
# @return [QuantityType] +self+ as a {QuantityType} of the supplied Unit
|
|
59
|
+
#
|
|
60
|
+
def |(other)
|
|
61
|
+
other = org.openhab.core.types.util.UnitUtils.parse_unit(other.to_str) if other.respond_to?(:to_str)
|
|
62
|
+
QuantityType.new(to_big_decimal, other)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
#
|
|
66
|
+
# Comparison
|
|
67
|
+
#
|
|
68
|
+
# @param [NumericType, Items::NumericItem, Numeric]
|
|
69
|
+
# other object to compare to
|
|
70
|
+
#
|
|
71
|
+
# @return [Integer, nil] -1, 0, +1 depending on whether +other+ is
|
|
72
|
+
# less than, equal to, or greater than self
|
|
73
|
+
#
|
|
74
|
+
# nil is returned if the two values are incomparable
|
|
75
|
+
#
|
|
76
|
+
def <=>(other) # rubocop:disable Metrics
|
|
77
|
+
logger.trace("(#{self.class}) #{self} <=> #{other} (#{other.class})")
|
|
78
|
+
if other.is_a?(QuantityType)
|
|
79
|
+
(other <=> self)&.-@
|
|
80
|
+
elsif other.is_a?(self.class)
|
|
81
|
+
compare_to(other)
|
|
82
|
+
elsif other.is_a?(Items::NumericItem) ||
|
|
83
|
+
(other.is_a?(Items::GroupItem) && other.base_item.is_a?(NumericItem))
|
|
84
|
+
return nil unless other.state?
|
|
85
|
+
|
|
86
|
+
self <=> other.state
|
|
87
|
+
elsif other.respond_to?(:to_d)
|
|
88
|
+
to_d <=> other.to_d
|
|
89
|
+
elsif other.respond_to?(:coerce)
|
|
90
|
+
lhs, rhs = other.coerce(self)
|
|
91
|
+
lhs <=> rhs
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
#
|
|
96
|
+
# Type Coercion
|
|
97
|
+
#
|
|
98
|
+
# Coerce object to a DecimalType
|
|
99
|
+
#
|
|
100
|
+
# @param [Items::NumericItem, Numeric, Type] other object to
|
|
101
|
+
# coerce to a {DecimalType}
|
|
102
|
+
#
|
|
103
|
+
# if +other+ is a {Type}, +self+ will instead be coerced
|
|
104
|
+
# to that type to accomodate comparison with things such as {OnOffType}
|
|
105
|
+
#
|
|
106
|
+
# @return [[DecimalType, DecimalType]]
|
|
107
|
+
#
|
|
108
|
+
def coerce(other) # rubocop:disable Metrics
|
|
109
|
+
logger.trace("Coercing #{self} as a request from #{other.class}")
|
|
110
|
+
if other.is_a?(Items::NumericItem) ||
|
|
111
|
+
(other.is_a?(Items::GroupItem) && other.base_item.is_a?(Items::NumericItem))
|
|
112
|
+
raise TypeError, "can't convert #{UnDefType} into #{self.class}" unless other.state?
|
|
113
|
+
|
|
114
|
+
[other.state, self]
|
|
115
|
+
elsif other.is_a?(Type)
|
|
116
|
+
[other, as(other.class)]
|
|
117
|
+
elsif other.respond_to?(:to_d)
|
|
118
|
+
[self.class.new(other.to_d), self]
|
|
119
|
+
else
|
|
120
|
+
raise TypeError, "can't convert #{other.class} into #{self.class}"
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
#
|
|
125
|
+
# Unary minus
|
|
126
|
+
#
|
|
127
|
+
# Negates self
|
|
128
|
+
#
|
|
129
|
+
# @return [DecimalType]
|
|
130
|
+
def -@
|
|
131
|
+
self.class.new(to_big_decimal.negate)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
{
|
|
135
|
+
:add => :+,
|
|
136
|
+
:subtract => :-,
|
|
137
|
+
:multiply => :*,
|
|
138
|
+
:divide => :/,
|
|
139
|
+
:remainder => :%,
|
|
140
|
+
:pow => :**
|
|
141
|
+
}.each do |java_op, ruby_op|
|
|
142
|
+
class_eval( # rubocop:disable Style/DocumentDynamicEvalDefinition https://github.com/rubocop/rubocop/issues/10179
|
|
143
|
+
# def +(other)
|
|
144
|
+
# if other.is_a?(DecimalType)
|
|
145
|
+
# self.class.new(to_big_decimal.add(other.to_big_decimal))
|
|
146
|
+
# elsif other.is_a?(java.math.BigDecimal)
|
|
147
|
+
# self.class.new(to_big_decimal.add(other))
|
|
148
|
+
# elsif other.respond_to?(:to_d)
|
|
149
|
+
# result = to_d + other
|
|
150
|
+
# # result could already be a QuantityType
|
|
151
|
+
# result = self.class.new(result) unless result.is_a?(NumericType)
|
|
152
|
+
# result
|
|
153
|
+
# elsif other.respond_to?(:coerce)
|
|
154
|
+
# lhs, rhs = other.coerce(to_d)
|
|
155
|
+
# lhs + rhs
|
|
156
|
+
# else
|
|
157
|
+
# raise TypeError, "#{other.class} can't be coerced into #{self.class}"
|
|
158
|
+
# end
|
|
159
|
+
# end
|
|
160
|
+
<<~RUBY, __FILE__, __LINE__ + 1
|
|
161
|
+
def #{ruby_op}(other)
|
|
162
|
+
if other.is_a?(DecimalType)
|
|
163
|
+
self.class.new(to_big_decimal.#{java_op}(other.to_big_decimal))
|
|
164
|
+
elsif other.is_a?(java.math.BigDecimal)
|
|
165
|
+
self.class.new(to_big_decimal.#{java_op}(other))
|
|
166
|
+
elsif other.respond_to?(:to_d)
|
|
167
|
+
result = to_d #{ruby_op} other
|
|
168
|
+
# result could already be a QuantityType
|
|
169
|
+
result = self.class.new(result) unless result.is_a?(NumericType)
|
|
170
|
+
result
|
|
171
|
+
elsif other.respond_to?(:coerce)
|
|
172
|
+
lhs, rhs = other.coerce(to_d)
|
|
173
|
+
lhs #{ruby_op} rhs
|
|
174
|
+
else
|
|
175
|
+
raise TypeError, "\#{other.class} can't be coerced into \#{self.class}"
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
RUBY
|
|
179
|
+
)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# any method that exists on BigDecimal gets forwarded to to_d
|
|
183
|
+
delegate (BigDecimal.instance_methods - instance_methods) => :to_d
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|