timesteps 0.9.5 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5c66eb1faabea2c6a697af1b84d2b6e4b36419a7fe67f11a04d448476485697e
4
- data.tar.gz: d32805b977369e85e0bacdce593275b6f5b30569be978cd74f61a355f9b4c67f
3
+ metadata.gz: 8ec2401e1d32acce3ddb048864c34b37de56ad5f83066fa617baeea8c65abc3a
4
+ data.tar.gz: '083c51a6c6fd1f295e7d2359960f5fe893ccc0847bcf014b7928f629c21e1a8e'
5
5
  SHA512:
6
- metadata.gz: f3aba6dae421df42fd53fa5aaaf7b273c9aec3a99d48bd95f9ffd3c0f9b2e6b5a694cc6b8e0c59f3d87355ddb803258dec77d1830280e41e504f85a11ed3b87a
7
- data.tar.gz: 835e7de674cc0dea1b51571b119bfd55922e8fe4215124048bdc0d519dfcd1ec0bcec176f3a918b844e60e4fe68105c7878b165695f6ec82e1da8e87cdb61d6e
6
+ metadata.gz: e242891a7aed17ea1a3c0c138e80382b55422b5d6c9cbeec0c32e0b5725d5979a1e7631b110eb136ca4942c16023b93b325390f28be30792aa15f637a736c09b
7
+ data.tar.gz: 4da60d62efce29eeb13b1fe93d2621cdc616c6d8fe4d421922257bbb5e28c5695504d6804806430feb1dd42adbba7870de37b41758cf3df121dae59b57c1d3bf
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --no-private
File without changes
data/NEWS.md ADDED
@@ -0,0 +1,8 @@
1
+ NEWS
2
+ ====
3
+
4
+ 0.9.8 -> 0.9.9
5
+ --------------
6
+
7
+ * [Mod] TimeStep::Range.new was modified to accept `start` and `last` argument of mixed type (Integer, DateTime, DateTimeLike, String).
8
+
data/Note.ja.md CHANGED
@@ -1,4 +1,13 @@
1
1
  # メモ
2
+
3
+ masterブランチ -> gem リリース最新版
4
+ developブランチ -> 開発統合版
5
+ topicブランチ -> Feature開発版
6
+
7
+
8
+
9
+ "XX since YYY" と since: time を同時に指定下場合は、前者が採用される (sinceオプションは無視される)。
10
+
2
11
  ### 時間の単位 (udunits)
3
12
  * years, year
4
13
  * months,month
data/README.md CHANGED
@@ -2,16 +2,16 @@ timesteps
2
2
  ================
3
3
 
4
4
  A library for handling discrete time series in constant increments.
5
- The main purpose of this library is to describe the time axis
5
+ The primary purpose is to describe the time axis
6
6
  when dealing with time series of observational data and climate data.
7
7
 
8
8
  Features
9
9
  --------
10
10
 
11
- * TimeStep consists of a pair of origin time and a time interval.
11
+ * TimeStep holds an origin time and a unit time interval.
12
12
  * Parsing a time step expression like "hours since 2001-01-01 00:00:00" (originate from udunits library)
13
- * Obtaining time value for index value (0 for the origin time)
14
- * Obtaining index value for time value
13
+ * Obtaining time value for the index value (0 for the origin time)
14
+ * Obtaining index value for the time value
15
15
  * Treating non-standard calendar-type such as 'noleap', 'allleap', and '360_day'
16
16
  * Comparing the index values between different time step definitions
17
17
 
@@ -29,27 +29,26 @@ require "timesteps"
29
29
  Description
30
30
  -----------
31
31
 
32
- This library was created for time conversion and intercomparison of multiple time series data in the case of handling time series data of the type that specifies the time using indexes of time steps since origin time.
32
+ This library is for time conversion and time index comparison of multiple time series. This library treats the time series data of the type that specifies the time using indexes of time steps since origin time.
33
33
 
34
34
  #### Time steps
35
35
 
36
- The main class of this library is the TimeStep class, which holds the origin time and the interval representing the unit time step. When the TimeStep class is initialized, the following notation is used.
36
+ The main class of this library is the TimeStep class, which holds the origin time and the interval representing the unit time step. For the initialization of the TimeStep object, the following notation is used.
37
37
 
38
38
  * "second since 1970-01-01 00:00:00 +00:00"
39
39
  * "hour since 2001-01-01 00:00:00 JST"
