aixm 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|