aixm 1.1.0 → 1.2.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
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +14 -0
- data/README.md +3 -1
- data/lib/aixm/a.rb +29 -15
- data/lib/aixm/association.rb +2 -1
- data/lib/aixm/classes.rb +4 -0
- data/lib/aixm/component/address.rb +15 -9
- data/lib/aixm/component/approach_lighting.rb +28 -25
- data/lib/aixm/component/fato.rb +38 -26
- data/lib/aixm/component/frequency.rb +32 -20
- data/lib/aixm/component/geometry/arc.rb +16 -3
- data/lib/aixm/component/geometry/border.rb +8 -1
- data/lib/aixm/component/geometry/circle.rb +14 -2
- data/lib/aixm/component/geometry/point.rb +8 -1
- data/lib/aixm/component/geometry/rhumb_line.rb +8 -1
- data/lib/aixm/component/geometry.rb +20 -10
- data/lib/aixm/component/helipad.rb +41 -20
- data/lib/aixm/component/layer.rb +31 -20
- data/lib/aixm/component/lighting.rb +22 -24
- data/lib/aixm/component/runway.rb +32 -25
- data/lib/aixm/component/service.rb +11 -17
- data/lib/aixm/component/surface.rb +47 -14
- data/lib/aixm/component/timesheet.rb +178 -0
- data/lib/aixm/component/timetable.rb +32 -13
- data/lib/aixm/component/vasis.rb +36 -6
- data/lib/aixm/component/vertical_limit.rb +26 -4
- data/lib/aixm/component.rb +4 -1
- data/lib/aixm/concerns/hash_equality.rb +21 -0
- data/lib/aixm/concerns/intensity.rb +30 -0
- data/lib/aixm/concerns/marking.rb +21 -0
- data/lib/aixm/concerns/remarks.rb +21 -0
- data/lib/aixm/concerns/timetable.rb +22 -0
- data/lib/aixm/d.rb +20 -14
- data/lib/aixm/document.rb +22 -5
- data/lib/aixm/f.rb +29 -17
- data/lib/aixm/feature/airport.rb +91 -45
- data/lib/aixm/feature/airspace.rb +28 -5
- data/lib/aixm/feature/navigational_aid/designated_point.rb +8 -1
- data/lib/aixm/feature/navigational_aid/dme.rb +12 -2
- data/lib/aixm/feature/navigational_aid/marker.rb +9 -2
- data/lib/aixm/feature/navigational_aid/ndb.rb +15 -3
- data/lib/aixm/feature/navigational_aid/vor.rb +20 -3
- data/lib/aixm/feature/navigational_aid.rb +29 -20
- data/lib/aixm/feature/obstacle.rb +105 -29
- data/lib/aixm/feature/obstacle_group.rb +3 -7
- data/lib/aixm/feature/organisation.rb +23 -14
- data/lib/aixm/feature/unit.rb +23 -11
- data/lib/aixm/feature.rb +23 -4
- data/lib/aixm/memoize.rb +3 -3
- data/lib/aixm/p.rb +20 -14
- data/lib/aixm/payload_hash.rb +5 -2
- data/lib/aixm/r.rb +15 -12
- data/lib/aixm/refinements.rb +42 -2
- data/lib/aixm/schedule/date.rb +181 -0
- data/lib/aixm/schedule/day.rb +114 -0
- data/lib/aixm/schedule/time.rb +255 -0
- data/lib/aixm/shortcuts.rb +3 -0
- data/lib/aixm/version.rb +1 -1
- data/lib/aixm/w.rb +20 -13
- data/lib/aixm/xy.rb +36 -25
- data/lib/aixm/z.rb +29 -17
- data/lib/aixm.rb +13 -0
- data.tar.gz.sig +0 -0
- metadata +22 -13
- metadata.gz.sig +0 -0
@@ -0,0 +1,181 @@
|
|
1
|
+
using AIXM::Refinements
|
2
|
+
|
3
|
+
module AIXM
|
4
|
+
module Schedule
|
5
|
+
|
6
|
+
# Dates suitable for schedules
|
7
|
+
#
|
8
|
+
# This class implements the bare minimum of stdlib +Date+ and adds some
|
9
|
+
# extensions:
|
10
|
+
#
|
11
|
+
# * yearless dates
|
12
|
+
# * {covered_by?} to check whether (yearless) date falls within (yearless)
|
13
|
+
# date range
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# date = AIXM.date('2022-04-20') # => 2022-04-20
|
17
|
+
# from = AIXM.date('03-20') # => XXXX-03-20
|
18
|
+
# date.covered_by?(from..AIXM.date('05-20')) # => true
|
19
|
+
class Date
|
20
|
+
include AIXM::Concerns::HashEquality
|
21
|
+
include Comparable
|
22
|
+
extend Forwardable
|
23
|
+
|
24
|
+
YEARLESS_YEAR = 0
|
25
|
+
|
26
|
+
# @api private
|
27
|
+
attr_accessor :date
|
28
|
+
|
29
|
+
# Parse the given representation of (yearless) date.
|
30
|
+
#
|
31
|
+
# @param date [Date, Time, String] either stdlib Date/Time, "YYYY-MM-DD",
|
32
|
+
# "XXXX-MM-DD" or "MM-DD" (yearless date)
|
33
|
+
def initialize(date)
|
34
|
+
@date = case date.to_s[0, 10]
|
35
|
+
when /\A\d{4}-\d{2}-\d{2}\z/
|
36
|
+
::Date.strptime(date.to_s)
|
37
|
+
when /\A(?:XXXX-)?(\d{2}-\d{2})\z/
|
38
|
+
::Date.strptime("#{YEARLESS_YEAR}-#{$1}")
|
39
|
+
else
|
40
|
+
fail ArgumentError
|
41
|
+
end
|
42
|
+
rescue ::Date::Error
|
43
|
+
raise ArgumentError
|
44
|
+
end
|
45
|
+
|
46
|
+
# Human readable representation such as "2002-05-19" or "XXXX-05-19"
|
47
|
+
#
|
48
|
+
# All formats from {strftime}[https://www.rubydoc.info/stdlib/date/Date#strftime-instance_method]
|
49
|
+
# are supported, however, +%Y+ is replaced with "XXXX" for yearless dates.
|
50
|
+
# Any other formats containing the year won't do so and should be avoided!
|
51
|
+
#
|
52
|
+
# @param format [String] see {strftime}[https://www.rubydoc.info/stdlib/date/Date#strftime-instance_method]
|
53
|
+
# @return [String]
|
54
|
+
def to_s(format='%Y-%m-%d')
|
55
|
+
@date.strftime(yearless? ? format.gsub('%Y', 'XXXX') : format)
|
56
|
+
end
|
57
|
+
|
58
|
+
def inspect
|
59
|
+
%Q(#<#{self.class} #{to_s}>)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Creates a new date with the given parts altered.
|
63
|
+
#
|
64
|
+
# @example
|
65
|
+
# date = AIXM.date('2000-12-22')
|
66
|
+
# date.at(month: 4) # => 2000-04-22
|
67
|
+
# date.at(year: 2020, day: 5) # => 2020-12-05
|
68
|
+
# date.at(month: 1) # => 2020-01-22
|
69
|
+
# date.at(month: 1, wrap: true) # => 2021-01-22 (year incremented)
|
70
|
+
#
|
71
|
+
# @param year [Integer] new year
|
72
|
+
# @param month [Integer] new month
|
73
|
+
# @param day [Integer] new day
|
74
|
+
# @param wrap [Boolean] whether to increment month when crossing month
|
75
|
+
# boundary and year when crossing year boundary
|
76
|
+
# @return [AIXM::Schedule::Date]
|
77
|
+
def at(year: nil, month: nil, day: nil, wrap: false)
|
78
|
+
return self unless year || month || day
|
79
|
+
wrap_month, wrap_year = day&.<(date.day), month&.<(date.month)
|
80
|
+
date = ::Date.new(year || self.year || YEARLESS_YEAR, month || self.month, day || self.day)
|
81
|
+
date = date.next_month if wrap && wrap_month && !month
|
82
|
+
date = date.next_year if wrap && wrap_year && !year
|
83
|
+
self.class.new(date.strftime(yearless? ? '%m-%d' : '%F'))
|
84
|
+
end
|
85
|
+
|
86
|
+
# Create new date one day after this one.
|
87
|
+
#
|
88
|
+
# @return [AIXM::Schedule::Date]
|
89
|
+
def succ
|
90
|
+
self.class.new(date.next_day).at(year: (YEARLESS_YEAR if yearless?))
|
91
|
+
end
|
92
|
+
|
93
|
+
# Convert date to day
|
94
|
+
#
|
95
|
+
# @raise [RuntimeError] if date is yearless
|
96
|
+
# @return [AIXM::Schedule::Day]
|
97
|
+
def to_day
|
98
|
+
fail "cannot convert yearless date" if yearless?
|
99
|
+
AIXM.day(date.wday)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Stdlib Date equivalent using the value of {YEARLESS_YEAR} to represent a
|
103
|
+
# yearless date.
|
104
|
+
#
|
105
|
+
# @return [Date]
|
106
|
+
def to_date
|
107
|
+
@date
|
108
|
+
end
|
109
|
+
|
110
|
+
# Whether the other schedule date can be compared to this one.
|
111
|
+
#
|
112
|
+
# @param other [AIXM::Schedule::Date]
|
113
|
+
# @return [Boolean]
|
114
|
+
def comparable_to?(other)
|
115
|
+
other.instance_of?(self.class) && yearless? == other.yearless?
|
116
|
+
end
|
117
|
+
|
118
|
+
def <=>(other)
|
119
|
+
fail "not comparable" unless comparable_to? other
|
120
|
+
@date.jd <=> other.to_date.jd
|
121
|
+
end
|
122
|
+
|
123
|
+
# @see Object#hash
|
124
|
+
def hash
|
125
|
+
[self.class, @date.jd].hash
|
126
|
+
end
|
127
|
+
|
128
|
+
# Whether this schedule date is yearless or not.
|
129
|
+
#
|
130
|
+
# @return [Boolean]
|
131
|
+
def yearless?
|
132
|
+
@date.year == YEARLESS_YEAR
|
133
|
+
end
|
134
|
+
|
135
|
+
# Yearless duplicate of self
|
136
|
+
#
|
137
|
+
# @return [AIXM::Schedule::Date]
|
138
|
+
def to_yearless
|
139
|
+
yearless? ? self : self.class.new(to_s[5..])
|
140
|
+
end
|
141
|
+
|
142
|
+
# @return [Integer] year or +nil+ if yearless
|
143
|
+
def year
|
144
|
+
@date.year unless yearless?
|
145
|
+
end
|
146
|
+
|
147
|
+
# @!method month
|
148
|
+
# @return [Integer]
|
149
|
+
# @!method day
|
150
|
+
# @return [Integer] day of month
|
151
|
+
def_delegators :@date, :month, :day
|
152
|
+
|
153
|
+
# Whether this schedule date falls within the given range of schedule dates
|
154
|
+
#
|
155
|
+
# @note It is possible to compare dates as well as days.
|
156
|
+
#
|
157
|
+
# @param other [AIXM::Schedule::Date, Range<AIXM::Schedule::Date>,
|
158
|
+
# AIXM::Schedule::Day, Range<AIXM::Schedule::Day>] single schedule
|
159
|
+
# date/day or range of schedule dates/days
|
160
|
+
# @return [Boolean]
|
161
|
+
def covered_by?(other)
|
162
|
+
range = Range.from(other)
|
163
|
+
case
|
164
|
+
when range.first.instance_of?(AIXM::Schedule::Day)
|
165
|
+
range.first.any? || to_day.covered_by?(range)
|
166
|
+
when range.first.yearless?
|
167
|
+
yearless? ? covered_by_yearless_date?(range) : to_yearless.covered_by?(range)
|
168
|
+
else
|
169
|
+
yearless? ? covered_by_yearless_date?(range.first.to_yearless..range.last.to_yearless) : range.cover?(self)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
def covered_by_yearless_date?(range)
|
176
|
+
range.min ? range.cover?(self) : range.first <= self || self <= range.last
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
using AIXM::Refinements
|
2
|
+
|
3
|
+
module AIXM
|
4
|
+
module Schedule
|
5
|
+
|
6
|
+
# Days suitable for schedules
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# from = AIXM.day(:monday) # => :monday
|
10
|
+
# to = AIXM.day(4) # => :thursday
|
11
|
+
# AIXM.day(:tuesday).covered_by?(from..to) # => true
|
12
|
+
class Day
|
13
|
+
include AIXM::Concerns::HashEquality
|
14
|
+
include Comparable
|
15
|
+
|
16
|
+
DAYS = %i(sunday monday tuesday wednesday thursday friday saturday workday day_preceding_workday day_following_workday holiday day_preceding_holiday day_following_holiday any).freeze
|
17
|
+
SORTABLE_DAYS = DAYS[0, 7]
|
18
|
+
|
19
|
+
# Day of the week or special named day
|
20
|
+
#
|
21
|
+
# @return [Symbol] any from {DAYS}
|
22
|
+
attr_reader :day
|
23
|
+
|
24
|
+
# Set the given day of the week or special named day.
|
25
|
+
#
|
26
|
+
# @param day [Symbol, String, Integer] any from {DAYS} or 0=Monday to
|
27
|
+
# 6=Sunday
|
28
|
+
def initialize(day=:any)
|
29
|
+
case day
|
30
|
+
when Symbol, String
|
31
|
+
self.day = day
|
32
|
+
when Integer
|
33
|
+
fail ArgumentError unless day.between?(0, 6)
|
34
|
+
self.day = SORTABLE_DAYS[day]
|
35
|
+
else
|
36
|
+
fail ArgumentError
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Human readable representation such as "monday" or "day preceding workday"
|
41
|
+
#
|
42
|
+
# @return [String]
|
43
|
+
def to_s
|
44
|
+
day.to_s.gsub('_', ' ')
|
45
|
+
end
|
46
|
+
|
47
|
+
def inspect
|
48
|
+
%Q(#<#{self.class} #{to_s}>)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Whether two days are equal.
|
52
|
+
#
|
53
|
+
# @return [Boolean]
|
54
|
+
def ==(other)
|
55
|
+
day == other.day
|
56
|
+
end
|
57
|
+
|
58
|
+
# @see Object#hash
|
59
|
+
def hash
|
60
|
+
[self.class, day].hash
|
61
|
+
end
|
62
|
+
|
63
|
+
# Whether the day is set to :any
|
64
|
+
#
|
65
|
+
# @return [Boolean]
|
66
|
+
def any?
|
67
|
+
day == :any
|
68
|
+
end
|
69
|
+
|
70
|
+
# Whether this schedule day sortable.
|
71
|
+
#
|
72
|
+
# @return [Boolean]
|
73
|
+
def sortable?
|
74
|
+
SORTABLE_DAYS.include? day
|
75
|
+
end
|
76
|
+
|
77
|
+
# Whether this schedule day falls within the given range of schedule
|
78
|
+
# days.
|
79
|
+
#
|
80
|
+
# @note Only weekdays and +:any+ can be computed!
|
81
|
+
#
|
82
|
+
# @param other [AIXM::Schedule::Day, Range<AIXM::Schedule::Day>] single
|
83
|
+
# schedule day or range of schedule days
|
84
|
+
# @raise RuntimeError if anything but workdays or +:any+ are involved
|
85
|
+
# @return [Boolean]
|
86
|
+
def covered_by?(other)
|
87
|
+
range = Range.from other
|
88
|
+
case
|
89
|
+
when any? || range.first.any? || range.last.any?
|
90
|
+
true
|
91
|
+
when !sortable? || !range.first.sortable? || !range.last.sortable?
|
92
|
+
fail "includes unsortables"
|
93
|
+
when range.min
|
94
|
+
range.cover? self
|
95
|
+
else
|
96
|
+
range.first <= self || self <= range.last
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def day=(value)
|
103
|
+
@day = value.to_s.to_sym
|
104
|
+
fail ArgumentError unless DAYS.include? @day
|
105
|
+
end
|
106
|
+
|
107
|
+
# @note Necessary to use this class in Range.
|
108
|
+
def <=>(other)
|
109
|
+
DAYS.index(day) <=> DAYS.index(other.day) || day.to_s <=> other.to_s
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,255 @@
|
|
1
|
+
using AIXM::Refinements
|
2
|
+
|
3
|
+
module AIXM
|
4
|
+
module Schedule
|
5
|
+
|
6
|
+
# Times suitable for schedules
|
7
|
+
#
|
8
|
+
# This class implements the bare minimum of stdlib +Time+ and adds some
|
9
|
+
# extensions:
|
10
|
+
#
|
11
|
+
# * converts to UTC
|
12
|
+
# * date, seconds and milliseconds are ignored
|
13
|
+
# * {covered_by?} to check whether schedule time falls within range of times
|
14
|
+
#
|
15
|
+
# @note The {DATELESS_DATE} is used to mark the date of the internal +Time"
|
16
|
+
# object irrelevant. However, Ruby does not persist end of days as 24:00,
|
17
|
+
# therefore {DATELESS_DATE} + 1 marks this case.
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# time = AIXM.time('21:30') # => 21:30
|
21
|
+
# time.covered_by?(AIXM.time('20:00')..AIXM.time('02:00')) # => true
|
22
|
+
class Time
|
23
|
+
include AIXM::Concerns::HashEquality
|
24
|
+
extend Forwardable
|
25
|
+
|
26
|
+
EVENTS = %i(sunrise sunset).freeze
|
27
|
+
PRECEDENCES = { first: :min, last: :max }.freeze
|
28
|
+
DATELESS_DATE = ::Date.parse('0000-01-01').freeze
|
29
|
+
|
30
|
+
# @api private
|
31
|
+
attr_accessor :time
|
32
|
+
|
33
|
+
# Event or alternative to time
|
34
|
+
#
|
35
|
+
# @return [Symbol, nil] any from {EVENTS}
|
36
|
+
attr_reader :event
|
37
|
+
|
38
|
+
# Minutes added or subtracted from event
|
39
|
+
#
|
40
|
+
# @return [Integer, nil]
|
41
|
+
attr_reader :delta
|
42
|
+
|
43
|
+
# Precedence of time vs. event
|
44
|
+
#
|
45
|
+
# @return [Symbol, nil] any key of {PRECEDENCES}
|
46
|
+
attr_reader :precedence
|
47
|
+
|
48
|
+
# Parse the given representation of time.
|
49
|
+
#
|
50
|
+
# @example
|
51
|
+
# AIXM.time('08:00')
|
52
|
+
# AIXM.time(:sunrise)
|
53
|
+
# AIXM.time(:sunrise, plus: 30)
|
54
|
+
# AIXM.time('08:00', or: :sunrise)
|
55
|
+
# AIXM.time('08:00', or: :sunrise, plus: 30)
|
56
|
+
# AIXM.time('08:00', or: :sunrise, minus: 15)
|
57
|
+
# AIXM.time('08:00', or: :sunrise, whichever_comes: :last)
|
58
|
+
#
|
59
|
+
# @param time_or_event [Time, DateTime, String, Symbol] either time as
|
60
|
+
# stdlib Time or DateTime, "HH:MM" (implicitly UTC), "HH:MM [+-]00:00",
|
61
|
+
# "HH:MM UTC" or event from {EVENTS} as Symbol
|
62
|
+
# @param or [Symbol] alternative event from {EVENTS}
|
63
|
+
# @param plus [Integer] minutes added to event
|
64
|
+
# @param minus [Integer] minutes subtracted from event
|
65
|
+
# @param whichever_comes [Symbol] any key from {PRECEDENCES}
|
66
|
+
def initialize(time_or_event, or: nil, plus: 0, minus: 0, whichever_comes: :first)
|
67
|
+
alternative_event = binding.local_variable_get(:or) # necessary since "or" is a keyword
|
68
|
+
@time = @event = @precedence = nil
|
69
|
+
case time_or_event
|
70
|
+
when Symbol
|
71
|
+
self.event = time_or_event
|
72
|
+
when ::Time, DateTime
|
73
|
+
time_or_event = time_or_event.to_time
|
74
|
+
set_time(time_or_event.hour, time_or_event.min, time_or_event.utc_offset)
|
75
|
+
when /\A(\d{2}):?(\d{2}) ?([+-]\d{2}:?\d{2}|UTC)?\z/
|
76
|
+
set_time($1, $2, $3)
|
77
|
+
else
|
78
|
+
fail(ArgumentError, "time or event not recognized")
|
79
|
+
end
|
80
|
+
fail(ArgumentError, "only one event allowed") if event && alternative_event
|
81
|
+
self.event ||= alternative_event
|
82
|
+
@delta = event ? plus - minus : 0
|
83
|
+
if @time && event
|
84
|
+
self.precedence = whichever_comes
|
85
|
+
fail(ArgumentError, "mandatory precedence missing") unless precedence
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Human readable representation
|
90
|
+
#
|
91
|
+
# The format recognises does the following interpolations:
|
92
|
+
# * +%R+ - "HH:MM" in UTC if time is present, "" otherwise
|
93
|
+
# * +%z+ - "UTC" if time is present, "" otherwise
|
94
|
+
# * +%o+ - "or" if both time and event are present, "" otherwise
|
95
|
+
# * +%E+ - "sunrise-15min" if no event is present, "" otherwise
|
96
|
+
# * +%P+ - "whichever comes first" if precedence is present, "" otherwise
|
97
|
+
#
|
98
|
+
# @param format [String]
|
99
|
+
# @return [String]
|
100
|
+
def to_s(format='%R %z %o %E %P')
|
101
|
+
format.gsub(/%[RzoEP]/,
|
102
|
+
'%R' => (sprintf("%02d:%02d", hour, min) if @time),
|
103
|
+
'%z' => ('UTC' if @time),
|
104
|
+
'%o' => ('or' if @time && event),
|
105
|
+
'%E' => "#{event}#{sprintf("%+dmin", delta) unless delta.zero?}",
|
106
|
+
'%P' => ("whichever comes #{precedence}" if precedence)
|
107
|
+
).compact
|
108
|
+
end
|
109
|
+
|
110
|
+
def inspect
|
111
|
+
%Q(#<#{self.class} #{to_s}>)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Creates a new time with the given parts altered.
|
115
|
+
#
|
116
|
+
# @example
|
117
|
+
# time = AIXM.time('22:12')
|
118
|
+
# time.at(min: 0) # => 22:00
|
119
|
+
# time.at(min: 0 wrap: true) # => 2021-01-22 (year incremented)
|
120
|
+
#
|
121
|
+
# @param hour [Integer] new hour
|
122
|
+
# @param min [Integer] new minutes
|
123
|
+
# @param wrap [Boolean] whether to increment hour when crossing minutes
|
124
|
+
# boundary
|
125
|
+
# @return [AIXM::Schedule::Date]
|
126
|
+
def at(hour: nil, min: nil, wrap: false)
|
127
|
+
return self unless hour || min
|
128
|
+
min ||= time.min
|
129
|
+
hour ||= time.hour
|
130
|
+
hour = (hour + 1) % 24 if wrap && min < time.min
|
131
|
+
self.class.new("%02d:%02d" % [hour, min])
|
132
|
+
end
|
133
|
+
|
134
|
+
# Resolve event to simple time
|
135
|
+
#
|
136
|
+
# * If +self+ doesn't have any event, +self+ is returned.
|
137
|
+
# * Otherwise a new time is created with the event resolved for the
|
138
|
+
# given date and geographical location.
|
139
|
+
#
|
140
|
+
# @example
|
141
|
+
# time = AIXM.time('21:00', or: :sunset, minus: 30, whichever_cones: first)
|
142
|
+
# time.resolve(on: AIXM.date('2000-08-01'), at: AIXM.xy(lat: 48.8584, long: 2.2945))
|
143
|
+
# # => 20:50
|
144
|
+
#
|
145
|
+
# @param on [AIXM::Date] defaults to today
|
146
|
+
# @param xy [AIXM::XY]
|
147
|
+
# @return [AIXM::Schedule::Time, self]
|
148
|
+
def resolve(on:, xy:)
|
149
|
+
if resolved?
|
150
|
+
self
|
151
|
+
else
|
152
|
+
sun_time = self.class.new(Sun.send(event, on.to_date, xy.lat, xy.long).utc + (delta * 60))
|
153
|
+
if time
|
154
|
+
self.class.new([sun_time.time, self.time].send(PRECEDENCES.fetch(precedence)))
|
155
|
+
else
|
156
|
+
sun_time
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Whether this time is resolved and doesn't contain an event (anymore).
|
162
|
+
#
|
163
|
+
# @return [Boolean]
|
164
|
+
def resolved?
|
165
|
+
!event
|
166
|
+
end
|
167
|
+
|
168
|
+
# Stdlib Time equivalent using the value of {DATELESS_DATE} to represent a
|
169
|
+
# time only.
|
170
|
+
#
|
171
|
+
# @return [Time]
|
172
|
+
def to_time
|
173
|
+
@time
|
174
|
+
end
|
175
|
+
|
176
|
+
# Hour from 0 (beginning of day) to 24 (end of day)
|
177
|
+
#
|
178
|
+
# @return [Integer]
|
179
|
+
def hour
|
180
|
+
@time.hour + (end_of_day? ? 24 : 0)
|
181
|
+
end
|
182
|
+
|
183
|
+
# @!method min
|
184
|
+
# @return [Integer]
|
185
|
+
def_delegators :@time, :min
|
186
|
+
|
187
|
+
# Whether two times are equal.
|
188
|
+
#
|
189
|
+
# @return [Boolean]
|
190
|
+
def ==(other)
|
191
|
+
to_s == other.to_s
|
192
|
+
end
|
193
|
+
|
194
|
+
# Whether this schedule time is sortable.
|
195
|
+
#
|
196
|
+
# @return [Boolean]
|
197
|
+
def sortable?
|
198
|
+
!event
|
199
|
+
end
|
200
|
+
|
201
|
+
# Whether this schedule time falls within the given range of schedule
|
202
|
+
# times.
|
203
|
+
#
|
204
|
+
# @param other [AIXM::Schedule::Time, Range<AIXM::Schedule::Time>] single
|
205
|
+
# schedule time or range of schedule times
|
206
|
+
# @raise RuntimeError if either self is or the range contains an
|
207
|
+
# unsortable time with event
|
208
|
+
# @return [Boolean]
|
209
|
+
def covered_by?(other)
|
210
|
+
range = Range.from(other)
|
211
|
+
case
|
212
|
+
when !sortable? || !range.first.sortable? || !range.last.sortable?
|
213
|
+
fail "includes unsortables"
|
214
|
+
when range.min
|
215
|
+
range.first.to_s <= self.to_s && self.to_s <= range.last.to_s
|
216
|
+
else
|
217
|
+
range.first.to_s <= self.to_s || self.to_s <= range.last.to_s
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
private
|
222
|
+
|
223
|
+
# Set the +@time+ instance variable.
|
224
|
+
#
|
225
|
+
# @param hour [Integer, String]
|
226
|
+
# @param min [Integer, String]
|
227
|
+
# @param offset [Integer, String] either UTC offset in seconds
|
228
|
+
# (default: 0) or 'UTC'
|
229
|
+
# @return [Time]
|
230
|
+
def set_time(hour, min, offset)
|
231
|
+
@time = ::Time.new(DATELESS_DATE.year, DATELESS_DATE.month, DATELESS_DATE.day, hour, min, 0, offset || 0).utc
|
232
|
+
end
|
233
|
+
|
234
|
+
def event=(value)
|
235
|
+
fail ArgumentError if value && !EVENTS.include?(value)
|
236
|
+
@event = value
|
237
|
+
end
|
238
|
+
|
239
|
+
def precedence=(value)
|
240
|
+
fail ArgumentError if value && !PRECEDENCES.has_key?(value)
|
241
|
+
@precedence = value
|
242
|
+
end
|
243
|
+
|
244
|
+
def end_of_day?
|
245
|
+
@time.day > DATELESS_DATE.day
|
246
|
+
end
|
247
|
+
|
248
|
+
# @note Necessary to use this class in Range.
|
249
|
+
def <=>(other)
|
250
|
+
to_time <=> other.to_time || to_s <=> other.to_s
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
end
|
255
|
+
end
|
data/lib/aixm/shortcuts.rb
CHANGED
data/lib/aixm/version.rb
CHANGED
data/lib/aixm/w.rb
CHANGED
@@ -7,6 +7,7 @@ module AIXM
|
|
7
7
|
# @example
|
8
8
|
# AIXM.w(2.9, :t)
|
9
9
|
class W
|
10
|
+
include AIXM::Concerns::HashEquality
|
10
11
|
include Comparable
|
11
12
|
extend Forwardable
|
12
13
|
|
@@ -17,16 +18,29 @@ module AIXM
|
|
17
18
|
ton: { kg: 907.18474, t: 0.90718474, lb: 2000.00000013718828 }
|
18
19
|
}.freeze
|
19
20
|
|
21
|
+
# Whether weight is zero.
|
22
|
+
#
|
20
23
|
# @!method zero?
|
21
|
-
#
|
24
|
+
# @return [Boolean]
|
22
25
|
def_delegator :@wgt, :zero?
|
23
26
|
|
24
|
-
#
|
27
|
+
# Weight
|
28
|
+
#
|
29
|
+
# @overload wgt
|
30
|
+
# @return [Float]
|
31
|
+
# @overload wgt=(value)
|
32
|
+
# @param value [Float]
|
25
33
|
attr_reader :wgt
|
26
34
|
|
27
|
-
#
|
35
|
+
# Unit
|
36
|
+
#
|
37
|
+
# @overload unit
|
38
|
+
# @return [Float] any of {UNITS}
|
39
|
+
# @overload unit=(value)
|
40
|
+
# @param value [Float] any of {UNITS}
|
28
41
|
attr_reader :unit
|
29
42
|
|
43
|
+
# See the {overview}[AIXM::W] for examples.
|
30
44
|
def initialize(wgt, unit)
|
31
45
|
self.wgt, self.unit = wgt, unit
|
32
46
|
end
|
@@ -52,12 +66,13 @@ module AIXM
|
|
52
66
|
fail(ArgumentError, "invalid unit") unless UNITS.has_key? @unit
|
53
67
|
end
|
54
68
|
|
69
|
+
# Convert weight
|
70
|
+
#
|
55
71
|
# @!method to_kg
|
56
72
|
# @!method to_t
|
57
73
|
# @!method to_lb
|
58
74
|
# @!method to_ton
|
59
|
-
#
|
60
|
-
# @return [AIXM::W] convert weight
|
75
|
+
# @return [AIXM::W] converted weight
|
61
76
|
UNITS.each_key do |target_unit|
|
62
77
|
define_method "to_#{target_unit}" do
|
63
78
|
return self if unit == target_unit
|
@@ -66,22 +81,14 @@ module AIXM
|
|
66
81
|
end
|
67
82
|
|
68
83
|
# @see Object#<=>
|
69
|
-
# @return [Integer]
|
70
84
|
def <=>(other)
|
71
85
|
wgt <=> other.send(:"to_#{unit}").wgt
|
72
86
|
end
|
73
87
|
|
74
88
|
# @see Object#==
|
75
|
-
# @return [Boolean]
|
76
89
|
def ==(other)
|
77
90
|
self.class === other && (self <=> other).zero?
|
78
91
|
end
|
79
|
-
alias_method :eql?, :==
|
80
92
|
|
81
|
-
# @see Object#hash
|
82
|
-
# @return [Integer]
|
83
|
-
def hash
|
84
|
-
to_s.hash
|
85
|
-
end
|
86
93
|
end
|
87
94
|
end
|