40
40
  * "3 days since 2001-01-01 00:00:00 +00:00"
41
41
  * "10 years since 1901-01-01 00:00:00 +00:00"
42
42
 
43
- This is a notation used as a time unit in Unidata's [UDUNITS](https://www.unidata.ucar.edu/software/udunits/) library, and is also used as a unit of time axis in [CF conventions](http://cfconventions.org) of NetCDF (this notation is borrowed from the UDUnit library, but it should be noted that there are many differences).
43
+ These notations are imported from Unidata's [UDUNITS](https://www.unidata.ucar.edu/software/udunits/) library. These are also used in [CF conventions](http://cfconventions.org) of NetCDF. However, note that there are some differences between our library and the udunits library about the date-time expressions.
44
44
 
45
- In this library, the elapsed time from the origin is expressed as an index value. For the case of "3 hours since 2001-01-01 00:00:00", the index values
46
- are expressed as,
45
+ In this library, the elapsed time from the origin is expressed as an index value. For the case of "3 hours since 2001-01-01 00:00:00", the index values are expressed as,
47
46
 
48
47
  * "2001-01-01 00:00:00" => 0 (0 / 3 hours) ( 0 days)
49
48
  * "2001-01-01 03:00:00" => 1 (1 / 3 hours) ((1/8) days)
50
49
  * "2001-01-02 00:00:00" => 8 (8 / 3 hours) ( 1 days)
51
50
 
52
- This is expressed as a Ruby script.
51
+ These are expressed as a Ruby script.
53
52
 
54
53
  ```ruby
55
54
  ts = TimeStep.new("3 hours since 2001-01-01 00:00:00")
@@ -59,18 +58,15 @@ ts.index_at("2001-01-02 00:00:00") ### => 8
59
58
  ts.time_at(0) ### => #<DateTime 2001-01-01T00:00:00 ...>
60
59
  ts.time_at(1) ### => #<DateTime 2001-01-01T03:00:00 ...>
61
60
  ts.time_at(8) ### => #<DateTime 2001-01-02T00:00:00 ...>
62
- ts.days_at(0) ### => 0 [Integer]
63
- ts.days_at(1) ### => (1/8) [Rational]
64
- ts.days_at(8) ### => 1 [Integer]
61
+ ts.duration_at(0) ### => 0 [Integer]
62
+ ts.duration_at(1) ### => (1/8) [Rational]
63
+ ts.duration_at(8) ### => 1 [Integer]
65
64
  ```
66
65
 
67
66
  #### Treatment of year and month units
68
67
 
69
68
  The time units like day, hour, minute, second have constant intervals,
70
- but the time units like years and months are not. So, the year and
71
- month units are given special treatment in this library.
72
- One year is counted at the same month and day as the origin time, and
73
- one month is counted at the same day as the origin time.
69
+ but the time units like years and months are not. So, the year and month units are given special treatment in this library. One year is counted at the same month and day as the origin time, and one month is counted on the same day as the origin time.
74
70
 
75
71
  ```ruby
76
72
  ts = TimeStep.new("year since 2000-01-15 00:00:00")
@@ -106,7 +102,7 @@ The following calendars, including non-standard calendars, can be handled.
106
102
  * allleap, 366_day
107
103
  * 360_day
108
104
 
109
- You can find the description for these calendar at the document of CF-Convensions ([4.4.1 Calendar](http://cfconventions.org/Data/cf-conventions/cf-conventions-1.8/cf-conventions.html#calendar)).
105
+ You can find the description for these calendars at the document of CF-Conventions ([4.4.1 Calendar](http://cfconventions.org/Data/cf-conventions/cf-conventions-1.8/cf-conventions.html#calendar)).
110
106
 
111
107
  ```ruby
112
108
  ts = TimeStep.new("day since 2000-01-01", calendar: "standard")
@@ -119,13 +115,11 @@ ts = TimeStep.new("day since 2000-01-01", calendar: "360_day")
119
115
  ts.time_at(59) ### => #<DateTime::Fixed360Day: 2000-02-30T00:00:00+00:00 ...>
120
116
  ```
121
117
 
122
- In this library, DateTime class is adopted as the object that represents date and time (not Time class). Non-standard calendars ("proleptic_gregorian", "Julian") can be handled by the DateTime class, with appropriate use of the start parameter. But, other non-standard calendars ("noleap", "allleap", "360_day") can not. So, DateTimeLike class and its subclasses DateTime::NoLeap, DateTime::AllLeap, DateTime::Fixed360Day are introduced. Since many (not all) of methods in DateTime class are also implemented in DateTimeLike class, users don't need to be too aware of these class differences.
118
+ In this library, the DateTime class is adopted as the object that represents date and time (not Time class). Non-standard calendars ("proleptic_gregorian", "julian") can be handled by the DateTime class, with appropriate use of the start parameter. But, other non-standard calendars ("noleap", "allleap", "360_day") can not. So, DateTimeLike class and its subclasses DateTime::NoLeap, DateTime::AllLeap, DateTime::Fixed360Day are introduced. Since many (not all) of methods in the DateTime class are also implemented in DateTimeLike class, users don't need to be too aware of these class differences.
123
119
 
124
120
  #### Parsing datetime string
125
121
 
126
- In `standard` calendar, use DateTime.parse as usual.
127
- In other calendar, you can use DateTime.parse_timestamp, which is
128
- special method to parse date time string with specification of calendar.
122
+ In `standard` calendar, use DateTime.parse as usual. In other calendars, you can use DateTime.parse_timestamp, which is a particular method to parse a date-time string with a specification of the calendar.
129
123
 
130
124
  ```ruby
131
125
  DateTime.parse_timestamp("1200-01-01") ### "standard"
@@ -143,14 +137,14 @@ ts = TimeStep.new("days since 2001-01-01", calendar: "allleap")
143
137
  ts.parse("2001-02-29") ### => #<DateTime::AllLeap 2001-02-29T ...>
144
138
  ```
145
139
 
146
- In the UDUNITS library, negative years are treated as BCs and A.D. 0 is treated as non-existent. This is different from how it is handled in Ruby's DateTime class. To parse date time string of UDUNITS type, `bc` option for `Date.parse_timestamp` method.
140
+ In the UDUNITS library, negative years are treated as BCs, and A.D. 0 is treated as non-existent. This is different from how it is handled in Ruby's DateTime class.
147
141
 
148
142
  ```ruby
149
143
  DateTime.parse_timestamp("-0001-01-01")
150
144
  # => #<DateTime: -0001-01-01T00:00:00+00:00 ...>
151
145
  # B.C. 2
152
146
 
153
- DateTime.parse_timestamp("-0001-01-01", bc: true)
147
+ DateTime.parse_timestamp("0000-01-01")
154
148
  # => #<DateTime: 0000-01-01T00:00:00+00:00 ...>
155
149
  # B.C. 1
156
150
 
@@ -159,12 +153,9 @@ DateTime.parse_timestamp("BC 0001-01-01")
159
153
  # B.C. 1
160
154
  ```
161
155
 
162
- #### Comparing two time series
156
+ #### Comparing multiple time series
163
157
 
164
- TimeStep::Pair is a class to compare indices of two time series,
165
- which is initialized by two time step object. It is possible to
166
- compute the other index corresponding to the time represented
167
- by one of the indices using TimeStep::Pair.
158
+ TimeStep::Pair is a class to compare indices of two time series, which is initialized by two time step object. It is possible to compute the other index corresponding to the time represented by one of the indices using TimeStep::Pair.
168
159
 
169
160
 
170
161
  ```ruby
@@ -11,12 +11,10 @@ class DateTime
11
11
  # `DateTime._parse()` is called internally.
12
12
  #
13
13
  # @param spec [String]
14
- # @option bc [Boolean]
15
14
  #
16
15
  # @return [DateTimeFixedDPY]
17
16
 
18
- def self.parse_timestamp (spec, calendar: "standard", bc: false, format: nil)
19
- raise "invalid option 'calendar'" if self != DateTime
17
+ def self.parse_timestamp (spec, format: nil, offset: nil, calendar: "standard", tz: nil)
20
18
  case calendar.downcase.intern
21
19
  when :standard, :gregorian
22
20
  klass = DateTime
@@ -39,25 +37,39 @@ class DateTime
39
37
  end
40
38
  if format
41
39
  hash = DateTime._strptime(spec, format)
42
- raise "timestring doesn't match with format" unless hash
40
+ raise "date-time string '#{spec}' doesn't match with the given format '#{format}'" unless hash
43
41
  else
44
- hash = DateTime._parse(spec)
42
+ if spec =~ /\A([+\-]?\d{1,4})(\-(\d{1,2}))?(\s+(\w{3}|[+\-]\d{1,2}(:?\d{1,2})))?\z/
43
+ year = $1.to_i
44
+ month = $3 ? $3.to_i : 1
45
+ mday = 1
46
+ rest = $4
47
+ hash = DateTime._parse("#{year}-#{month}-#{mday} 00:00:00 #{rest}")
48
+ else
49
+ hash = DateTime._parse(spec)
50
+ end
45
51
  end
46
- year, month, day, hour, minute, second, sec_fraction, offset =
52
+ year, month, day, hour, minute, second, sec_fraction, offset_ =
47
53
  hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset)
48
- if bc and year < 0
49
- year = year + 1
50
- end
51
54
  hour ||= 0
52
55
  minute ||= 0
53
56
  second ||= 0.0
54
57
  sec_fraction ||= 0.0
55
- offset ||= 0
56
- if hour == 24 && minute == 0 && second == 0.0
57
- klass.new(year, month, day, 23, minute, second + sec_fraction, offset.quo(86400), start) + 1.quo(24)
58
+ if offset_
59
+ offset = offset_.quo(86400)
60
+ else
61
+ offset ||= 0
62
+ end
63
+ if tz
64
+ time = tz.local_datetime(year, month, day, hour, minute, second.to_i, sec_fraction.to_r)
58
65
  else
59
- klass.new(year, month, day, hour, minute, second + sec_fraction, offset.quo(86400), start)
66
+ if hour == 24 && minute == 0 && second == 0.0
67
+ time = klass.new(year, month, day, 23, minute, second + sec_fraction, offset, start) + 1.quo(24)
68
+ else
69
+ time = klass.new(year, month, day, hour, minute, second + sec_fraction, offset, start)
70
+ end
60
71
  end
72
+ return time
61
73
  end
62
74
 
63
75
  end
@@ -0,0 +1,64 @@
1
+
2
+ class DateTime
3
+
4
+ def timestep (interval_spec, tz: nil)
5
+ case start
6
+ when Date::ITALY
7
+ calendar = "standard"
8
+ when Date::GREGORIAN
9
+ calendar = "proleptic_gregorian"
10
+ when Date::JULIAN
11
+ calendar = "proleptic_julian"
12
+ end
13
+ return TimeStep.new(interval_spec, since: self, calendar: calendar, tz: tz)
14
+ end
15
+
16
+ def timeperiod (interval_spec, tz: nil, ends: "[]")
17
+ case start
18
+ when Date::ITALY
19
+ calendar = "standard"
20
+ when Date::GREGORIAN
21
+ calendar = "proleptic_gregorian"
22
+ when Date::JULIAN
23
+ calendar = "proleptic_julian"
24
+ end
25
+ return TimePeriod.new(interval_spec, since: self, calendar: calendar, ends: ends, tz: tz)
26
+ end
27
+
28
+ end
29
+
30
+ class DateTime::NoLeap
31
+
32
+ def timestep (interval_spec, tz: nil)
33
+ return TimeStep.new(interval_spec, since: self, clanedar: "noleap", tz: tz)
34
+ end
35
+
36
+ def timeperiod (interval_spec, tz: nil, ends: "[]")
37
+ return TimePeriod.new(interval_spec, since: self, calendar: "noleap", ends: ends, tz: tz)
38
+ end
39
+
40
+ end
41
+
42
+ class DateTime::AllLeap
43
+
44
+ def timestep (interval_spec, tz: nil)
45
+ return TimeStep.new(interval_spec, since: self, clanedar: "allleap", tz: tz)
46
+ end
47
+
48
+ def timeperiod (interval_spec, tz: nil, ends: "[]")
49
+ return TimePeriod.new(interval_spec, since: self, calendar: "allleap", ends: ends, tz: tz)
50
+ end
51
+
52
+ end
53
+
54
+ class DateTime::Fixed360Day
55
+
56
+ def timestep (interval_spec, tz: nil)
57
+ return TimeStep.new(interval_spec, since: self, clanedar: "360day", tz: tz)
58
+ end
59
+
60
+ def timeperiod (interval_spec, tz: nil, ends: "[]")
61
+ return TimePeriod.new(interval_spec, since: self, calendar: "360day", ends: ends, tz: tz)
62
+ end
63
+
64
+ end
@@ -75,16 +75,16 @@ class DateTimeLike
75
75
 
76
76
  def check_valid_datetime
77
77
  unless valid_date?
78
- raise "invalid date"
78
+ raise "invalid date for #{self.class} [year: #{@year}, month: #{@month}, day: #{@day}]"
79
79
  end
80
80
  unless valid_time?
81
- raise "invalid time"
82
- end
81
+ raise "invalid time for #{self.class} [hour: #{@hour}, minute: #{@minute}, second: #{@second}]"
82
+ end
83
83
  end
84
84
 
85
85
  def valid_time?
86
86
  begin
87
- DateTime.new(2001,1,1,@hour,@minute,@second)
87
+ DateTime.new(2001, 1, 1, @hour, @minute, @second)
88
88
  true
89
89
  rescue
90
90
  false
@@ -199,7 +199,7 @@ class DateTimeLike
199
199
  end
200
200
 
201
201
  # Returns the difference between the two dates
202
- # if the other is a date object.
202
+ # if the other is a datetime object.
203
203
  # If the other is a numeric value,
204
204
  # returns a date object pointing other days before self.
205
205
  def - (other_or_days)
@@ -209,7 +209,7 @@ class DateTimeLike
209
209
  when self.class
210
210
  return self.jd - other_or_days.jd
211
211
  else
212
- raise "invalid object for other"
212
+ raise "other shoud be date-time object or numeric value"
213
213
  end
214
214
  end
215
215
 
@@ -398,29 +398,18 @@ class DateTimeLike
398
398
  #
399
399
  # @return [Integer]
400
400
  def difference_in_years (other)
401
- extra = 0
402
- if self.year > other.year && self.compare_md(other) < 0
403
- extra = -1
404
- end
405
- if self.year < other.year && self.compare_md(other) > 0
406
- extra = 1
407
- end
408
- return self.year - other.year + extra
401
+ my = self.new_offset(0)
402
+ other = other.new_offset(0)
403
+ return my.year - other.year + my.compare_md(other).quo(2)
409
404
  end
410
405
 
411
406
  # Calculate difference between the object and other object in months.
412
407
  #
413
408
  # @return [Integer]
414
409
  def difference_in_months (other)
415
- extra = 0
416
- if self.month > other.month && self.compare_d(other) < 0
417
- extra = -1
418
- end
419
- if self.month < other.month && self.compare_d(other) > 0
420
- extra = 1
421
- end
422
- return 12*(self.year - other.year) + self.month - other.month + extra
410
+ my = self.new_offset(0)
411
+ other = other.new_offset(0)
412
+ return 12*(my.year - other.year) + my.month - other.month + my.compare_d(other).quo(2)
423
413
  end
424
-
425
414
  end
426
415
 
File without changes
@@ -1,6 +1,24 @@
1
- require_relative "../timesteps"
1
+ require "timesteps"
2
2
 
3
- class TimeStep
3
+ class DateTime
4
+
5
+ def self.parse_grads_time (time_string)
6
+ if time_string.strip =~ /\A(((\d{2})?(:(\d{2}))?Z)?(\d{2}))?(\w{3})(\d{4})\z/i
7
+ hour = $3 || "00"
8
+ min = $5 || "00"
9
+ day = $6 || "01"
10
+ mon = $7
11
+ year = $8
12
+ time = DateTime.parse("#{year}#{mon}#{day} #{hour}:#{min}:00")
13
+ else
14
+ raise "invalid time format"
15
+ end
16
+ return time
17
+ end
18
+
19
+ end
20
+
21
+ class TimeStep::Range
4
22
 
5
23
  # @private
6
24
  REGEXP_GRADS_TDEF = /\A(?:TDEF\s+)?(\d+)\s+LINEAR\s+([^\s]*?)\s+([^\s]*?)\s*\z/i
@@ -13,8 +31,17 @@ class TimeStep
13
31
  "mo" => "months",
14
32
  "yr" => "years",
15
33
  }
16
-
17
- def self.parse_grads_tdef (tdef_string)
34
+
35
+ module GrADSMixin
36
+
37
+ def parse_grads_time (time_string)
38
+ return DateTime.parse_grads_time(time_string)
39
+ end
40
+
41
+ end
42
+
43
+ def self.from_grads_tdef (tdef_string)
44
+
18
45
  if tdef_string.strip =~ REGEXP_GRADS_TDEF
19
46
  count = $1.to_i
20
47
  time = $2
@@ -22,22 +49,16 @@ class TimeStep
22
49
  else
23
50
  raise "invalid grads tdef string"
24
51
  end
25
- if time =~ /(((\d{2})?(:(\d{2}))?Z)?(\d{2}))?(\w{3})(\d{4})/i
26
- hour = $3 || "00"
27
- min = $5 || "00"
28
- day = $6 || "01"
29
- mon = $7
30
- year = $8
31
- origin = DateTime.parse("#{year}#{mon}#{day} #{hour}:#{min}:00")
32
- else
33
- raise "invalid time format"
34
- end
52
+
53
+ origin = DateTime.parse_grads_time(time)
35
54
 
36
55
  increment = increment.sub(/(mn|hr|dy|mo|yr)/i) {|s| GRADS_INCREMENT[s.downcase]}
37
-
38
- spec = format("%s since %s", increment, origin.strftime("%F %T"))
39
-
40
- return TimeStep.new(spec, count: count)
56
+
57
+ range = TimeStep.new(increment, since: origin).range(count)
58
+ range.extend GrADSMixin
59
+
60
+ return range
41
61
  end
42
62
 
63
+
43
64
  end
@@ -0,0 +1,14 @@
1
+ require 'date'
2
+
3
+ class Time
4
+ def to_datetime
5
+ # Convert seconds + microseconds into a fractional number of seconds
6
+ seconds = sec + Rational(usec, 10**6)
7
+
8
+ # Convert a UTC offset measured in minutes to one measured in a
9
+ # fraction of a day.
10
+ offset = Rational(utc_offset, 60 * 60 * 24)
11
+
12
+ DateTime.new(year, month, day, hour, min, seconds, offset)
13
+ end
14
+ end
@@ -0,0 +1,205 @@
1
+
2
+
3
+ class TimePeriod < TimeStep
4
+
5
+ def initialize (spec, since: nil, format: nil, offset: nil, calendar: "standard", tz: nil, ends: "[]" )
6
+ super(spec, since: since, format: format, offset: offset, calendar: calendar, tz: tz)
7
+ raise "invalid ends specification" unless ends =~ /\A[\[\(][\]\)]\z/
8
+ @ends = ends
9
+ @include_start = ( ends[0] == "[" ) ? true : false
10
+ @include_last = ( ends[1] == "]" ) ? true : false
11
+ @last = time_at(1)
12
+ end
13
+
14
+ attr_reader :last, :ends
15
+
16
+ def start
17
+ return @origin
18
+ end
19
+
20
+ def include_start?
21
+ return @include_start
22
+ end
23
+
24
+ def include_last?
25
+ return @include_last
26
+ end
27
+
28
+ # Returns the value as a string for inspection.
29
+ #
30
+ def inspect
31
+ options = ""
32
+ case @calendar.name
33
+ when "standard", "gregorian"
34
+ else
35
+ options << " calendar='#{calendar.name}'"
36
+ end
37
+ left_paren = @include_start ? "[" : "("
38
+ right_paren = @include_last ? "]" : ")"
39
+ "#<TimePeriod '#{interval_spec}' #{left_paren}#{start.to_s}, #{last.to_s}#{right_paren} #{options}>"
40
+ end
41
+
42
+ def include? (other)
43
+ case other
44
+ when TimePeriod
45
+ if ( other.origin < @origin ) ||
46
+ ( other.origin == @origin && ( not @include_start ) && other.include_start? )
47
+ left = false
48
+ else
49
+ left = true
50
+ end
51
+ if ( @last < other.last ) ||
52
+ ( @last == other.last && ( not @include_last ) && other.include_last? )
53
+ right = false
54
+ else
55
+ right = true
56
+ end
57
+ return left & right
58
+ else
59
+ if @include_start
60
+ left = @origin <= other
61
+ else
62
+ left = @origin < other
63
+ end
64
+ if @include_last
65
+ right = other <= @last
66
+ else
67
+ right = other < @last
68
+ end
69
+ return left & right
70
+ end
71
+ end
72
+
73
+ def overlap? (other)
74
+ case other
75
+ when TimePeriod
76
+ if ( @origin < other.last ) ||
77
+ ( @origin == other.last && @include_start && other.include_last? )
78
+ left = true
79
+ else
80
+ left = false
81
+ end
82
+ if ( other.origin < @last ) ||
83
+ ( other.origin == @last && @include_last && other.include_start? )
84
+ right = true
85
+ else
86
+ right = false
87
+ end
88
+ return left && right
89
+ else
90
+ return include?(other)
91
+ end
92
+ end
93
+
94
+ def contact? (other)
95
+ case other
96
+ when TimePeriod
97
+ if @origin <= other.last
98
+ left = true
99
+ else
100
+ left = false
101
+ end
102
+ if other.origin <= @last
103
+ right = true
104
+ else
105
+ right = false
106
+ end
107
+ return left && right
108
+ else
109
+ left = @origin <= other
110
+ right = other <= @last
111
+ return left & right
112
+ end
113
+ end
114
+
115
+ def merge (other)
116
+ raise "can't merge period without contacted period" unless contact?(other)
117
+ if ( other.origin < @origin ) ||
118
+ ( other.origin == @origin && ( not include_start? ) && other.include_start? )
119
+ left = other.origin
120
+ left_end = other.include_start? ? "[" : "("
121
+ else
122
+ left = @origin
123
+ left_end = include_start? ? "[" : "("
124
+ end
125
+ if ( @last < other.last ) ||
126
+ ( @last == other.last && ( not include_last? ) && other.include_last? )
127
+ right = other.last
128
+ right_end = other.include_last? ? "]" : ")"
129
+ else
130
+ right = @last
131
+ right_end = include_last? ? ")" : "]"
132
+ end
133
+ ridx = index_at(right)
134
+ lidx = index_at(left)
135
+ numeric = (ridx - lidx) * @numeric
136
+ origin = left
137
+ interval_spec = format("%g %s", numeric, @symbol)
138
+ ends = left_end + right_end
139
+ return TimePeriod.new(interval_spec, since: origin, calendar: @calendar, ends: ends)
140
+ end
141
+
142
+ def clip (other)
143
+ raise "can't clip period without contacted period" unless contact?(other)
144
+ if ( @origin < other.origin ) ||
145
+ ( @origin == other.origin && include_start? && ( not other.include_start? ) )
146
+ left = other.origin
147
+ left_end = other.include_start? ? "[" : "("
148
+ else
149
+ left = @origin
150
+ left_end = include_start? ? "[" : "("
151
+ end
152
+ if ( other.last < @last ) ||
153
+ ( other.last == @last && include_last? && ( not other.include_last? ) )
154
+ right = other.last
155
+ right_end = other.include_last? ? "]" : ")"
156
+ else
157
+ right = @last
158
+ right_end = include_last? ? ")" : "]"
159
+ end
160
+ ridx = index_at(right)
161
+ lidx = index_at(left)
162
+ numeric = (ridx - lidx) * @numeric
163
+ origin = left
164
+ interval_spec = format("%g %s", numeric, @symbol)
165
+ ends = left_end + right_end
166
+ return TimePeriod.new(interval_spec, since: origin, calendar: @calendar, ends: ends)
167
+ end
168
+
169
+ def split (time, ends: ")[")
170
+ raise "can't split period without contacted time" unless contact?(time)
171
+ # left
172
+ numeric = index_at(time) * @numeric
173
+ interval_spec = format("%g %s", numeric, @symbol)
174
+ ends = (@include_start ? "[" : "(") + ends[0]
175
+ left_period = TimePeriod.new(interval_spec, since: @origin, calendar: @calendar, ends: ends)
176
+ # right
177
+ numeric = (1 - index_at(time)) * @numeric
178
+ interval_spec = format("%g %s", numeric, @symbol)
179
+ ends = ends[1] + (@include_last ? "]" : ")")
180
+ right_period = TimePeriod.new(interval_spec, since: @origin, calendar: @calendar, ends: ends)
181
+ return left_period, right_period
182
+ end
183
+
184
+ def next
185
+ return TimePeriod.new(interval_spec, since: @last, calendar: @calendar, ends: ends)
186
+ end
187
+
188
+ def prev
189
+ origin = index_at(-1)
190
+ return TimePeriod.new(interval_spec, since: origin, calendar: @calendar, ends: ends)
191
+ end
192
+
193
+ def shift_origin (index, with: "index")
194
+ case with
195
+ when :index, "index"
196
+ return TimePeriod.new(interval_spec, since: time_at(index), calendar: @calendar, ends: ends)
197
+ when :duration, "duration", :days, "days"
198
+ time = @origin + index
199
+ return TimePeriod.new(interval_spec, since: time, calendar: @calendar, ends: ends)
200
+ end
201
+
202
+ end
203
+
204
+ end
205
+